Until now in previous articles, simple boolean expressions were checked on every clock edge. But sequential checks take several clock cycles to complete and the time delay is specified by ## sign.

## Operator

If a is not high on any given clock cycle, the sequence starts and fails on the same cycle. However, if a is high on any clock, the assertion starts and succeeds if b is high 2 clocks later. It fails if b is low 2 clocks later.

A sequence is a simple building block in SystemVerilog assertions that can represent certain expressions to aid in creating more complex properties.

Simple Sequence

  
  
module tb;
  	bit a;
  	bit clk;
  
	// This sequence states that a should be high on every posedge clk
  	sequence s_a;
      @(posedge clk) a;
    endsequence
  
  	// When the above sequence is asserted, the assertion fails if 'a'
  	// is found to be not high on any posedge clk
  	assert property(s_a);

      
	always #10 clk = ~clk;
      
	initial begin
      for (int i = 0; i < 10; i++) begin        
        a = $random;
        @(posedge clk);
        
        // Assertion is evaluated in the preponed region and 
        // use $display to see the value of 'a' in that region
        $display("[%0t] a=%0d", $time, a);
      end
      #20 $finish;
    end
endmodule

  
Time (ns) a Result
10 0 FAIL
30 1 PASS
50 1 PASS
70 1 PASS
90 1 PASS
110 1 PASS
130 1 PASS
150 0 FAIL
170 1 PASS
190 1 PASS
Simulation Log
Compiler version P-2019.06-1; Runtime version P-2019.06-1;  Jan 14 06:32 2020
[10] a=0
"testbench.sv", 12: tb.unnamed$$_0: started at 10ns failed at 10ns
	Offending 'a'
[30] a=1
[50] a=1
[70] a=1
[90] a=1
[110] a=1
[130] a=1
[150] a=0
"testbench.sv", 12: tb.unnamed$$_0: started at 150ns failed at 150ns
	Offending 'a'
[170] a=1
[190] a=1
$finish called from file "testbench.sv", line 27.
$finish at simulation time                  210
           V C S   S i m u l a t i o n   R e p o r t 
Time: 210 ns

$rose

The system task $rose is used to detect a positive edge of the given signal. In this case $rose of a indicates that a posedge of a is expected to be seen on every posedge of clk. Because SystemVerilog assertions evaluate in the preponed region, it can only detect value of the given signal in the preponed region. When value of the signal is 0 in the first edge and then 1 on the next edge, a positive edge is assumed to have happened. So, this requires 2 clocks to be identified.

  
  
module tb;
  	bit a;
  	bit clk;
  
	// This sequence states that 'a' should rise on every posedge clk
  	sequence s_a;
      @(posedge clk) $rose(a);
    endsequence
  
  	// When the above sequence is asserted, the assertion fails if 
  	// posedge 'a' is not found on every posedge clk
  	assert property(s_a);

      
	// Rest of the testbench stimulus
endmodule

  

See that a positive edge was detected and the assertion passed at 30ns in the image shown below. This is because value of a is 0 at 10ns and 1 at 30ns upon which the assertion completes and is proven to be successful.

Time (ns) a Transition Result
10 0 FAIL
30 1 0->1 PASS
50 1 FAIL
70 1 FAIL
90 1 FAIL
110 1 FAIL
130 1 FAIL
150 0 1->0 FAIL
170 1 0->1 PASS
190 1 FAIL

The behavior is visible in the simulation log where assertion failed at all times other than 30ns and 170ns.

Simulation Log
Compiler version P-2019.06-1; Runtime version P-2019.06-1;  Jan 14 06:58 2020
[10] a=0
"testbench.sv", 12: tb.unnamed$$_0: started at 10ns failed at 10ns
	Offending '$rose(a)'
[30] a=1
[50] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 50ns failed at 50ns
	Offending '$rose(a)'
[70] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 70ns failed at 70ns
	Offending '$rose(a)'
[90] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 90ns failed at 90ns
	Offending '$rose(a)'
[110] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 110ns failed at 110ns
	Offending '$rose(a)'
[130] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 130ns failed at 130ns
	Offending '$rose(a)'
[150] a=0
"testbench.sv", 12: tb.unnamed$$_0: started at 150ns failed at 150ns
	Offending '$rose(a)'
[170] a=1
[190] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 190ns failed at 190ns
	Offending '$rose(a)'
$finish called from file "testbench.sv", line 27.
$finish at simulation time                  210
           V C S   S i m u l a t i o n   R e p o r t 
Time: 210 ns

$fell

The system task $fell is used to detect negative edge of the given signal. In this case $fell of a indicates that a negedge of a is expected to be seen on every posedge of clk. Because SystemVerilog assertions evaluate in the preponed region, it can only detect value of the given signal in the preponed region. When value of the signal is 1 on the first edge and then 0 on the next edge, a negative edge is assumed to have happened. So, this requires 2 clocks to be identified.

  
  
module tb;
  	bit a;
  	bit clk;
  
	// This sequence states that 'a' should fall on every posedge clk
  	sequence s_a;
      @(posedge clk) $fell(a);
    endsequence
  
  	// When the above sequence is asserted, the assertion fails if 
  	// negedge 'a' is not found on every posedge clk
  	assert property(s_a);

      
	// Rest of the testbench stimulus
endmodule

  

See that a negative edge was detected and the assertion passed at 1500ns in the image shown below. This is because value of a is 1 at 130ns and 0 at 150ns upon which the assertion completes and is proven to be successful.

Time (ns) a Transition Result
10 0 FAIL
30 1 0->1 FAIL
50 1 FAIL
70 1 FAIL
90 1 FAIL
110 1 FAIL
130 1 FAIL
150 0 1->0 PASS
170 1 0->1 FAIL
190 1 FAIL
Simulation Log
Compiler version P-2019.06-1; Runtime version P-2019.06-1;  Jan 14 07:09 2020
[10] a=0
"testbench.sv", 12: tb.unnamed$$_0: started at 10ns failed at 10ns
	Offending '$fell(a)'
[30] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 30ns failed at 30ns
	Offending '$fell(a)'
[50] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 50ns failed at 50ns
	Offending '$fell(a)'
[70] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 70ns failed at 70ns
	Offending '$fell(a)'
[90] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 90ns failed at 90ns
	Offending '$fell(a)'
[110] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 110ns failed at 110ns
	Offending '$fell(a)'
[130] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 130ns failed at 130ns
	Offending '$fell(a)'
[150] a=0
[170] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 170ns failed at 170ns
	Offending '$fell(a)'
[190] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 190ns failed at 190ns
	Offending '$fell(a)'
$finish called from file "testbench.sv", line 27.
$finish at simulation time                  210
           V C S   S i m u l a t i o n   R e p o r t 
Time: 210 ns

$stable

  
  
module tb;
  	bit a;
  	bit clk;
  
	// This sequence states that 'a' should be stable on every clock
	// and should not have posedge/negedge at any posedge clk
  	sequence s_a;
      @(posedge clk) $stable(a);
    endsequence
  
  	// When the above sequence is asserted, the assertion fails if 
  	// 'a' toggles at any posedge clk
  	assert property(s_a);

      
	// Rest of the testbench stimulus
endmodule

  
Time (ns) a Transition Result
10 0 FAIL
30 1 0->1 FAIL
50 1 PASS
70 1 PASS
90 1 PASS
110 1 PASS
130 1 PASS
150 0 1->0 FAIL
170 1 0->1 FAIL
190 1 PASS
Simulation Log
Compiler version P-2019.06-1; Runtime version P-2019.06-1;  Jan 14 07:12 2020
[10] a=0
[30] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 30ns failed at 30ns
	Offending '$stable(a)'
[50] a=1
[70] a=1
[90] a=1
[110] a=1
[130] a=1
[150] a=0
"testbench.sv", 12: tb.unnamed$$_0: started at 150ns failed at 150ns
	Offending '$stable(a)'
[170] a=1
"testbench.sv", 12: tb.unnamed$$_0: started at 170ns failed at 170ns
	Offending '$stable(a)'
[190] a=1
$finish called from file "testbench.sv", line 27.
$finish at simulation time                  210
           V C S   S i m u l a t i o n   R e p o r t 
Time: 210 ns

The behavior of a system can be written as an assertion that should be true at all times. Hence assertions are used to validate the behavior of a system defined as properties, and can also be used in functional coverage.

What are properties of a design ?

If a property of the design that is being checked for by an assertion does not behave in the expected way, the assertion fails. For example, assume the design requests for grant and expects to receive an ack within the next four cycles. But if the design gets an ack on the fifth cycle, the property that an ack should be returned within 4 clocks is violated and the assertion fails.

If a property of the design that is being checked for by an assertion is forbidden from happening, the assertion fails. For example, assume a small processor decodes instructions read from memory, encounters an unknown instruction and results in a fatal error. If such a scenario is never expected from the design, the property of the design that only valid instructions can be read from memory is violated and the assertion fails.

As evident from the two examples above, properties of a given design is checked for by writing SystemVerilog assertions.

Why do we need assertions ?

An assertion is nothing but a more concise representation of a functional checker. The functionality represented by an assertion can also be written as a SystemVerilog task or checker that involves more line of code. Some disadvantages of doing so are listed below:

  • SystemVerilog is verbose and difficult to maintain and scale code with the number of properties
  • Being a procedural language, it is difficult to write checkers that involve many parallel events in the same period of time
  
  
// A property written in Verilog/SystemVerilog
always @ (posedge clk) begin
	if (!(a && b))
		$display ("Assertion failed");
end

  

SystemVerilog Assertions is a declarative language used to specify temporal conditions, and is very concise and easier to maintain.

  
  
// The property above written in SystemVerilog Assertions syntax
assert property(@(posedge clk) a && b);

  

Types of Assertion Statements

An assertion statement can be of the following types:

Type Description
assert To specify that the given property of the design is true in simulation
assume To specify that the given property is an assumption and used by formal tools to generate input stimulus
cover To evaluate the property for functional coverage
restrict To specify the property as a constraint on formal verification computations and is ignored by simulators

Building Blocks of Assertions

Sequence

A sequence of multiple logical events typically form the functionality of any design. These events may span across multiple clocks or exist for just a single clock cycle. To keep things simple, smaller events can be depicted using simple assertions which can then be used to build more complex behavior patterns.

  
  
// Sequence syntax
sequence <name_of_sequence>
  <test expression>
endsequence

// Assert the sequence
assert property (<name_of_sequence>);

  

Property

These events can be represented as a sequence and a number of sequences can be combined to create more complex sequences or properties.

It is necessary to include a clocking event inside a sequence or property in order to assert it.

  
  
// Property syntax
property <name_of_property>
  <test expression> or
  <sequence expressions>
endproperty

// Assert the property
assert property (<name_of_property>);

  

There are two kinds of assertions - Immediate and Concurrent.

Immediate Assertion

Immediate assertions are executed like a statement in a procedural block and follow simulation event semantics. These are used to verify an immediate property during simulation.

  
  
	always @ (<some_event>) begin
		...
		// This is an immediate assertion executed only
		// at this point in the execution flow
		$assert(!fifo_empty);      // Assert that fifo is not empty at this point
		...
	end

  

Click here to learn more on Immediate Assertions

Concurrent Assertions

Concurrent assertions are based on clock semantics and use sampled values of their expressions. Circuit behavior is described using SystemVerilog properties that gets evaluated everytime on the given clock and a failure in simulation indicates that the described functional behavior got violated.

  
  

// Define a property to specify that an ack should be 
// returned for every grant within 1:4 clocks
property p_ack;
	@(posedge clk) gnt ##[1:4] ack;
endproperty

assert property(p_ack);    // Assert the given property is true always

  

Click here to learn more on Concurrent Assertions

Steps to create assertions

Following are the steps to create assertions:

  • Step 1: Create boolean expressions
  • Step 2: Create sequence expressions
  • Step 3: Create property
  • Step 4: Assert property

Example

The first sequence s_ab validates that b is high the next clock when a is high, and the second sequence s_cd validates that d is high 2 clocks after c is found high. The property asserts that the second sequence is on the next cycle after the first sequence.

  
  
module tb;
  bit a, b, c, d;
  bit clk;
  
  always #10 clk = ~clk;
  
  initial begin
    for (int i = 0; i < 20; i++) begin
      {a, b, c, d} = $random;
      $display("%0t a=%0d b=%0d c=%0d d=%0d", $time, a, b, c, d);
      @(posedge clk);
    end
    #10 $finish;
  end
  
  sequence s_ab;
    a ##1 b;
  endsequence
  
  sequence s_cd;
    c ##2 d;
  endsequence
  
  property p_expr;
    @(posedge clk) s_ab ##1 s_cd;
  endproperty
  
  assert property (p_expr);
endmodule

  

Note that there are some violations for the property that is asserted using assert statement.

Simulation Log
Compiler version P-2019.06-1; Runtime version P-2019.06-1;  Jan  8 05:02 2020
Warning : License for product VCSRuntime_Net(725) will expire within 10 days, on: 17-jan-2020.

If you would like to temporarily disable this message, set 
 the VCS_LIC_EXPIRE_WARNING environment variable to the number of days
before expiration that you want this message to start (the minimum is 0).
0 a=0 b=1 c=0 d=0
10 a=0 b=0 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 10ns failed at 10ns
	Offending 'a'
30 a=1 b=0 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 30ns failed at 30ns
	Offending 'a'
50 a=0 b=0 c=1 d=1
70 a=1 b=1 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 70ns failed at 70ns
	Offending 'a'
"testbench.sv", 28: tb.unnamed$$_3: started at 50ns failed at 70ns
	Offending 'b'
90 a=1 b=1 c=0 d=1
110 a=0 b=1 c=0 d=1
130 a=0 b=0 c=1 d=0
"testbench.sv", 28: tb.unnamed$$_3: started at 130ns failed at 130ns
	Offending 'a'
"testbench.sv", 28: tb.unnamed$$_3: started at 90ns failed at 130ns
	Offending 'c'
150 a=0 b=0 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 150ns failed at 150ns
	Offending 'a'
170 a=1 b=1 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 170ns failed at 170ns
	Offending 'a'
190 a=0 b=1 c=1 d=0
210 a=1 b=1 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 210ns failed at 210ns
	Offending 'a'
230 a=1 b=1 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 190ns failed at 230ns
	Offending 'c'
250 a=1 b=1 c=0 d=0
270 a=1 b=0 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 230ns failed at 270ns
	Offending 'c'
290 a=0 b=1 c=1 d=0
"testbench.sv", 28: tb.unnamed$$_3: started at 270ns failed at 290ns
	Offending 'b'
"testbench.sv", 28: tb.unnamed$$_3: started at 250ns failed at 290ns
	Offending 'c'
310 a=0 b=1 c=0 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 310ns failed at 310ns
	Offending 'a'
330 a=1 b=0 c=1 d=0
"testbench.sv", 28: tb.unnamed$$_3: started at 330ns failed at 330ns
	Offending 'a'
"testbench.sv", 28: tb.unnamed$$_3: started at 290ns failed at 330ns
	Offending 'c'
350 a=0 b=1 c=0 d=1
370 a=0 b=1 c=1 d=1
"testbench.sv", 28: tb.unnamed$$_3: started at 370ns failed at 370ns
	Offending 'a'
"testbench.sv", 28: tb.unnamed$$_3: started at 390ns failed at 390ns
	Offending 'a'
$finish called from file "testbench.sv", line 13.
$finish at simulation time                  400
           V C S   S i m u l a t i o n   R e p o r t 

Here is an example of how a SystemVerilog testbench can be constructed to verify functionality of a simple adder. Remember that the goal here is to develop a modular and scalable testbench architecture with all the standard verification components in a testbench.

You can also write Verilog code for testing such simple circuits, but bigger and more complex designs typically require a scalable testbench architecture and this is an example of how to build a scalable testbench. Different designs require different driver, monitor and scoreboard implementation that depends on design specifics.

Design

  
  
// An adder is combinational logic and does not
// have a clock

module my_adder (adder_if _if);
  always_comb begin
    if (_if.rstn) begin
      _if.sum <= 0;
      _if.carry <= 0;
    end else begin
      {_if.carry, _if.sum} <= _if.a + _if.b;
    end
  end
endmodule

  

Transaction Object

  
  
// To verify that the adder adds, we also need to check that it 
// does not add when rstn is 0, and hence rstn should also be 
// randomized along with a and b.
class Packet;
  rand bit 		rstn;
  rand bit[7:0] a;
  rand bit[7:0] b;
  bit [7:0] 	sum;
  bit 			carry;
  
  // Print contents of the data packet
  function void print(string tag="");
    $display ("T=%0t %s a=0x%0h b=0x%0h sum=0x%0h carry=0x%0h", $time, tag, a, b, sum, carry);
  endfunction
  
  // This is a utility function to allow copying contents in 
  // one Packet variable to another.
  function void copy(Packet tmp);
    this.a = tmp.a;
    this.b = tmp.b;
    this.rstn = tmp.rstn;
    this.sum = tmp.sum;
    this.carry = tmp.carry;
  endfunction
endclass

  

Driver

  
  
class driver;
  virtual adder_if m_adder_vif;
  virtual clk_if  m_clk_vif;
  event drv_done;
  mailbox drv_mbx;
  
  task run();
    $display ("T=%0t [Driver] starting ...", $time);
    
    // Try to get a new transaction every time and then assign 
    // packet contents to the interface. But do this only if the 
    // design is ready to accept new transactions
    forever begin
      Packet item;
      
      $display ("T=%0t [Driver] waiting for item ...", $time);
      drv_mbx.get(item);
      @ (posedge m_clk_vif.tb_clk);
	  item.print("Driver");
      m_adder_vif.rstn <= item.rstn;
      m_adder_vif.a <= item.a;
      m_adder_vif.b <= item.b; ->drv_done;
    end   
  endtask
endclass

  

Monitor

  
  
// The monitor has a virtual interface handle with which it can monitor
// the events happening on the interface. It sees new transactions and then
// captures information into a packet and sends it to the scoreboard
// using another mailbox.
class monitor;
  virtual adder_if 	m_adder_vif;
  virtual clk_if 	m_clk_vif;
  
  mailbox scb_mbx; 		// Mailbox connected to scoreboard
  
  task run();
    $display ("T=%0t [Monitor] starting ...", $time);
    
    // Check forever at every clock edge to see if there is a 
    // valid transaction and if yes, capture info into a class
    // object and send it to the scoreboard when the transaction 
    // is over.
    forever begin
	  Packet m_pkt = new();
      @(posedge m_clk_vif.tb_clk);
      #1;
        m_pkt.a 	= m_adder_vif.a;
        m_pkt.b 	= m_adder_vif.b;
      	m_pkt.rstn 	= m_adder_vif.rstn;
        m_pkt.sum 	= m_adder_vif.sum;
        m_pkt.carry = m_adder_vif.carry;
        m_pkt.print("Monitor");
      scb_mbx.put(m_pkt);
    end
  endtask
endclass

  

Scoreboard

  
  
// The scoreboard is responsible to check data integrity. Since the design
// simple adds inputs to give sum and carry, scoreboard helps to check if the
// output has changed for given set of inputs based on expected logic
class scoreboard;
  mailbox scb_mbx;
  
  task run();
    forever begin
      Packet item, ref_item;
      scb_mbx.get(item);
      item.print("Scoreboard");
      
      // Copy contents from received packet into a new packet so
      // just to get a and b.
      ref_item = new();
      ref_item.copy(item);
      
      // Let us calculate the expected values in carry and sum
      if (ref_item.rstn) 
      	{ref_item.carry, ref_item.sum} = ref_item.a + ref_item.b;
      else
      {ref_item.carry, ref_item.sum} = 0;
      
      // Now, carry and sum outputs in the reference variable can be compared
      // with those in the received packet
      if (ref_item.carry != item.carry) begin
        $display("[%0t] Scoreboard Error! Carry mismatch ref_item=0x%0h item=0x%0h", $time, ref_item.carry, item.carry);
      end else begin
        $display("[%0t] Scoreboard Pass! Carry match ref_item=0x%0h item=0x%0h", $time, ref_item.carry, item.carry);
      end
      
      if (ref_item.sum != item.sum) begin
        $display("[%0t] Scoreboard Error! Sum mismatch ref_item=0x%0h item=0x%0h", $time, ref_item.sum, item.sum);
      end else begin
        $display("[%0t] Scoreboard Pass! Sum match ref_item=0x%0h item=0x%0h", $time, ref_item.sum, item.sum);
      end
    end
  endtask
endclass

  

Generator

  
  
// Sometimes we simply need to generate N random transactions to random
// locations so a generator would be useful to do just that. In this case
// loop determines how many transactions need to be sent
class generator;
  int 	loop = 10;
  event drv_done;
  mailbox drv_mbx;
  
  task run();
    for (int i = 0; i < loop; i++) begin
      Packet item = new;
      item.randomize();
      $display ("T=%0t [Generator] Loop:%0d/%0d create next item", $time, i+1, loop);
      drv_mbx.put(item);
      $display ("T=%0t [Generator] Wait for driver to be done", $time);
      @(drv_done);
    end
  endtask
endclass

  

Environment

  
  
// Lets say that the environment class was already there, and generator is 
// a new component that needs to be included in the ENV. 
class env;
  generator 		g0; 			// Generate transactions
  driver 			d0; 			// Driver to design
  monitor 			m0; 			// Monitor from design
  scoreboard 		s0; 			// Scoreboard connected to monitor
  mailbox 			scb_mbx; 		// Top level mailbox for SCB <-> MON 
  virtual adder_if 	m_adder_vif; 	// Virtual interface handle
  virtual clk_if 	m_clk_vif; 		// TB clk
  
  event drv_done;
  mailbox drv_mbx;
  
  function new();
    d0 = new;
    m0 = new;
    s0 = new;
    scb_mbx = new();
    g0 = new;
    drv_mbx = new;
  endfunction
  
  virtual task run();
    // Connect virtual interface handles
    d0.m_adder_vif = m_adder_vif;
    m0.m_adder_vif = m_adder_vif;
    d0.m_clk_vif = m_clk_vif;
    m0.m_clk_vif = m_clk_vif;
    
    // Connect mailboxes between each component
    d0.drv_mbx = drv_mbx;
    g0.drv_mbx = drv_mbx;
    
    m0.scb_mbx = scb_mbx;
    s0.scb_mbx = scb_mbx;
    
    // Connect event handles
    d0.drv_done = drv_done;
    g0.drv_done = drv_done;
    
    // Start all components - a fork join_any is used because 
    // the stimulus is generated by the generator and we want the
    // simulation to exit only when the generator has finished 
    // creating all transactions. Until then all other components
    // have to run in the background.
    fork
    	s0.run();
		d0.run();
    	m0.run();
      	g0.run();
    join_any
  endtask
endclass

  

Test

  
  
// The test can instantiate any environment. In this test, we are using
// an environment without the generator and hence the stimulus should be 
// written in the test. 
class test;
  env e0;
  mailbox drv_mbx;
  
  function new();
    drv_mbx = new();
    e0 = new();
  endfunction
  
  virtual task run();
    e0.d0.drv_mbx = drv_mbx;
    e0.run();
  endtask
endclass

  

Interface

  
  
// Adder interface contains all signals that the adder requires
// to operate
interface adder_if();
  logic 		rstn;
  logic [7:0] 	a;
  logic [7:0] 	b;
  logic [7:0] 	sum;
  logic 		carry;
endinterface

// Although an adder does not have a clock, let us create a mock clock 
// used in the testbench to synchronize when value is driven and when 
// value is sampled. Typically combinational logic is used between 
// sequential elements like FF in a real circuit. So, let us assume
// that inputs to the adder is provided at some posedge clock. But because
// the design does not have clock in its input, we will keep this clock
// in a separate interface that is available only to testbench components
interface clk_if();
  logic tb_clk;
  
  initial tb_clk <= 0;
  
  always #10 tb_clk = ~tb_clk;
endinterface

  

Testbench Top

  
  
module tb;
  bit tb_clk;
  
  clk_if 	m_clk_if 	();
  adder_if 	m_adder_if	();
  my_adder 	u0 			(m_adder_if);
  
  initial begin
    test t0;

    t0 = new;
    t0.e0.m_adder_vif = m_adder_if;
    t0.e0.m_clk_vif = m_clk_if;
    t0.run();
    
    // Once the main stimulus is over, wait for some time
    // until all transactions are finished and then end 
    // simulation. Note that $finish is required because
    // there are components that are running forever in 
    // the background like clk, monitor, driver, etc
    #50 $finish;
  end
endmodule

  
Simulation Log
ncsim> run
T=0 [Driver] starting ...
T=0 [Driver] waiting for item ...
T=0 [Monitor] starting ...
T=0 [Generator] Loop:1/5 create next item
T=0 [Generator] Wait for driver to be done
T=10 Driver a=0x16 b=0x11 sum=0x0 carry=0x0
T=10 [Driver] waiting for item ...
T=10 [Generator] Loop:2/5 create next item
T=10 [Generator] Wait for driver to be done
T=11 Monitor a=0x16 b=0x11 sum=0x0 carry=0x0
T=11 Scoreboard a=0x16 b=0x11 sum=0x0 carry=0x0
[11] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[11] Scoreboard Pass! Sum match ref_item=0x0 item=0x0
T=30 Driver a=0xde b=0x6 sum=0x0 carry=0x0
T=30 [Driver] waiting for item ...
T=30 [Generator] Loop:3/5 create next item
T=30 [Generator] Wait for driver to be done
T=31 Monitor a=0xde b=0x6 sum=0x0 carry=0x0
T=31 Scoreboard a=0xde b=0x6 sum=0x0 carry=0x0
[31] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[31] Scoreboard Pass! Sum match ref_item=0x0 item=0x0
T=50 Driver a=0xb1 b=0xbd sum=0x0 carry=0x0
T=50 [Driver] waiting for item ...
T=50 [Generator] Loop:4/5 create next item
T=50 [Generator] Wait for driver to be done
T=51 Monitor a=0xb1 b=0xbd sum=0x0 carry=0x0
T=51 Scoreboard a=0xb1 b=0xbd sum=0x0 carry=0x0
[51] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[51] Scoreboard Pass! Sum match ref_item=0x0 item=0x0
T=70 Driver a=0x63 b=0xfb sum=0x0 carry=0x0
T=70 [Driver] waiting for item ...
T=70 [Generator] Loop:5/5 create next item
T=70 [Generator] Wait for driver to be done
T=71 Monitor a=0x63 b=0xfb sum=0x5e carry=0x1
T=71 Scoreboard a=0x63 b=0xfb sum=0x5e carry=0x1
[71] Scoreboard Pass! Carry match ref_item=0x1 item=0x1
[71] Scoreboard Pass! Sum match ref_item=0x5e item=0x5e
T=90 Driver a=0x71 b=0xbc sum=0x0 carry=0x0
T=90 [Driver] waiting for item ...
T=91 Monitor a=0x71 b=0xbc sum=0x0 carry=0x0
T=91 Scoreboard a=0x71 b=0xbc sum=0x0 carry=0x0
[91] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[91] Scoreboard Pass! Sum match ref_item=0x0 item=0x0
T=111 Monitor a=0x71 b=0xbc sum=0x0 carry=0x0
T=111 Scoreboard a=0x71 b=0xbc sum=0x0 carry=0x0
[111] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[111] Scoreboard Pass! Sum match ref_item=0x0 item=0x0
T=131 Monitor a=0x71 b=0xbc sum=0x0 carry=0x0
T=131 Scoreboard a=0x71 b=0xbc sum=0x0 carry=0x0
[131] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[131] Scoreboard Pass! Sum match ref_item=0x0 item=0x0
Simulation complete via $finish(1) at time 140 NS + 0
./testbench.sv:265     #50 $finish;

Buggy Design

Although the previous simulation showed everything as pass, how do we know if there is a bug in the checker ? Let us introduce a bug in the design to see if the checker fails.

  
  
module my_adder (adder_if _if);
  always_comb begin
  	// Let sum and carry be reset when rstn is 1 instead of 0
  	// A simple but yet possible design bug
    if (_if.rstn) begin
      _if.sum <= 0;
      _if.carry <= 0;
    end else begin
      {_if.carry, _if.sum} <= _if.a + _if.b;
    end
  end
endmodule

  

See that the checker now reports an error which proves that the checker is implemented correctly.

Testbench example with design bug Simulation Log
ncsim> run
T=0 [Driver] starting ...
T=0 [Driver] waiting for item ...
T=0 [Monitor] starting ...
T=0 [Generator] Loop:1/5 create next item
T=0 [Generator] Wait for driver to be done
T=10 Driver a=0x16 b=0x11 sum=0x0 carry=0x0
T=10 [Driver] waiting for item ...
T=10 [Generator] Loop:2/5 create next item
T=10 [Generator] Wait for driver to be done
T=11 Monitor a=0x16 b=0x11 sum=0x27 carry=0x0
T=11 Scoreboard a=0x16 b=0x11 sum=0x27 carry=0x0
[11] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[11] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x27
T=30 Driver a=0xde b=0x6 sum=0x0 carry=0x0
T=30 [Driver] waiting for item ...
T=30 [Generator] Loop:3/5 create next item
T=30 [Generator] Wait for driver to be done
T=31 Monitor a=0xde b=0x6 sum=0xe4 carry=0x0
T=31 Scoreboard a=0xde b=0x6 sum=0xe4 carry=0x0
[31] Scoreboard Pass! Carry match ref_item=0x0 item=0x0
[31] Scoreboard Error! Sum mismatch ref_item=0x0 item=0xe4
T=50 Driver a=0xb1 b=0xbd sum=0x0 carry=0x0
T=50 [Driver] waiting for item ...
T=50 [Generator] Loop:4/5 create next item
T=50 [Generator] Wait for driver to be done
T=51 Monitor a=0xb1 b=0xbd sum=0x6e carry=0x1
T=51 Scoreboard a=0xb1 b=0xbd sum=0x6e carry=0x1
[51] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1
[51] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x6e
T=70 Driver a=0x63 b=0xfb sum=0x0 carry=0x0
T=70 [Driver] waiting for item ...
T=70 [Generator] Loop:5/5 create next item
T=70 [Generator] Wait for driver to be done
T=71 Monitor a=0x63 b=0xfb sum=0x0 carry=0x0
T=71 Scoreboard a=0x63 b=0xfb sum=0x0 carry=0x0
[71] Scoreboard Error! Carry mismatch ref_item=0x1 item=0x0
[71] Scoreboard Error! Sum mismatch ref_item=0x5e item=0x0
T=90 Driver a=0x71 b=0xbc sum=0x0 carry=0x0
T=90 [Driver] waiting for item ...
T=91 Monitor a=0x71 b=0xbc sum=0x2d carry=0x1
T=91 Scoreboard a=0x71 b=0xbc sum=0x2d carry=0x1
[91] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1
[91] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x2d
T=111 Monitor a=0x71 b=0xbc sum=0x2d carry=0x1
T=111 Scoreboard a=0x71 b=0xbc sum=0x2d carry=0x1
[111] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1
[111] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x2d
T=131 Monitor a=0x71 b=0xbc sum=0x2d carry=0x1
T=131 Scoreboard a=0x71 b=0xbc sum=0x2d carry=0x1
[131] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1
[131] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x2d
Simulation complete via $finish(1) at time 140 NS + 0
./testbench.sv:265     #50 $finish;

This is another example of a SystemVerilog testbench using OOP concepts like inheritance, polymorphism to build a functional testbench for a simple design.

Design

  
  
module switch 
  # (parameter ADDR_WIDTH = 8,
     parameter DATA_WIDTH = 16,
     parameter ADDR_DIV = 8'h3F
    )
  
  ( input clk,
   	input rstn,
    input vld,
    
    input [ADDR_WIDTH-1:0] 	addr,
   input [DATA_WIDTH-1:0] 	data,
   
   output reg [ADDR_WIDTH-1:0] 	addr_a,
   output reg [DATA_WIDTH-1:0] 	data_a,

   output reg [ADDR_WIDTH-1:0] 	addr_b,
   output reg [DATA_WIDTH-1:0] 	data_b
  );
  
  always @ (posedge clk) begin
    if (!rstn) begin
      	addr_a <= 0;
    	data_a <= 0;
    	addr_b <= 0;
    	data_b <= 0; end else begin if (vld) begin if (addr >= 0 & addr <= ADDR_DIV) begin
        	addr_a <= addr;
      		data_a <= data;
          	addr_b <= 0;
          	data_b <= 0;
      	end else begin
          	addr_a <= 0;
          	data_a <= 0;
        	addr_b <= addr;
        	data_b <= data;
      	end
      end
    end
  end
endmodule