In this post we look at how we use Verilog to write a basic testbench. We start by looking at the architecture of a Verilog testbench before considering some key concepts in verilog testbench design. This includes modelling time in verilog, the initial block, verilog-initial-block and the verilog system tasks. Finally, we go through a complete verilog testbench example.
MetalsKingdom Product Page
When using verilog to design digital circuits, we normally also create a testbench to stimulate the code and ensure that it functions as expected.
We can write our testbench using a variety of languages, with VHDL, Verilog and System Verilog being the most popular.
System Verilog is widely adopted in industry and is probably the most common language to use. If you are hoping to design FPGAs professionally, then it will be important to learn this skill at some point.
As it is better to focus on one language as a time, this blog post introduces the basic principles of testbench design in verilog. This allows us to test designs while working through the verilog tutorials on this site.
If you are interested in learning more about testbench design using either verilog or SystemVerilog, then there are several excellent courses paid course available on sites such as udemy.
Testbenches consist of non-synthesizable verilog code which generates inputs to the design and checks that the outputs are correct.
The diagram below shows the typical architecture of a simple testbench.
The stimulus block generates the inputs to our FPGA design and the output checker tests the outputs to ensure they have the correct values.
The stimulus and output checker will be in separate files for larger designs. It is also possible to include all of these different elements in a single file.
The main purpose of this post is to introduce the skills which will allow us to test our solutions to the exercises on this site.
Therefore, we don't discuss the output checking block as it adds unnecessary complexity.
Instead, we can use a simulation tool which allows for waveforms to be viewed directly. The freely available software packages from Xilinx (Vivado) and Intel (Quartus) both offer this capability.
Alternatively, open source tools such as icarus verilog can be used in conjunction with GTKWave to run verilog simulations.
We can also make use of EDA playground which is a free online verilog simulation tool.
In this case, we would need to use system tasks to monitor the outputs of our design. This gives us a textual output which we can use to check the state of our signals at given times in our simulation.
The first step in writing a testbench is creating a verilog module which acts as the top level of the test.
Unlike the verilog modules we have discussed so far, we want to create a module which has no inputs or outputs in this case. This is because we want the testbench module to be totally self contained.
The code snippet below shows the syntax for an empty module which we can use as our testbench.
module <module_name> (); // Our testbench code goes here endmodule : <module_name>
After we have created a testbench module, we must then instantiate the design which we are testing. This allows us to connect signals to the design in order to stimulate the code.
We have already discussed how we instantiate modules in the previous post on verilog modules. However, the code snippet below shows how this is done using named instantiation.
<module_name> # ( // If the module uses parameters they are connected here .<parameter_name> (<parameter_value>) ) <instance_name> ( // Connection to the module ports .<port_name> (<signal_name>), .<port_name> (signal_name>) );
Once we have done this, we are ready to start writing our stimulus to the FPGA. This includes generating the clock and reset, as well creating test data to send to the FPGA.
In order to this we need to use some verilog constructs which we have not yet encountered - initial blocks, forever loops and time consuming statements.
We will look at these in more detail before we go through a complete verilog testbench example.
One of the key differences between testbench code and design code is that we don't need to synthesize the testbench.
As a result of this, we can use special constructs which consume time. In fact, this is crucial for creating test stimulus.
We have a construct available to us in Verilog which enables us to model delays. In verilog, we use the # character followed by a number of time units to model delays.
As an example, the verilog code below shows an example of using the delay operator to wait for 10 time units.
#10
One important thing to note here is that there is no semi-colon at the end of the code. When we write code to model a delay in Verilog, this would actually result in compilation errors.
It is also common to write the delay in the same line of code as the assignment. This effectively acts as a scheduler, meaning that the change in signal is scheduled to take place after the delay time.
The code snippet below shows an example of this type of code.
// A is set to 1 after 10 time units #10 a = 1'b1;
So far, we have talked about delays which are ten units of time. This is fairly meaningless until we actually define what time units we should use.
In order to specify the time units that we use during simulation, we use a verilog compiler directive which specifies the time unit and resolution. We only need to do this once in our testbench and it should be done outside of a module.
The code snippet below shows the compiler directive we use to specify the time units in verilog.
`timescale <unit_time> / <resolution>
We use the <unit_time> field to specify the main time unit of our testbench and the <resolution> field to define the resolution of the time units in our simulation.
The <resolution> field is important as we can use non-integer numbers to specify the delay in our verilog code. For example, if we want to have a delay of 10.5ns, we could simply write #10.5 as the delay.
Therefore, the <resolution> field in the compiler directive determines the smallest time step we can actually model in our Verilog code.
Both of the fields in this compiler directive take a time type such as 1ps or 1ns.
In the post on always blocks in verilog, we saw how we can use procedural blocks to execute code sequentially.
Another type of procedural block which we can use in verilog is known as the initial block.
Any code which we write inside an initial block is executed once, and only once, at the beginning of a simulation.
The verilog code below shows the syntax we use for an initial block.
initial begin // Our code goes here end
Unlike the always block, verilog code written within initial block is not synthesizable. As a result of this, we use them almost exclusively for simulation purposes.
However, we can also use initial blocks in our verilog RTL to initialise signals.
When we write stimulus code in our verilog testbench we almost always use the initial block.
To give a better understanding of how we use the initial block to write stimulus in verilog, let's consider a basic example.
For this example imagine that we want to test a basic two input and gate.
To do this, we would need code which generates each of the four possible input combinations.
In addition, we would also need to use the delay operator in order to wait for some time between generating the inputs.
This is important as it allows time for the signals to propagate through our design.
The verilog code below shows the method we would use to write this test within an initial block.
initial begin // Generate each input to an AND gate // Waiting 10 time units between each and_in = 2b'00; #10 and_in = 2b'01; #10 and_in = 2b'10; #10 and_in = 2b'11; end
Although we haven't yet discussed loops, they can be used to perform important functions in Verilog. In fact, we will discuss verilog loops in detail in a later post in this series
However, there is one important type of loop which we can use in a verilog testbench - the forever loop.
When we use this construct we are actually creating an infinite loop. This means we create a section of code which runs contimnuously during our simulation.
The verilog code below shows the syntax we use to write forever loops.
forever begin // our code goes here end
When writing code in other programming languages, we would likely consider an infinite loop as a serious bug which should be avoided.
However, we must remember that verilog is not like other programming languages. When we write verilog code we are describing hardware and not writing software.
Therefore, we have at least one case where we can use an infinite loop - to generate a clock signal in our verilog testbench.
To do this, we need a way of continually inverting the signal at regular intervals. The forever loop provides us with an easy method to implement this.
The verilog code below shows how we can use the forever loop to generate a clock in our testbench. It is important to note that any loops we write must be contained with in a procedural block or generate block.
initial begin clk = 1'b0; forever begin #1 clk = ~clk; end end
When we write testbenches in verilog, we have some inbuilt tasks and functions which we can use to help us.
Collectively, these are known as system tasks or system functions and we can identify them easily as they always begin wtih a dollar symbol.
There are actually several of these tasks available. However, we will only look at three of the most commonly used verilog system tasks - $display, $monitor and $time.
The $display function is one of the most commonly used system tasks in verilog. We use this to output a message which is displayed on the console during simulation.
We use the $display macro in a very similar way to the printf function in C.
If you are looking for more details, kindly visit Test Bench For And Gate.
This means we can easily create text statements in our testbench and use them to display information about the status of our simulation.
We can also use a special character (%) in the string to display signals in our design. When we do this we must also include a format letter which tells the task what format to display the variable in.
The most commonly used format codes are b (binary), d (decimal) and h (hex). We can also include a number in front of this format code to determine the number of digits to display.
The verilog code below shows the general syntax for the $display system task. This code snippet also includes an example use case.
// General syntax $display(<string_to_display>, <variables_to_display); // Example - display value of x as a binary, hex and decimal number $display("x (bin) = %b, x (hex) = %h, x (decimal) = %d", x, x, x);
The full list of different formats we can use with the $display system task are shown in the table below.
Format CodeDescription%b or %BDisplay as binary%d or %DDisplay as decimal%h or %HDisplay as hexidecimal%o or %ODisplay as octal format%c or %CDisplay as ASCII character%m or %MDisplay the hierarchical name of our module%s or %SDisplay as a string%t or %TDisplay as timeThe $monitor function is very similar to the $display function, except that it has slightly more intelligent behaviour.
We use this function to monitor the value of signals in our testbench and display a message whenever one of these signals changes state.
All system tasks are actually ignored by the synthesizer so we could even include $monitor statements in our verilog RTL code, although this is not common.
The general syntax for this system task is shown in the code snippet below. This code snippet also includes an example use case.
// General syntax $monitor(<message_to_display>, <variables_to_display>); // Example - monitor the values of the in_a and in_b signals $monitor("in_a=%b, in_b=%b\n", in_a, in_b);
The final system task which we commonly use in testbenches is the $time function. We use this system task to get the current simulation time.
In our verilog testbenches, we commonly use the $time function together with either the $display or $monitor tasks to display the time in our messages.
The verilog code below shows how we use the $time and $display tasks together to create a message.
$display("Current simulation time = %t", $time);
Now that we have discussed the most important topics for testbench design, let's consider a compete example.
We will use a very simple circuit for this and build a testbench which generates every possible input combination.
The circuit shown below is the one we will use for this example. This consists of a simple two input and gate as well as a flip flip.
The first thing we do in the testbench is declare an empty module to write our testbench code in.
The code snippet below shows the declaration of the module for this testbench.
Note that it is good practise to keep the name of the design being tested and the testbench similar. Normally this is done by simply appending _tb or _test to the end of the design name.
module example_tb (); // Our testbench code goes here endmodule : example_tb
Now that we have a blank testbench module to work with, we need to instantiate the design we are going to test.
As named instantiation is generally easy to maintain than positional instantiation, as well as being easier to understand, this is the method we use.
The code snippet below shows how we would instantiate the DUT, assuming that the signals clk, in_1, in_b and out_q are declared previously.
example_design dut ( .clock (clk), .reset (reset), .a (in_a), .b (in_b), .q (out_q) );
The next thing we do is generate a clock and reset signal in our verilog testbench.
In both cases, we can write the code for this within an initial block. We then use the verilog delay operator to schedule the changes of state.
In the case of the clock signal, we use the forever keyword to continually run the clock signal during our tests.
Using this construct, we schedule an inversion every 1 ns, giving a clock frequency of 500MHz.
This frequency is chosen purely to give a fast simulation time. In reality, 500MHz clock rates in FPGAs are difficult to achieve and the testbench clock frequency should match the frequency of the hardware clock.
The verilog code below shows how the clock and the reset signals are generated in our testbench.
// generate the clock initial begin clk = 1'b0; forever #1 clk = ~clk; end // Generate the reset initial begin reset = 1'b1; #10 reset = 1'b0; end
The final part of the testbench that we need to write is the test stimulus.
In order to test the circuit we need to generate each of the four possible input combinations in turn. We then need to wait for a short time while the signals propagate through our code block.
To do this, we assign the inputs a value and then use the verilog delay operator to allow for propagation through the FPGA.
We also want to monitor the values of the inputs and outputs, which we can do with the $monitor verilog system task.
The code snippet below shows the code for this.
initial begin // Use the monitor task to display the FPGA IO $monitor("time=%3d, in_a=%b, in_b=%b, q=%2b \n", $time, in_a, in_b, q); // Generate each input with a 20 ns delay between them in_a = 1'b0; in_b = 1'b0; #20 in_a = 1'b1; #20 in_a = 1'b0; in_b = 1'b1; #20 in_a = 1'b1; end
The verilog code below shows the testbench example in its entirety.
`timescale 1ns / 1ps module example_tb (); // Clock and reset signals reg clk; reg reset; // Design Inputs and outputs reg in_a; reg in_b; wire out_q; // DUT instantiation example_design dut ( .clock (clk), .reset (reset), .a (in_a), .b (in_b), .q (out_q) ); // generate the clock initial begin clk = 1'b0; forever #1 clk = ~clk; end // Generate the reset initial begin reset = 1'b1; #10 reset = 1'b0; end // Test stimulus initial begin // Use the monitor task to display the FPGA IO $monitor("time=%3d, in_a=%b, in_b=%b, q=%2b \n", $time, in_a, in_b, q); // Generate each input with a 20 ns delay between them in_a = 1'b0; in_b = 1'b0; #20 in_a = 1'b1; #20 in_a = 1'b0; in_b = 1'b1; #20 in_a = 1'b1; end endmodule : example_tb
When using a basic testbench architecture which block generates inputs to the DUT?
show answerThe stimulus block is used to generate inputs to the DUT.
hide answerWrite an empty verilog module which can be used as a verilog testbench.
show answermodule example_tb (); // Our test bench code goes here endmodule : example_tbhide answer
Why is named instantiation generally preferable to positional instantiation.
show answerIt is easier to maintain our code as the module connections are explicitly given.
hide answerWhat is the difference between the $display and $monitor verilog system tasks.
show answerThe $display task runs once whenever it is called. The $monitor task monitors a number of signals and displays a message whenever one of them changes state,
hide answerWrite some verilog code which generates stimulus for a 3 input AND gate with a delay of 10 ns each time the inputs change state.
show answer`timescale 1ns / 1ps intial begin and_in = 3'b000; #10 and_in = 3'b001; #10 and_in = 3'b010; #10 and_in = 3'b011; #10 and_in = 3'b100; #10 and_in = 3'b101; #10 and_in = 3'b110; #10 endhide answer
While we typically follow a standard formula for our Build It section every month, sometimes it's nice to deviate a bit from the norm and explore different types of systems that are a bit more unconventional. One such system is the type of build we use at Maximum PC HQ for testing hardware, known as the open-air test bench. We have several of them deployed throughout the office alongside our standard-issue desktop PCs, and both types of machines serve an important purpose. The standard desktops are great for YouTube and Reddit, and occasional work, while the open-air test benches are used for most of our component testing since they let us swap a video card, CPU, SSD, RAM stick, or even the entire motherboard with minimal effort. When youre using an open test bench setup on top of a desk, youll never again have to dig through the guts of your computer while on your hands and knees, with a flashlight clenched in your teeth. All you need to set up one for yourself is a basic set of spare parts, and it will let you operate like a civilized gentleperson, from the comfort of a chair, without breaking a sweat. With that in mind, we thought we would show you how to build an open air test bench PC !
There are a lot of reasons any died-in-the-wool hardware enthusiast would want to have a test bench up and running at all times. The most obvious is that its great for quickly testing a stick of RAM, a malfunctioning piece of hardware, or benchmarking hardware outside of a system that needs to be used for productivity. At Maximum PC, our bench of choice is the Top Deck Tech Station Kit made by HighSpeed PC ($140, www.highspeedpc.com ). This is a two-tier workbench, where the motherboard sits on the upper tray, and the power supply and storage devices (or other external bay items) sit on the lower tier. The stations legs, rails, and PCI-card support brace are all made of sturdy and nonconductive materials, and the kit supports a decent amount of hardware, too. The top of the tray looks just like a standard motherboard tray in that it has rubber standoffs for clearance. A nylon guide post helps you align add-on cards with their slots in the motherboard, and a bundled neoprene mat helps prevent items in the lower tray from sliding around. In place of your cases power and reset switches, there are switches you plug into the board's front-panel connectors that allow you to turn the machine on, reboot, monitor drive activity, and hear the PC speaker. Yes, they are pricey, but very durable and able to accommodate hardware not even conceived of yet, due to their open-air design and flexibility. As always, there are several things to consider before diving in, so lets take a look at whats involved in letting your hardware go commando.
Storage devices slide into rails pre-installed on the underside of the upper tray, and they only accommodate 3.5-inch drives. The rails also have no holes for drive screws, by designyou just slide the drive in, then slide it out when you're done. If you want to install an SSD, you'll need to order a 2.5-inch rail kit separately at HighSpeedPC.com. Or you can skip the adapter, since SSDs don't need to be near the 120mm fan that cools the devices in that area, and since they have no moving parts they dont need to be stabilized at all times like a spinning hard drive. The rails are long enough to support two 3.5-inch drives, and we put SSDs on the lower tray dangling from their SATA power cables.
A modular power supply is extremely useful when trying to keep your cables organized in an open test bench. If youre not using an optical drive, there's plenty of space in the lower tray alongside the power supply to store the bag that contains the unused cables. Orienting the power supply can be a little tricky, since the 8-pin CPU power cable has to go to the top of the board, the 24-pin motherboard cable goes to the side, and the SATA power cables go to the bottom. Therefore, our preferred setup is to have the cables going toward the top of the motherboard, and the AC power plug facing the "bottom" of the motherboard. We also recommend using a stock CPU cooler since it makes accessing the area around the CPU easier, and if you can, just use the CPU's integrated graphics since it gives you one less PCI Express power cable to deal with. If we're testing a CPU without integrated graphics, we just use an old GPU that doesnt require PCIe power.
Click on page two for the rest of the instructions on how to build an open-air test bench PC .
If you want to learn more, please visit our website Valve Stem Grinder.
Contact me with news and offers from other Future brands Receive from us on behalf of our trusted partners or sponsors