{index}

Peripheral devices are usually mapped to fall into a specific region of the memory map and each peripheral is allotted an address space that ranges from a start address to an end address. Consider the memory map shown for a processor on the left where peripherals have an address range from 0xE7B0_0000 to 0xE7BF_0000. This means that the processor has to send out bus transactions with an address that falls within this range to access the peripherals.

Design

  
  
module modN_ctr 
  # (parameter N = 10,
     parameter WIDTH = 4)
  
  ( input   clk,
    input   rstn,
   	output  reg[WIDTH-1:0] out);
 
  always @ (posedge clk) begin
    if (!rstn) begin
      out <= 0;
    end else begin
      if (out == N-1) 
        out <= 0;
      else
        out <= out + 1;
    end
  end
endmodule

  

Testbench

  
  
module tb;
  parameter N = 10;
  parameter WIDTH = 4;
  
  reg clk;
  reg rstn;
  wire [WIDTH-1:0] out;
  
  modN_ctr u0  ( 	.clk(clk),
                	.rstn(rstn),
                	.out(out));

  always #10 clk = ~clk;
  
  initial begin
    {clk, rstn} <= 0;
    
    $monitor ("T=%0t rstn=%0b out=0x%0h", $time, rstn, out);
    repeat(2) @ (posedge clk);
    rstn <= 1;
    
    repeat(20) @ (posedge clk);
    $finish;  
  end
endmodule

  
Simulation Log

ncsim> run
T=0 rstn=0 out=0xx
T=10 rstn=0 out=0x0
T=30 rstn=1 out=0x0
T=50 rstn=1 out=0x1
T=70 rstn=1 out=0x2
T=90 rstn=1 out=0x3
T=110 rstn=1 out=0x4
T=130 rstn=1 out=0x5
T=150 rstn=1 out=0x6
T=170 rstn=1 out=0x7
T=190 rstn=1 out=0x8
T=210 rstn=1 out=0x9
T=230 rstn=1 out=0xa
T=250 rstn=1 out=0x0
T=270 rstn=1 out=0x1
T=290 rstn=1 out=0x2
T=310 rstn=1 out=0x3
T=330 rstn=1 out=0x4
T=350 rstn=1 out=0x5
T=370 rstn=1 out=0x6
T=390 rstn=1 out=0x7
T=410 rstn=1 out=0x8
Simulation complete via $finish(1) at time 430 NS + 0

SystemVerilog offers much flexibility in building complicated data structures through the different types of arrays.

Static Arrays

A static array is one whose size is known before compilation time. In the example shown below, a static array of 8-bit wide is declared, assigned some value and iterated over to print its value.

  
  
module tb;
	bit [7:0] 	m_data; 	// A vector or 1D packed array
	
	initial begin
		// 1. Assign a value to the vector
		m_data = 8'hA2; 
		
		// 2. Iterate through each bit of the vector and print value
		for (int i = 0; i < $size(m_data); i++) begin
			$display ("m_data[%0d] = %b", i, m_data[i]);
		end
	end
endmodule

  

Hardware behavior is made more configurable through control registers, and the verification of these registers has become one of the primary items in the to-do list of any design. It is quite pointless to test any other feature of a design if we cannot access/modify its register bits to change any of its functionality. Registers are typically accessed using low-bandwidth bus protocols like AMBA Advanced Peripehral Bus, IBM On-Chip Peripheral Bus or something similar developed in-house by a semiconductor firm. It's because of these registers that software can exercise greater control on the overall behavior of the chip, and most design specifications have a number of pages detailing the functionalities of each bit of every register.

We need to have an environment known as a testbench to run any kind of simulation on the design.

Click here to refresh basic concepts of a simulation

What is the purpose of a testbench ?

A testbench allows us to verify the functionality of a design through simulations. It is a container where the design is placed and driven with different input stimulus.

  1. Generate different types of input stimulus
  2. Drive the design inputs with the generated stimulus
  3. Allow the design to process input and provide an output
  4. Check the output with expected behavior to find functional defects
  5. If a functional bug is found, then change the design to fix the bug
  6. Perform the above steps until there are no more functional defects

Components of a testbench

The example shown in Introduction is not modular, scalable, flexible or even re-usable because of the way DUT is connected, and how signals are driven. Let's take a look at a simple testbench and try to understand about the various components that facilitate data transfer from and to the DUT.

Component Description
Generator Generates different input stimulus to be driven to DUT
Interface Contains design signals that can be driven or monitored
Driver Drives the generated stimulus to the design
Monitor Monitor the design input-output ports to capture design activity
Scoreboard Checks output from the design with expected behavior
Environment Contains all the verification components mentioned above
Test Contains the environment that can be tweaked with different configuration settings
simple-testbench

What is DUT ?

DUT stands for Design Under Test and is the hardware design written in Verilog or VHDL. DUT is a term typically used in post validation of the silicon once the chip is fabricated. In pre validation, it is also called as Design Under Verification, DUV in short.

  
  
// All verification components are placed in this top testbench module
module tb_top;

	// Declare variables that need to be connected to the design instance
	// These variables are assigned some values that in turn gets transferred to
	// the design as inputs because they are connected with the ports in the design
	reg clk;        
	wire en; 		
	wire wr;
	wire data;

	// Instantiate the design module and connect the variables declared above
	// with the ports in the design
	design myDsn ( .clk (clk),
	               .en  (en),
	               .wr  (wr),
	               . ...
	               .rdata);
	
	// Develop rest of the testbench and write stimulus that can be driven to the design
endmodule

  

Click here for a complete SystemVerilog testbench example !