Testbenching is one of the most important tools in FPGA design and development. I personally feel like it's one of the most overlooked topics in a lot of beginner FPGA courses, which is why I decided to do this simple demonstration of the behavioral simulation flow in Vivado. Vivado's behavioral simulation runs a specified testbench module and displays the logic of the testbench's results in a waveform window. This allows a developer to verify the proper functionality of every RTL module in a design at any time without needing to run synthesis or implementation (place & routing).
Currently I'm still using Vivado 2019.2, but this behavioral simulation flow has been the same and remains the same over virtually every release of Vivado.
Add RTL Module as a Design SourceStart by adding a design source for the RTL module to the Vivado project. I decided on writing a simple SR (Set - Reset) flip flop module in Verilog.
The truth table for the SR flip flop outlines the expected behavior of it based upon the current and previous state of its inputs.
When set is zero and reset is one, the Q output is one while the ~Q (Q not) is the inverse of Q (zero). Then when set is one and reset is zero, the Q output is zero while the ~Q (Q not) is again the inverse of Q (one).
When both set and reset are equal to one, Q and ~Q maintain their current values. Set and reset both being set to zero however is an invalid condition resulting in undefined behavior. I mirror this by setting both Q and ~Q to high-impedance Z in my RTL module.
I also added an enable signal that will allow for normal behavior of the flip flop when set high, but disables it and sets both outputs to zero when set low.
module sr_flipflop(
input clk,
input set,
input reset,
input ena,
output reg q,
output reg q_n
);
always @ (posedge clk)
begin
if (ena == 1'b1)
begin
if (set == 1'b1 && reset == 1'b1)
begin
q <= q;
q_n <= ~q;
end
else if (set == 1'b1 && reset == 1'b0)
begin
q <= 1'b0;
q_n <= 1'b1;
end
else if (set == 1'b0 && reset == 1'b1)
begin
q <= 1'b1;
q_n <= 1'b0;
end
else
begin
q <= 1'bz;
q_n <= 1'bz;
end
end
else
begin
q <= 1'b0;
q_n <= 1'b0;
end
end
endmodule
Add the Testbench Module as a Simulation SourceCreate/Add your testbench design file as a simulation source.
I designed my testbench to walk through the SR flip flop truth table twice, once with enable set high, and again with enable set low. This fully encompasses all possible conditions my SR flip flop RTL module may see while in use. Each pass of the SR flip flop truth table executes in a 100ns period with each state of the inputs (set and reset) staying static for 25ns. This gives my testbench a total runtime of 200ns.
Since a behavioral simulation is targeting a specific RTL module independent of the rest of the design, the testbench needs to generate all of the signals for the module's inputs. This includes a clock source.
For the most part, the clock the testbench supplies to the module should be the same frequency as what the module will be sourced in the overall design.
Along with the clock and input signals, the target RTL module to be tested must be instantiated in the testbench module.
module tb_sr_flipflop;
reg clk, set, reset, ena;
wire q, q_n;
/*
* Generate a 100Mhz (10ns) clock
*/
always begin
clk = 1; #5;
clk = 0; #5;
end
/*
* ena signal behavior
*/
always begin
ena = 1; #100;
ena = 0; #100;
end
/*
* set signal behavior
*/
always begin
set = 1; #25; // set = 1 reset = 1 ena = 1 so q = q q_n = ~q
set = 1; #25; // set = 1 reset = 0 ena = 1 so q = 0 q_n = 1
set = 0; #25; // set = 0 reset = 1 ena = 1 so q = 1 q_n = 0
set = 0; #25; // set = 0 reset = 0 ena = 1 so q = z q_n = z
set = 1; #25; // set = 1 reset = 1 ena = 0 so q = 0 q_n = 0
set = 1; #25; // set = 1 reset = 0 ena = 0 so q = 0 q_n = 0
set = 0; #25; // set = 0 reset = 1 ena = 0 so q = 0 q_n = 0
set = 0; #25; // set = 0 reset = 0 ena = 0 so q = 0 q_n = 0
end
/*
* reset signal behavior
*/
always begin
reset = 1; #25; // set = 1 reset = 1 ena = 1 so q = q q_n = ~q
reset = 0; #25; // set = 1 reset = 0 ena = 1 so q = 0 q_n = 1
reset = 1; #25; // set = 0 reset = 1 ena = 1 so q = 1 q_n = 0
reset = 0; #25; // set = 0 reset = 0 ena = 1 so q = z q_n = z
reset = 1; #25; // set = 1 reset = 1 ena = 0 so q = 0 q_ = 0
reset = 0; #25; // set = 1 reset = 0 ena = 0 so q = 0 q_ = 0
reset = 1; #25; // set = 0 reset = 1 ena = 0 so q = 0 q_ = 0
reset = 0; #25; // set = 0 reset = 0 ena = 0 so q = 0 q_ = 0
end
/*
* instantiate target rtl module to test
*/
sr_flipflop sr_flipflop_i(
.clk(clk),
.set(set),
.reset(reset),
.ena(ena),
.q(q),
.q_n(q_n)
);
endmodule
In order for the behavioral simulation to know which testbench to run, the target testbench needs to be set as the top file of the Simulation Sources in the Sources window. Simply right-click and select the Set as Top option.
With the testbench completed, save the file and launch the behavioral simulation from the Run Simulation option in the Flow Navigator window.
Depending on the complexity of the RTL module under test and the testbench, the simulation may take a few moments to run then the Wave Window will appear with the results of the RTL module's response to the stimulus of the testbench.
To add any signals to the wave window that didn't automatically appear there at the launch of the simulation, simply right click on the signal name in the Objects window and select the Add to Wave Window option.
Additional runtime can be added to the simulation and resulting captured waveform. The play button with a subscript "T" adds the additional runtime specified in the box immediately to its right, while the circle arrow symbol button will reset and rerun the whole simulation.
Since I designed my testbench to complete over a time period of 200 ns, I was able to see it run a total of 4 times since the default simulation runtime at launch is 1 us (1000 ns). Depending on if your testbench takes longer than the default 1 us, you'll need to calculate how much extra time needs to be added to the simulation runtime to ensure you'll see the entire testbench logic complete.
When closing the simulation, a prompt will appear asking if you want to save the waveform in the Wave Window. This will also save any customizations you've made to the Wave Window (such as adding/removing any signals) so that you won't have to re-do any of it when you run the simulation again for the first time after closing it.
Comments