Coding for Reuse Course - Module 1 Lesson 3
Preliminary Test Bench

In this lesson we will study how to start developing a test bench concurrently with your project.
1. Starting a Test Bench  

Once you have created the I/O block for your project it's easy to convert it to a preliminary test bench. It is best to develop the test bench concurrently with your project. This will allow you to test some of the modules in your project as you create them, rather than waiting for project completion to address all errors simultaneously.

Create your preliminary test bench from your project I/O block as follows:

  1. Copy your I/O block into a new file '<project>_t.v'. Be sure to create a new header for this file.

  2. Define a term, such as 'SIMULATION', that can be used by the RTL code (which the test bench will instantiate) for conditional compiles related only to simulation. For example:

    `define SIMULATION 1

  3. Set a 'timescale' specification consistent with your needs and any simulator constraints. For example:

    `timescale 1ns/10ps

  4. Eliminate all input signals from the module declaration.

  5. Convert all input signal declarations to registers (some may be converted to wires later if they are created in subsequent modules).

  6. All outputs should remain outputs. All inouts should be converted to outputs.

  7. All output signals should be declared as a wire.

  8. Simulation tools are generally immune from the requirements of Rule 3.7 and inout signals can become wires with multiple drivers. If these signals are pulled up or down externally however; for best results, you should consider making them either a 'tri0' or a tri1'. The tristates originating from the test bench itself should still be confined, as much as possible, to the top level module if the test bench is partitioned; however, when using existing device models within the test bench that have tristate signals, the combination of tristate signals originating from these devices, the device-under-test, and the test bench itself will occur in the top level module.

  9. Create an 'INITIAL' block that sets all input signals to known states. This block MUST always include a compiler '$stop' directive, unless you are willing to wait the hours or days until your disk completely fills up and your computer crashes before you see any results. Some waveform viewers, such as DAI SIGNALSCAN, also require additional directives to control recording. Note that the stop directive delay may be expressed as a definition, or the stop directive itself may be expressed as a definition containing a definition. I recommend using the former for clarity and later use. The stop definition can reside in a separate file (with use of the 'include statement) or, in some simulators, on the command line. For example:

    `define SIMULATION_TIME 8000000

    $recordfile("i8085c_r.trn","sequence");
    $recordvars("Primitives","drivers");
    #(`SIMULATION_TIME) $stop;
  10. Create "always" blocks for your principle clocks so that they run continuously. These clocks MUST use blocking assignments for accurate modelling. Clock periods are best expressed with a "`define statement" (Some advanced simulators also support arguments within the define statement itself; enabling you to use parameters for clock periods as an argument for the define statement; since parameters are evaluated first in the lexical blocks. However, for general use in projects that require continuing support, I cannot recommend this type of construct. Some simulators will also let you set the define statements on the command line.

    Setting your principle clock periods does not require brain surgery, however they must be expressed in 'timescale units. We have chosen to use a timescale of one nanosecond, with a 10 picosecond resolution. Therefore, a 25 Mhz 50% duty cycle clock is expressed as:

    `define CLK_X1_PERIOD 40



      clk_x1 = #(`CLK_X1_PERIOD/2) ~clk_x1;

    end

    Asymmetric clocks may also be expressed with use of an "always" blocks containing conditional statements. Try to ensure that the expressions used can evaluate to an integer value for the time unit base and resolution timespec. For example; a sixty percent duty cycle 10 MHz clock with a 1ns and a 10ps resolution is:

    `define CLK_X1_PERIOD 100

    forever begin

      if(clk_x1== 1'b0) begin
         clk_x1 = #(`CLK_X1_PERIOD/10*4) ~clk_x1;
      end
      else begin
         clk_x1 = #(`CLK_X1_PERIOD/10*6) ~clk_x1;
      end

    end

    Jitter requires a "for" block or nested "for" blocks within the "always" block. Since this mode is not needed within our immediate project; I'll let you experiment on your own.

  11. Cycle your master reset (Rule 5.2) According to your reset specifications. This is best done by setting your signal to a reset state in the "initial" block, and the using an "always" block, the "SIMULATION_TIME" definition, and the additional, "POWER_ON_RESET_TIME" definition.

  12. For large projects, your test bench should be partitioned as if it were a project in itself. In other words, your test bench should model, as closely as possible, a system environment. If you have models for external devices, already, they should be used within the test bench. Our project test bench will require a clock, reset, address latch, address decoder, RAM to serve as stack and scratch pad memory, ROM to supply instructions, simulated I/O devices, simulated DMA devices, a source select mux to choose stimulus data from ROM, RAM, DMA, or I/O, an interrupt generator or generators that can be controlled by the instructions in ROM, and the DUT itself.

    Test bench Block Diagram

    When partitioning a test bench, signals which originate from sub-blocks should be declared as wires in the top level module and, within the skeleton test bench, become registers in the sub-blocks. The initial instantiations for these particular signals will then reside within those originating sub-blocks. Any module that does not supply outputs directly to the top-level module (inputs only modules or those modules whose outputs only connect to a subsequent module) should not be instantiated until it is actually required to provide stimulus signals for the device under test (DUT). In our project this excludes the address latch, ROM, RAM, and simulated I/O from the initial test bench.

  13. Instantiate your project as a device-under-test (DUT) with inputs and the input states of bi-directional signals coming from your test bench. The DUT code must be incorporated into your code via an `include statement.

Preliminary Test Bench

2. Exercise  
  1. Create a 10 Mhz, 30% duty cycle clock named "clk_data" using a definition that can be changed.

  2. (Extra credit). Define the clock from last question with jitter +/-20% unit interval.

  3. An 8251 USART intellectual property has this partial top-level module.

    Create a preliminary test bench block diagram. Search the web for the 8251 USART datasheet if necessary.

  4. Now create a preliminary test bench for the IP using a 24 MHz interface clock and a 307.2 KHz transmit and receive clock.