In the previous few articles, we have seen what a register model is and how it can be used to access registers in a given design. Let us see a complete example of how such a model can be written for a given design, how it can be integrated into the environment and how it can be used to write and read into design fields.

Click here to refresh the concept on register models !

Design

The following design has the following registers and fields that are accessible through an APB interface. The design essentially represents a traffic light controller which can be configured by writing into certain control registers.

The ctl register contains fields to start the module, and configure it to be in the blink yellow or blink red mode. The state register is read-only and returns current state of the design - yellow, red or green. The two timer registers stores the time between transition from each state. The profile bit allows the user to choose between the two programmed timer values. This can be useful for peak and off-peak times.

register model example design

This is not a complete design since our purpose is simply to show how registers in this design can be read/written using a UVM register model. All the signals listed as the module ports belong to APB specification.

  
  
module traffic (  input          pclk,
                  input          presetn,
                  input [31:0]   paddr,
                  input [31:0]   pwdata,
                  input          psel,
                  input          pwrite,
                  input          penable,

                  // Outputs
                  output [31:0]  prdata);

   reg [3:0]      ctl_reg;    // profile, blink_red, blink_yellow, mod_en RW
   reg [1:0]      stat_reg;   // state[1:0] 
   reg [31:0]     timer_0;    // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
   reg [31:0]     timer_1;    // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW

   reg [31:0]     data_in;
   reg [31:0]     rdata_tmp;

   // Set all registers to default values
   always @ (posedge pclk) begin
      if (!presetn) begin
         data_in <= 0;
         ctl_reg  <= 0; 
         stat_reg <= 0; 
         timer_0  <= 32'hcafe_1234; 
         timer_1  <= 32'hface_5678;
      end
   end

   // Capture write data
   always @ (posedge pclk) begin
      if (presetn & psel & penable) 
         if (pwrite) 
            case (paddr)
               'h0   : ctl_reg <= pwdata;
               'h4   : timer_0 <= pwdata;
               'h8   : timer_1 <= pwdata;
               'hc   : stat_reg <= pwdata;
            endcase
   end

   // Provide read data
   always @ (penable) begin
      if (psel & !pwrite) 
         case (paddr)
            'h0 : rdata_tmp <= ctl_reg;
            'h4 : rdata_tmp <= timer_0;
            'h8 : rdata_tmp <= timer_1;
            'hc : rdata_tmp <= stat_reg;
         endcase
   end

   assign prdata = (psel & penable & !pwrite) ? rdata_tmp : 'hz;

endmodule

  

Interface

Let us declare an interface with signals in the APB protocol and this interface can be passed to the testbench as a virtual interface for the driver to drive some values to the design. To keep things simple, let us not declare clocking blocks and modports, although they are recommended in a real project.

  
  
interface bus_if (input pclk);
   logic [31:0]   paddr;
   logic [31:0]   pwdata;
   logic [31:0]   prdata;
   logic          pwrite;
   logic          psel;
   logic          penable;
   logic          presetn;
endinterface

  

SystemVerilog also has many other 2-state data types in addition to all the data types supported by Verilog. Most commonly used data types in modern testbenches are bit, int, logic and byte.

Integer

Integers are numbers without a fractional part or in other words, they are whole numbers. SystemVerilog has three new signed data types to hold integer values each with a different size. The smallest is shortint which can range from -32768 to 32767, and the largest is longint. The sign can be explicitly defined using the keywords signed and unsigned. Also they can be converted into one another by casting.

  
  
	// ubyte is converted to signed type and assigned to si
	si = signed' (ubyte);	

  

Signed

By default, integer variables are signed in nature and hence can hold both positive and negative values.

  
  
module tb;
  // By default int data types are signed which means
  // that MSB is the sign bit and the integer variables can
  // also store negative numbers
  shortint 	var_a;      
  int 		var_b;
  longint 	var_c;
  
  initial begin
    // Print initial values of the integer variables
    $display ("Sizes var_a=%0d var_b=%0d var_c=%0d", $bits(var_a), $bits(var_b), $bits(var_c));
    
    // Assign the maximum value for each of the variables
    // MSB of each variable represents the sign bit and is set to 0
    // Rest of the bit positions are filled with 1 and hence you
    // get the maximum value that these variables can hold
    #1 var_a = 'h7FFF;                  
       var_b = 'h7FFF_FFFF;           	
       var_c = 'h7FFF_FFFF_FFFF_FFFF;   
    
    // When added a 1, the sign changes to negative because this is a signed variable
    #1 var_a += 1;   // Value becomes 'h8000 => which is a rollover from + sign to - sign
       var_b += 1;   // Value becomes 'h8000_0000 => which is a rollover from + sign to - sign
       var_c += 1;
  end
  
  // Start a monitor to print out values of each variables as they change
  initial
    $monitor ("var_a=%0d var_b=%0d var_c=%0d", var_a, var_b, var_c);
endmodule

  

The $bits system task returns the number of bits in a variable. Note that var_a, var_b and var_c roll over to the negative side.

Simulation Log
Sizes var_a=16 var_b=32 var_c=64
var_a=0 var_b=0 var_c=0
var_a=32767 var_b=2147483647 var_c=9223372036854775807
var_a=-32768 var_b=-2147483648 var_c=-9223372036854775808

SystemVerilog is an extension to Verilog and is also used as an HDL. Verilog has reg and wire data-types to describe hardware behavior. Since verification of hardware can become more complex and demanding, datatypes in Verilog are not sufficient to develop efficient testbenches and testcases. Hence SystemVerilog has extended Verilog by adding more C like data-types for better encapsulation and compactness.

Click here to refresh Verilog Data Types

The image shown below is a comprehensive list of the major basic data types available in SystemVerilog.

SystemVerilog data types

There are many built-in methods in SystemVerilog to help in array searching and ordering.

Array manipulation methods simply iterate through the array elements and each element is used to evaluate the expression specified by the with clause. The iterator argument specifies a local variable that can be used within the with expression to refer to the current element in the iteration. If an argument is not provided, item is the name used by default.

Specifying an iterator argument without the with clause is illegal.

Array Locator Methods

The with clause and expresison is mandatory for some of these methods and for some others its optional.

Mandatory 'with' clause

These methods are used to filter out certain elements from an existing array based on a given expression. All such elements that satisfy the given expression is put into an array and returned. Hence the with clause is mandatory for the following methods.

Method name Description
find() Returns all elements satisfying the given expression
find_index() Returns the indices of all elements satisfying the given expression
find_first() Returns the first element satisfying the given expression
find_first_index() Returns the index of the first element satisfying the given expression
find_last() Returns the last element satisfying the given expression
find_last_index() Returns the index of the last element satisfying the given expression
Example
  
  
module tb;
  int array[9] = '{4, 7, 2, 5, 7, 1, 6, 3, 1};
  int res[$];
  
  initial begin
    res = array.find(x) with (x > 3);
    $display ("find(x)         : %p", res);
    
    res = array.find_index with (item == 4);
    $display ("find_index      : res[%0d] = 4", res[0]);
    
    res = array.find_first with (item < 5 & item >= 3);
    $display ("find_first      : %p", res);
    
    res = array.find_first_index(x) with (x > 5);
    $display ("find_first_index: %p", res);
    
    res = array.find_last with (item <= 7 & item > 3);
    $display ("find_last       : %p", res);
    
    res = array.find_last_index(x) with (x < 3);
    $display ("find_last_index : %p", res);
  end
endmodule

  
Simulation Log
ncsim> run
find(x)         : '{4, 7, 5, 7, 6}
find_index      : res[0] = 4
find_first      : '{4}
find_first_index: '{1}
find_last       : '{6}
find_last_index : '{8}
ncsim: *W,RNQUIE: Simulation is complete.

Optional 'with' clause

Methods Description
min() Returns the element with minimum value or whose expression evaluates to a minimum
max() Returns the element with maximum value or whose expression evaluates to a maximum
unique() Returns all elements with unique values or whose expression evaluates to a unique value
unique_index() Returns the indices of all elements with unique values or whose expression evaluates to a unique value
Example
  
  
module tb;
  int array[9] = '{4, 7, 2, 5, 7, 1, 6, 3, 1};
  int res[$];
  
  initial begin   
    res = array.min();
    $display ("min          : %p", res);
    
    res = array.max();
    $display ("max          : %p", res);
    
    res = array.unique();
    $display ("unique       : %p", res);
    
    res = array.unique(x) with (x < 3);
    $display ("unique       : %p", res);
    
    res = array.unique_index;
    $display ("unique_index : %p", res);
  end
endmodule

  
Simulation Log
ncsim> run
min          : '{1}
max          : '{7}
unique       : '{4, 7, 2, 5, 1, 6, 3}
unique       : '{4, 2}
unique_index : '{0, 1, 2, 3, 5, 6, 7}
ncsim: *W,RNQUIE: Simulation is complete.

Array Ordering Methods

These methods operate and alter the array directly.

Method Description
reverse() Reverses the order of elements in the array
sort() Sorts the array in ascending order, optionally using with clause
rsort() Sorts the array in descending order, optionally using with clause
shuffle() Randomizes the order of the elements in the array. with clause is not allowed here.

Example

  
  
module tb;
  int array[9] = '{4, 7, 2, 5, 7, 1, 6, 3, 1};
  
  initial begin   
    array.reverse();
    $display ("reverse  : %p", array);
    
    array.sort();
    $display ("sort     : %p", array);
    
    array.rsort();
    $display ("rsort    : %p", array);
    
    for (int i = 0; i < 5; i++) begin
    	array.shuffle();
      $display ("shuffle Iter:%0d  = %p", i, array);
    end
  end
endmodule

  
Simulation Log
ncsim> run
reverse  : '{1, 3, 6, 1, 7, 5, 2, 7, 4}
sort     : '{1, 1, 2, 3, 4, 5, 6, 7, 7}
rsort    : '{7, 7, 6, 5, 4, 3, 2, 1, 1}
shuffle Iter:0  = '{6, 7, 1, 7, 3, 2, 1, 4, 5}
shuffle Iter:1  = '{5, 1, 3, 4, 2, 7, 1, 7, 6}
shuffle Iter:2  = '{3, 1, 7, 4, 6, 7, 1, 2, 5}
shuffle Iter:3  = '{6, 4, 7, 3, 1, 7, 5, 2, 1}
shuffle Iter:4  = '{3, 6, 2, 5, 4, 7, 7, 1, 1}
ncsim: *W,RNQUIE: Simulation is complete.

Using array ordering on classes

  
  
class Register;
  string name;
  rand bit [3:0] rank;
  rand bit [3:0] pages;
  
  function new (string name);
    this.name = name;
  endfunction
  
  function void print();
    $display("name=%s rank=%0d pages=%0d", name, rank, pages);
  endfunction
  
endclass

module tb;
  Register rt[4];
  string name_arr[4] = '{"alexa", "siri", "google home", "cortana"};
  
  initial begin
    $display ("
-------- Initial Values --------");
    foreach (rt[i]) begin
      rt[i] = new (name_arr[i]);
      rt[i].randomize();
      rt[i].print();
    end
    
    $display ("
--------- Sort by name ------------");
    
    rt.sort(x) with (x.name);
    foreach (rt[i]) rt[i].print();
    
    $display ("
--------- Sort by rank, pages -----------");
    
    rt.sort(x) with ( {x.rank, x.pages});
    foreach (rt[i]) rt[i].print();
  end
endmodule

  
Simulation Log
ncsim> run

-------- Initial Values --------
name=alexa rank=12 pages=13
name=siri rank=6 pages=12
name=google home rank=12 pages=13
name=cortana rank=7 pages=11

--------- Sort by name ------------
name=alexa rank=12 pages=13
name=cortana rank=7 pages=11
name=google home rank=12 pages=13
name=siri rank=6 pages=12

--------- Sort by rank, pages -----------
name=siri rank=6 pages=12
name=cortana rank=7 pages=11
name=alexa rank=12 pages=13
name=google home rank=12 pages=13
ncsim: *W,RNQUIE: Simulation is complete.

Array Reduction Methods

Method Description
sum() Returns the sum of all array elements
product() Returns the product of all array elements
and() Returns the bitwise AND (&) of all array elements
or() Returns the bitwise OR (|) of all array elements
xor() Returns the bitwise XOR (^) of all array elements
  
  
module tb;
  int array[4] = '{1, 2, 3, 4};
  int res[$];
  
  initial begin
    $display ("sum     = %0d", array.sum());    
    $display ("product = %0d", array.product());    
    $display ("and     = 0x%0h", array.and());
    $display ("or      = 0x%0h", array.or());    
    $display ("xor     = 0x%0h", array.xor());   
  end
endmodule

  
Simulation Log
ncsim> run
sum     = 10
product = 24
and     = 0x0
or      = 0x7
xor     = 0x4
ncsim: *W,RNQUIE: Simulation is complete.

Hardware Description Languages (HDL) like Verilog and VHDL are used to describe hardware behavior so that it can be converted to digital blocks made up of combinational gates and sequential elements. In order to verify that the hardware description in HDL is correct, there is a need for a language with more features in OOP that will support complicated testing procedures and is often called a Hardware Verification Language.

SystemVerilog is an extension of Verilog with many such verification features that allow engineers to verify the design using complex testbench structures and random stimuli in simulation.

Why is Verilog not preferred ?

Back in the 1990's, Verilog was the primary language to verify functionality of designs that were small, not very complex and had less features. As design complexity increases, so does the requirement of better tools to design and verify it. SystemVerilog is far superior to Verilog because of its ability to perform constrained random stimuli, use OOP features in testbench construction, functional coverage, assertions among many others.

What is verification ?

Verification is the process of ensuring that a given hardware design works as expected. Chip design is a very extensive and time consuming process and costs millions to fabricate. Functional defects in the design if caught at an earlier stage in the design process will help save costs. If a bug is found later on in the design flow, then all of the design steps have to be repeated again which will use up more resources, money and time. If the entire design flow has to be repeated, then its called a respin of the chip.

What about Vera, e, and other similar HVL ?

They have been in use for some time. SystemVerilog can be considered an extension of Verilog (the most popular HDL), and it makes sense to verify a Verilog design in SystemVerilog. Also SystemVerilog supports OOP which makes verification of designs at a higher level of abstraction possible.