Let us see how we can create a simple testbench environment using SystemVerilog. This will help to get familiar with the usage of SystemVerilog to create a simple testbench.
Consider the following simple memory module as the DUT for which we will create a SystemVerilog Testbench using generic testbench components.
DUT: memory.v
module memory ( clk, rstn, addr, wdata, wr, rdata, rvalid ); input clk; input rstn; input [4:0] addr; input [31:0] wdata; input wr; output reg [31:0] rdata; output reg rvalid; reg [31:0] mem [31:0]; always @ (posedge clk or negedge rstn) begin if (!rstn) begin mem[addr] <= mem[addr]; rdata <= 32'd0; rvalid <= 1'b0; end else begin mem[addr] <= (wr) ? wdata : mem[addr]; rdata <= (!wr)? mem[addr] : rdata; rvalid <= (!wr)? 1'b1 : 1'b0; end end endmodule
As we know to completely verify the DUT, it must be tested with all combinations of inputs.
Input ports: addr, wdata, wr
Output ports: rdata, rvalid
Our testbench must randomize the inputs and obtain the DUT responses from the outputs.
DUT Functionality:
- Active low reset
- Write operation takes place whenever wr pin is high, otherwise it is a read.
- Read data (rdata) will be available one cycle after a read operation is performed.
- Read valid (rvalid) is a pulse that indicates the first cycle of read data.
For signals to communicate with the DUT, we need to create an interface.
interface.sv:
interface mem_if (input bit clk); logic rstn; logic [4:0] addr; logic [31:0] wdata; logic wr; logic [31:0] rdata; logic rvalid; endinterface
Create the port list as a class, declaring the variables to be randomized as rand. This will be useful to store data obtained from the virtual interface. We will call this the item class.
I have added a constraint which limits the address values to between 0x1A and 0x1F.
mem_item.sv:
class mem_item; randc logic [4:0] addr; rand logic [31:0] wdata; bit wr; logic [31:0] rdata; logic rvalid; constraint addr_limit {addr inside {[5'h1A:5'h1F]};} //Constraint endclass
The testbench is organized as follows:
1. Testbench Top
Function: Overall wrapper
- Top module of the testbench which instantiates the DUT
- Create the virtual interface handle and connect the signals accordingly with DUT.
- Generate clock and reset
- Create a handle for test class, connect with virtual interface and call its run task.
- Assertions can be placed here.
- Functional coverage sampling can also be done.
- Many $display statements can be present throughout the tb for easier readability and debug.
module tb_top (); test t0; reg clk; always #10 clk = ~clk; mem_if top_vif (clk); //Virtual interface memory dut ( //DUT .clk (clk), .rstn (top_vif.rstn), .addr (top_vif.addr), .wdata (top_vif.wdata), .wr (top_vif.wr), .rdata (top_vif.rdata), .rvalid (top_vif.rvalid) ); assert property ( //Concurrent Assertion @(posedge clk) disable iff (!top_vif.rstn) !top_vif.wr |-> ##1 top_vif.rvalid ); covergroup FuncCov @(posedge clk); //Functional Coverage option.per_instance=1; coverpoint top_vif.addr { bins feature1 = {[5'h1A:5'h1C]}; bins feature2 = {[5'h1D:5'h1F]}; } coverpoint top_vif.wdata; endgroup initial begin //Clock and reset clk = 1'b0; top_vif.rstn = 1'b0; @(posedge clk); @(posedge clk); #10 top_vif.rstn = 1'b1; end initial begin //Test class and functional coverage sample FuncCov funccov = new(); funccov.sample(); t0 = new; t0.e0.vif = top_vif; t0.run(); @(posedge clk); $display("\n***SIMULATION COMPLETE***"); $display("Total number of scoreboard matches: %d",t0.e0.s0.match); $display("Total number of scoreboard errors: %d",t0.e0.s0.error); $finish; end endmodule
2. Test
Function: Randomization of data according to requirement
Function: Randomization of data according to requirement
- Declare environment class.
- Declare mailbox since randomized data generated in test, is sent across to the driver via mailbox.
- In the run task of test, call the environment's run task to start the environment. Then apply the required stimulus by calling it as a separate task.
- In the stimulus task, we randomize the signals in item class according to requirement.
- Put the randomized data into the mailbox.
class test; environment e0; mailbox mbx_drv; mem_item item; reg [4:0] temp; function new(); e0 = new; item = new; mbx_drv = new(); endfunction virtual task run(); e0.d0.mbx_drv = mbx_drv; $display("T=%0t [Test] Starting stimulus...",$time); fork e0.run(); join_none item.wr = 1'b1; apply_stim(); repeat (10) begin //Apply stimulus multiple times @(e0.d0.drv_done) //Event from driver apply_stim(); end endtask virtual task apply_stim(); //Data randomization $display("\nT=%0t [Test] Applying stimulus...",$time); temp = item.addr; item.randomize(); item.wr = ~item.wr; if (!item.wr) begin item.addr = temp; end if (!e0.vif.rstn) begin item.addr = 5'd0; item.wdata = 32'd0; item.wr = 1'b0; $display("Data is reset"); end mbx_drv.put(item); //Put in mailbox to driver endtask endclass
3. Environment
Function: Holds the driver, monitor and scoreboard together.
Function: Holds the driver, monitor and scoreboard together.
- Declare the components inside the environment, which are driver, monitor and scoreboard.
- In the run task, connect the virtual interface to the components and call their respective run tasks.
- Also connect another mailbox to send data from monitor to the scoreboard.
class environment; driver d0; //Each env component declared monitor m0; scoreboard s0; mailbox mbx_scb; virtual mem_if vif; function new(); d0 = new; m0 = new; s0 = new; mbx_scb = new(); endfunction virtual task run(); m0.mbx_scb = mbx_scb; //Connect mailboxes s0.mbx_scb = mbx_scb; d0.vif = vif; //Connect virtual interfaces m0.vif = vif; s0.vif = vif; fork d0.run(); //Run respective run tasks m0.run(); s0.run(); join_none; endtask endclass
4. Driver
Function: Drive input stimulus to the DUT
- In the run task, we collect data from mailbox given by test class.
- This data is provided to the virtual interface which is inturn, connected to the DUT.
- The completion of data driving for that clock cycle is indicated by an event. This event informs the test class that data has been driven and it is ready to drive the next set of data.
- Forever loop is used because it has to take place until end of simulation time.
class driver; virtual mem_if vif; event drv_done; mailbox mbx_drv; mem_item item; task run(); $display ("T=%0t [Driver] starting...", $time); forever begin mbx_drv.get(item); //Get item from mailbox vif.addr = item.addr; //Drive vif.wr = item.wr; vif.wdata = item.wdata; $display("Driven:"); $display("Addr:%h Wr:%h Wdata:%h ",item.addr,item.wr,item.wdata); @(posedge vif.clk) -> drv_done; //Completion event end endtask
endclass
5. Monitor
Function: Accept DUT responses from the virtual interface and make basic check for validity.
- Monitor performs the opposite function of driver, meaning it accepts data from the virtual interface.
- In run task, data is stored in the item class and sent to the scoreboard via mailbox.
class monitor; virtual mem_if vif; mailbox mbx_scb; mem_item item; task run(); $display ("T=%0t [Monitor] starting...", $time); forever begin if (vif.rstn) begin item = new; item.addr = vif.addr; //Capture vif in item variables item.wr = vif.wr; item.wdata = vif.wdata; item.rdata = vif.rdata; item.rvalid = vif.rvalid; $display("Read data %h sent to scoreboard",item.rdata); mbx_scb.put(item); //Send to scoreboard via mailbox end @(posedge vif.clk); end endtask endclass
6. Scoreboard
Function: Checks for data integrity issues
- Here, we are verifying a simple memory. What we do is during every read operation, we need to check if the data being read is the same as what was written to the memory at the same address.
- In the run task, get the item from mailbox and code the logic to perform the above check.
Logic used for data integrity check:
- Basically, we must try and mimic the DUT. Have a dummy memory in the scoreboard which writes data to the address during write operation.
- During read operation, store the address in the first clock cycle (flag=0).
- In the next clock cycle (flag=1) when read data is available, compare it with the data which was written to the dummy memory at the same address. If same, then it is a scoreboard match. Else, display as scoreboard error.
class scoreboard; virtual mem_if vif; mailbox mbx_scb; mem_item item; logic [4:0] temp_addr; logic flag; integer match; integer error; logic [31:0] mem [31:0]; task run(); flag = 0; match=0; error=0; @(posedge vif.clk); $display ("T=%0t [Scoreboard] starting...", $time); forever begin @(posedge vif.clk); #1; mbx_scb.get(item); //Get item from mailbox $display ("T=%0t [Scoreboard] Item Received...", $time); if (flag==1) //Logic to check data integrity begin flag = 0; if (mem[temp_addr]==item.rdata && item.rvalid) begin $display("Scoreboard Match, Addr:%h Data:%h",temp_addr,item.rdata); match++; end else begin $display ("Scoreboard Error, Data1:%h Data2:%h",mem[temp_addr],item.rdata); error++; end end if (!item.wr) begin temp_addr = item.addr; flag=1; end else begin mem[item.addr] = item.wdata; end end endtask endclass
Display statements from log file:
ncsim> run T=0 [Test] Starting stimulus... T=0 [Test] Applying stimulus... Data is reset T=0 [Driver] starting... Driven: Addr:00 Wr:0 Wdata:00000000 T=0 [Monitor] starting... T=10 [Scoreboard] starting... T=10 [Test] Applying stimulus... Data is reset Driven: Addr:00 Wr:0 Wdata:00000000 T=30 [Test] Applying stimulus... Data is reset Driven: Addr:00 Wr:0 Wdata:00000000 Read data 00000000 sent to scoreboard T=50 [Scoreboard] Item Received... T=50 [Test] Applying stimulus... Driven: Addr:1c Wr:1 Wdata:5bc68746 Read data xxxxxxxx sent to scoreboard T=70 [Test] Applying stimulus... Driven: Addr:1c Wr:0 Wdata:dbbdecb8 T=71 [Scoreboard] Item Received... Scoreboard Error, Data1:xxxxxxxx Data2:xxxxxxxx Read data xxxxxxxx sent to scoreboard T=90 [Test] Applying stimulus... Driven: Addr:1a Wr:1 Wdata:78e7b1bc T=91 [Scoreboard] Item Received... Read data 5bc68746 sent to scoreboard T=110 [Test] Applying stimulus... Driven: Addr:1a Wr:0 Wdata:5af67b86 T=111 [Scoreboard] Item Received... Scoreboard Match, Addr:1c Data:5bc68746 Read data 5bc68746 sent to scoreboard T=130 [Test] Applying stimulus... Driven: Addr:1f Wr:1 Wdata:cf00ca20 T=131 [Scoreboard] Item Received... Read data 78e7b1bc sent to scoreboard T=150 [Test] Applying stimulus... Driven: Addr:1f Wr:0 Wdata:57806fbe T=151 [Scoreboard] Item Received... Scoreboard Match, Addr:1a Data:78e7b1bc Read data 78e7b1bc sent to scoreboard T=170 [Test] Applying stimulus... Driven: Addr:1b Wr:1 Wdata:abf4b73f T=171 [Scoreboard] Item Received... Read data cf00ca20 sent to scoreboard T=190 [Test] Applying stimulus... Driven: Addr:1b Wr:0 Wdata:1c6d4df8 T=191 [Scoreboard] Item Received... Scoreboard Match, Addr:1f Data:cf00ca20 ***SIMULATION COMPLETE*** Total number of scoreboard matches: 3 Total number of scoreboard errors: 1 Simulation complete via $finish(1) at time 210 NS + 0 ./tb_top.sv:53 $finish; ncsim> exit
From here, we can understand what all processes have taken place. At the end of simulation, we can print a summary of how many scoreboard errors were detected.
One error that we see above is due to x value upon coming out of reset, which can be ignored.
Simulation Waveform:
The simulated waveform is shown above. As seen, address has been constrained within the specified values. Read data is matching the write data for the same address.
Assertions:
We can have assertions in the testbench which convey some activity that is expected from the DUT. Here according to the DUT property, rvalid will be asserted exactly one cycle after any read operation. This can be implemented as an assertion so that we can ensure that this property is always satisfied throughout simulation time.
In fact, verification through assertions is a different topic of discussion in itself, known as Formal Verification.
Coverage:
Coverage details can be viewed on a tool like Cadence IMC. We have two types of coverage: Functional and Code coverage.
So this concludes a simple testbench in SystemVerilog. Note that at present, most industries use UVM standard for verification purposes. However, it is always expected that one has a strong hold on SystemVerilog concepts.
References:
Idea has been taken from here and modified with explanations and additional features.
This is a recommended website for self-learning SystemVerilog and UVM concepts.
No comments:
Post a Comment