image/svg+xml
  • Contents
      • Back
      • Digital Basics
      • Verilog
      • Verification
      • SystemVerilog
      • UVM
Most Popular
Verification
  Testbench Evolution
  Constraint Random Verification
  Verification Techniques
  Verification Plan
  Code Coverage

Verilog
  Data Types
  Basic Constructs
  Behavioral Modeling
  Gate Modeling
  Simulation Basics
  Design Examples

SystemVerilog
  Data Types
  Class
  Interface
  Constraints and more!
  Testbench Examples

UVM
  Sequences
  Testbench Components
  TLM Tutorial
  Register Model Tutorial
  Testbench Examples

Digital Fundamentals
  Binary Arithmetic
  Boolean Logic
  Karnaugh Maps
  Combinational Logic
  Sequential Logic




UVM utility & field macros

  1. Utility Macros
    1. Object Utility
    2. Component Utility
    3. Macro Expansion: Behind the Scenes
    4. Creation of class object
  2. Field Macros
    1. Usage Example

UVM uses the concept of a factory where all objects are registered with it so that it can return an object of the requested type when required. The utility macros help to register each object with the factory.

UVM also introduces a bunch of automation mechanisms for implementing print , copy , and compare objects and are defined using the field macros.

Utility Macros

The utils macro is used primarily to register an object or component with the factory and is required for it to function correctly. It is required to be used inside every user-defined class that is derived from uvm_object which includes all types of sequence items and components.

Object Utility

All classes derived directly from uvm_object or uvm_transaction require them to be registered using `uvm_object_utils macro. Note that it is mandatory for the new function to be explicitly defined for every class, and takes the name of the class instance as an argument.


class ABC extends uvm_object;

	// Register this user defined class with the factory
	`uvm_object_utils(ABC)
	
	function new(string name = "ABC");
		super.new(name);
	endfunction
endclass

Component Utility

All classes derived directly or indirectly from uvm_component require them to be registered with the factory using `uvm_component_utils macro. Note that it is mandatory for the new function to be explicitly defined for every class derived directly or indirectly from uvm_component and takes the name of the class instance and a handle to the parent class where this object is instantiated.


class DEF extends uvm_component;
	
	// Class derived from uvm_component, register with factory
	`uvm_component_utils(DEF)
	
	function new(string name = "DEF", uvm_component parent=null);
		super.new(name, parent);
	endfunction
endclass

Macro Expansion: Behind the Scenes

`uvm_object_utils eventually gets expanded into its *_begin and *_end form with nothing between the begin and end. *_begin implements other macros required for the correct functionality of UVM factory.


// Empty uvm_object_utils macro
`define uvm_object_utils(T) 
  `uvm_object_utils_begin(T) 
  `uvm_object_utils_end

`define uvm_object_utils_begin(T) 
   `m_uvm_object_registry_internal(T,T)     // Sub-macro #1
   `m_uvm_object_create_func(T)  			 // Sub-macro #2
   `m_uvm_get_type_name_func(T)  			 // Sub-macro #3
   `uvm_field_utils_begin(T)				 // Sub-macro #4
   
// uvm_object_utils_end simply terminates a function started 
// somewhere in the middle
`define uvm_object_utils_end 
     end 
   endfunction 

It can be seen from the code shown below that each macro expanded by *_begin implements some functions required for factory operation and performs the following steps.

  • Implements get_type static method which basically returns a factory proxy object for the requested type.
  • Implements create, which instantiates an object of the specified type by calling its constructor with no arguments.
  • Implements get_type_name method which returns the type as a string
  • Registers the type with UVM factory


// Sub-macro #1. Implement the functions "get_type()" and "get_object_type()"
`define m_uvm_object_registry_internal(T,S) 
   typedef uvm_object_registry#(T,`"S`") type_id; 
   static function type_id get_type(); 
     return type_id::get(); 
   endfunction 
   virtual function uvm_object_wrapper get_object_type(); 
     return type_id::get(); 
   endfunction
   
// Sub-macro #2. Implement the function "create()"
`define m_uvm_object_create_func(T) 
   function uvm_object create (string name=""); 
     T tmp; 
`ifdef UVM_OBJECT_DO_NOT_NEED_CONSTRUCTOR 
     tmp = new(); 
     if (name!="") 
       tmp.set_name(name); 
`else 
     if (name=="") tmp = new(); 
     else tmp = new(name); 
`endif 
     return tmp; 
   endfunction

// Sub-macro #3. Implement the function "get_type_name()"
`define m_uvm_get_type_name_func(T) 
   const static string type_name = `"T`"; 
   virtual function string get_type_name (); 
     return type_name; 
   endfunction 

// Sub-macro #4. Implement field automation macros
`define uvm_field_utils_begin(T) 
   function void __m_uvm_field_automation (uvm_object tmp_data__, 
                                     int what__, 
                                     string str__); 
...

In a similar way, `uvm_component_utils_* also has equivalent macros and function calls. Note that the create function for components take two arguments, name and parent, unlike that of an object.

Paratemerized classes should use `uvm_object_param_utils_* since they do not have to give a type during factory registration.

Creation of class object

It is recommended that all class objects are created by calling the type_id::create() method which is already defined using the macro `m_uvm_object_create_func. This makes any child class object to be created and returned using factory mechanism and promotes testbench flexibility and reusability.


class ABC extends uvm_object;
	`uvm_object_utils(ABC)
	
	function new(string name = "ABC");
		super.new(name);
	endfunction
endclass

class base_test extends uvm_test;
	`uvm_component_utils(base_test)
	
	function new(string name = "base_test", uvm_component parent=null);
		super.new(name, parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
	
		// An object of class "ABC" is instantiated in UVM by calling
		// its "create()" function which has been defined using a macro
		// as shown above
		ABC abc = ABC::type_id::create("abc_inst");
	endfunction
endclass

Field Macros

`uvm_field_* macros that were used between *_begin and *_end utility macros are basically called field macros since they operate on class properties and provide automatic implementations of core methods like copy, compare and print. This helps the user save some time from implementing custom do_copy, do_compare and do_print functions for each and every class. However, since these macros are expanded into general code, it may impact simulation performance and are generally not recommended.

Usage Example


class ABC extends uvm_object;
	rand bit [15:0] 	m_addr;
	rand bit [15:0] 	m_data;
	
	`uvm_object_utils_begin(ABC)
		`uvm_field_int(m_addr, UVM_DEFAULT)
		`uvm_field_int(m_data, UVM_DEFAULT)
	`uvm_object_utils_end
	
	function new(string name = "ABC");
		super.new(name);
	endfunction
endclass

The `uvm_field_* corresponding to the data type of each variable should be used. For example, variables of type int, bit, byte should use `uvm_field_int, while variables of type string should use `uvm_field_string and so on. The macro accepts atleast two arguments: ARG and FLAG.

ArgumentDescription
ARGName of the variable, whose type should be appropriate for the macro that is used
FLAGWhen set to something other than UVM_DEFAULT or UVM_ALL_ON, it specifies which data method implementations will not be included. For example, if FLAG is set to NO_COPY, everything else will be implemented for the variable except copy.

The field FLAG can take the following values and multiple values can be OR'ed together. A combination of these flags determine the kind of operations that are allowed to be done on the given variable.

FlagDescription
UVM_ALL_ONAll operations are turned on
UVM_DEFAULTEnables all operations and equivalent to UVM_ALL_ON
UVM_NOCOPYDo not copy the given variable
UVM_NOCOMPAREDo not compare the given variable
UVM_NOPRINTDo not print the given variable
UVM_NOPACKDo not pack or unpack the given variable
UVM_REFERENCEOperate only on handles, i.e. for object types, do not do deep copy, etc

Along with control on operations, `uvm_field_* macros also provide some control on the radix of the given variable which can take the following values. This value can be OR'ed with the operational flags listed in the table above. UVM_HEX is the default radix if none is specified.

RadixDescription
UVM_BINPrint/record the given variable in binary format
UVM_DECPrint/record the given variable in decimal format
UVM_HEXPrint/record the given variable in hexadecimal format
UVM_OCTPrint/record the given variable in octal format
UVM_STRINGPrint/record the given variable in string format
UVM_TIMEPrint/record the given variable in time format

See more examples of field macros in print , copy , and compare and pack

UVM Tutorial

  1. What is UVM ?
  2. What was used before UVM ?
  3. Why was OVM replaced by UVM ?
  4. What does UVM contain ?
  5. What are some of the prerequisites for learning UVM ?

What is UVM ?

UVM stands for Universal Verification Methodology. It is a standardized methodology for verifying digital designs and systems-on-chip (SoCs) in the semiconductor industry. UVM is built on top of the SystemVerilog language and provides a framework for creating modular, reusable testbench components that can be easily integrated into the design verification process. It also includes a set of guidelines and best practices for developing testbenches, as well as a methodology for running simulations and analyzing results.

UVM has become the de facto standard for design verification in the semiconductor industry, and is widely used by chip designers and verification engineers to verify the correctness and functionality of their designs.

What was used before UVM ?

OVM (Open Verification Methodology) was introduced in 2008 as an open-source verification methodology for digital designs and systems-on-chip (SoCs) and was based on SystemVerilog

UVM was introduced in 2011 as a successor to OVM, and it built upon the concepts and principles of OVM. UVM was designed to be a more standardized and flexible methodology that could be easily adapted to different verification environments and use cases.

Why was OVM replaced by UVM ?

This was done mainly for the following reasons :

  • Standardization: OVM was an open-source methodology that lacked a formal standard, which made it difficult for different organizations and tools to interoperate effectively. UVM was designed to be a more standardized methodology, with a well-defined standard that could be adopted by the entire semiconductor industry.
  • Flexibility: OVM was designed primarily for transaction-level modeling (TLM), which limited its applicability to other verification scenarios, such as register-transfer level (RTL) modeling. UVM was designed to be more flexible, with support for both TLM and RTL modeling, as well as other verification scenarios.
  • Reusability: OVM provided a set of pre-defined classes and components for creating testbenches, but these components were not always modular and reusable. UVM was designed to be more modular and reusable, with a clear separation between testbench components and the design-under-test (DUT).
  • Maintainability: OVM was not always easy to maintain or update, as changes to the methodology could affect existing testbenches and components. UVM was designed to be more maintainable, with a clear separation between the methodology and the implementation of testbenches and components.

What does UVM contain ?

It contains a set of pre-defined classes and methods that enable users to create modular, reusable testbench components for verifying digital designs and systems-on-chip (SoCs). Some of the key components of UVM include:

  • Testbench Components: UVM provides a set of base classes that can be extended to create testbench components, such as drivers, monitors, scoreboards, and agents.
  • For example, the image below shows how a typical verification environment is built by extending readily available UVM classes which are denoted by uvm_* prefix. These components already have the necessary code that will let them connect between each other, handle data packets and work synchronously with others.

  • Transactions: Transactions are used to model the communication between the design-under-test (DUT) and the testbench. UVM provides a transaction class that can be extended to create transaction objects that carry information between the DUT and the testbench.
  • Phases: UVM defines a set of simulation phases that enable users to control the order in which testbench components are created, initialized, and executed.
  • Messaging and Reporting: UVM provides a messaging and reporting infrastructure that enables users to output information about the simulation, such as warnings, errors, and debug information.
  • Configuration: UVM provides a configuration database that allows users to store and retrieve configuration information for testbench components.
  • Functional Coverage: UVM provides a mechanism for tracking functional coverage, which is used to ensure that the design has been thoroughly tested.
  • Register Abstraction Layer: UVM provides a register abstraction layer (RAL) that simplifies the process of creating and accessing register maps.

It also undergoes many revisions where new features are added and some older ones deprecated. The reference manual for UVM can be obtained here and contains description on class hierarchy, functions and tasks. It might be overwhelming for new users to understand UVM because of its extensive API. So it requires a more disciplined approach to understand the framework part by part. Hopefully, you'll find the information in these pages useful.

What are some of the prerequisites for learning UVM ?

UVM is based on the SystemVerilog language, so you should have a basic understanding of SystemVerilog syntax and constructs, such as classes, inheritance, and randomization. This knowledge will help you understand the UVM code and develop your own UVM-based testbenches.

You should be familiar with simulation tools, such as Cadence Incisive, Mentor Graphics Questa, or Synopsys VCS, which are commonly used in digital design verification. This knowledge will help you run and debug UVM-based testbenches using these tools.

You should have a basic understanding of verification methodologies, such as directed testing, constrained random testing, and coverage-driven verification. This knowledge will help you understand the role of UVM in the overall verification process and how to use UVM to implement these methodologies.

Click here to refresh SystemVerilog concepts !

SystemVerilog Constraint 'inside'

  1. Syntax
  2. When used in conditional statements
  3. Used in constraints
  4. Inverted inside
  5. Practical Example
  6. Combinational Logic with assign
-->

The inside keyword in SystemVerilog allows to check if a given value lies within the range specified using the inside phrase. This can also be used inside if and other conditional statements in addition to being used as a constraint.

Syntax


	<variable> inside {<values or range>}
	
	// Inverted "inside"
	!(<variable> inside {<values or range>})

For example,


	m_var inside {4, 7, 9} 		// Check if m_var is either 4,7 or 9
	m_var inside {[10:100]} 	// Check if m_var is between 10 and 100 inclusive

When used in conditional statements

In the following example, inside operator is used both in an if else statement and a ternary operator. flag gets the value 1 if the randomized value of m_data lies within 4 to 9, including 4 and 9. If not, flag gets 0.

Similarly, if else block uses the same operator and prints a display message.


module tb;
	bit [3:0] 	m_data;
	bit 		flag;
	
	initial begin
		for (int i = 0; i < 10; i++) begin
			m_data = $random;
			
			// Used in a ternary operator
			flag = m_data inside {[4:9]} ? 1 : 0;
			
			// Used with "if-else" operators
			if (m_data inside {[4:9]})
				$display ("m_data=%0d INSIDE [4:9], flag=%0d", m_data, flag);
			else 
				$display ("m_data=%0d outside [4:9], flag=%0d", m_data, flag);
			
			
		end
	end
endmodule
 Simulation Log
ncsim> run
m_data=4 INSIDE [4:9], flag=1
m_data=1 outside [4:9], flag=0
m_data=9 INSIDE [4:9], flag=1
m_data=3 outside [4:9], flag=0
m_data=13 outside [4:9], flag=0
m_data=13 outside [4:9], flag=0
m_data=5 INSIDE [4:9], flag=1
m_data=2 outside [4:9], flag=0
m_data=1 outside [4:9], flag=0
m_data=13 outside [4:9], flag=0
ncsim: *W,RNQUIE: Simulation is complete.

Used in constraints

The inside operator is quite useful in constraints and makes code shorter and more readable.


class ABC;
	rand bit [3:0] 	m_var;
	
	// Constrain m_var to be either 3,4,5,6 or 7
	constraint c_var { m_var inside {[3:7]}; }
endclass

module tb;
	initial begin
		ABC abc = new();
		repeat (5) begin
			abc.randomize();
			$display("abc.m_var = %0d", abc.m_var);
		end
		
	end
endmodule

See that all values of m_var lies inside the given range.

 Simulation Log
ncsim> run
abc.m_var = 7
abc.m_var = 6
abc.m_var = 6
abc.m_var = 3
abc.m_var = 4
ncsim: *W,RNQUIE: Simulation is complete.

Inverted inside

The opposite of what the inside operator does can be achieved by placing a not symbol ! before it. This is applicable for both constraints and conditional statements. The following example is the same as we saw before except that its constraint has been tweaked to reflect an inverted inside statement.


class ABC;
	rand bit [3:0] 	m_var;
	
	// Inverted inside: Constrain m_var to be outside 3 to 7
	constraint c_var { !(m_var inside {[3:7]}); }
endclass

module tb;
	initial begin
		ABC abc = new();
		repeat (5) begin
			abc.randomize();
			$display("abc.m_var = %0d", abc.m_var);
		end
		
	end
endmodule

See that all values of m_var lies inside the given range.

 Simulation Log
ncsim> run
abc.m_var = 1
abc.m_var = 12
abc.m_var = 0
abc.m_var = 14
abc.m_var = 10
ncsim: *W,RNQUIE: Simulation is complete.

Practical Example

Say we have a memory located between the address range 0x4000 and 0x5FFF, which is partitioned into two. The first part is used to store instructions and the second part to store data. Say we want to randomize the address for data such that the address falls within the data part of memory, we can easily use the inside operator.


class Data;
	rand bit [15:0] 	m_addr;
	
	constraint c_addr 	{ m_addr inside {[16'h4000:16'h4fff]}; }
endclass

module tb;
	initial begin
		Data data = new();
		repeat (5) begin
			data.randomize();
			$display ("addr = 0x%0h", data.m_addr);
		end
	end
endmodule
 Simulation Log
ncsim> run
addr = 0x48ef
addr = 0x463f
addr = 0x4612
addr = 0x4249
addr = 0x4cee
ncsim: *W,RNQUIE: Simulation is complete.

Combinational Logic with assign

  1. Example #1 : Simple combinational logic
    1. Testbench
  2. Example #2: Half Adder
    1. Testbench
  3. Example #3: Full Adder
    1. Testbench
  4. Example #4: 2x1 Multiplexer
    1. Testbench
  5. Example #5: 1x4 Demultiplexer
    1. Testbench
  6. Example #6: 4x16 Decoder
    1. Testbench
  7. UVM Register Backdoor Access
-->

The verilog assign statement is typically used to continuously drive a signal of wire datatype and gets synthesized as combinational logic. Here are some more design examples using the assign statement.

Example #1 : Simple combinational logic

The code shown below implements a simple digital combinational logic which has an output wire z that is driven continuously with an assign statement to realize the digital equation.


module combo ( 	input 	a, b, c, d, e,
								output 	z);

	assign z = ((a & b) | (c ^ d) & ~e);
	
endmodule

The module combo gets elaborated into the following hardware schematic using synthesis tools and can be seen that the combinational logic is implemented with digital gates.

simple combinational logic with assign

Testbench

The testbench is a platform for simulating the design to ensure that the design does behave as expected. All combinations of inputs are driven to the design module using a for loop with a delay statement of 10 time units so that the new value is applied to the inputs after some time.


module tb;
	// Declare testbench variables
  reg a, b, c, d, e;
  wire z;
  integer i;
  
  // Instantiate the design and connect design inputs/outputs with
  // testbench variables
  combo u0 ( .a(a), .b(b), .c(c), .d(d), .e(e), .z(z));
  
  initial begin
  	// At the beginning of time, initialize all inputs of the design
  	// to a known value, in this case we have chosen it to be 0.
    a <= 0;
    b <= 0;
    c <= 0;
    d <= 0;
    e <= 0;
    
    // Use a $monitor task to print any change in the signal to 
    // simulation console 
    $monitor ("a=%0b b=%0b c=%0b d=%0b e=%0b z=%0b", 
              a, b, c, d, e, z);
    
    // Because there are 5 inputs, there can be 32 different input combinations
    // So use an iterator "i" to increment from 0 to 32 and assign the value
    // to testbench variables so that it drives the design inputs
    for (i = 0; i < 32; i = i + 1) begin
      {a, b, c, d, e} = i;
      #10;
    end
  end
endmodule
 Simulation Log
ncsim> run
a=0 b=0 c=0 d=0 e=0 z=0
a=0 b=0 c=0 d=0 e=1 z=0
a=0 b=0 c=0 d=1 e=0 z=1
a=0 b=0 c=0 d=1 e=1 z=0
a=0 b=0 c=1 d=0 e=0 z=1
a=0 b=0 c=1 d=0 e=1 z=0
a=0 b=0 c=1 d=1 e=0 z=0
a=0 b=0 c=1 d=1 e=1 z=0
a=0 b=1 c=0 d=0 e=0 z=0
a=0 b=1 c=0 d=0 e=1 z=0
a=0 b=1 c=0 d=1 e=0 z=1
a=0 b=1 c=0 d=1 e=1 z=0
a=0 b=1 c=1 d=0 e=0 z=1
a=0 b=1 c=1 d=0 e=1 z=0
a=0 b=1 c=1 d=1 e=0 z=0
a=0 b=1 c=1 d=1 e=1 z=0
a=1 b=0 c=0 d=0 e=0 z=0
a=1 b=0 c=0 d=0 e=1 z=0
a=1 b=0 c=0 d=1 e=0 z=1
a=1 b=0 c=0 d=1 e=1 z=0
a=1 b=0 c=1 d=0 e=0 z=1
a=1 b=0 c=1 d=0 e=1 z=0
a=1 b=0 c=1 d=1 e=0 z=0
a=1 b=0 c=1 d=1 e=1 z=0
a=1 b=1 c=0 d=0 e=0 z=1
a=1 b=1 c=0 d=0 e=1 z=1
a=1 b=1 c=0 d=1 e=0 z=1
a=1 b=1 c=0 d=1 e=1 z=1
a=1 b=1 c=1 d=0 e=0 z=1
a=1 b=1 c=1 d=0 e=1 z=1
a=1 b=1 c=1 d=1 e=0 z=1
a=1 b=1 c=1 d=1 e=1 z=1
ncsim: *W,RNQUIE: Simulation is complete.

Example #2: Half Adder

The half adder module accepts two scalar inputs a and b and uses combinational logic to assign the outputs sum and carry bit cout . The sum is driven by an XOR between a and b while the carry bit is obtained by an AND between the two inputs.


module ha ( input 	a, b,
						output	sum, cout);

	assign sum  = a ^ b;
	assign cout = a & b;
endmodule
half adder circuit with assign

Testbench


module tb;
	// Declare testbench variables
  reg a, b;
  wire sum, cout;
  integer i;

  // Instantiate the design and connect design inputs/outputs with
  // testbench variables  
  ha u0 ( .a(a), .b(b), .sum(sum), .cout(cout));
  
  initial begin
  	// At the beginning of time, initialize all inputs of the design
  	// to a known value, in this case we have chosen it to be 0.  
    a <= 0;
    b <= 0;
    
    // Use a $monitor task to print any change in the signal to 
    // simulation console     
    $monitor("a=%0b b=%0b sum=%0b cout=%0b", a, b, sum, cout);
    
    // Because there are only 2 inputs, there can be 4 different input combinations
    // So use an iterator "i" to increment from 0 to 4 and assign the value
    // to testbench variables so that it drives the design inputs    
    for (i = 0; i < 4; i = i + 1) begin
      {a, b} = i;
      #10;
    end
  end
endmodule
 Simulation Log
ncsim> run
a=0 b=0 sum=0 cout=0
a=0 b=1 sum=1 cout=0
a=1 b=0 sum=1 cout=0
a=1 b=1 sum=0 cout=1
ncsim: *W,RNQUIE: Simulation is complete.

Example #3: Full Adder

A full adder can be built using the half adder module shown above or the entire combinational logic can be applied as is with assign statements to drive the outputs sum and cout .


module fa (	input 	a, b, cin,
						output 	sum, cout);

	assign sum  = (a ^ b) ^ cin;
	assign cout = (a & b) | ((a ^ b) & cin);
endmodule
full adder circuit with assign

Testbench


module tb;
  reg a, b, cin;
  wire sum, cout;
  integer i;
  
  fa u0 ( .a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
  
  initial begin
    a <= 0;
    b <= 0;
    
    $monitor("a=%0b b=%0b cin=%0b sum=%0b cout=%0b", a, b, cin, sum, cout);
    
    for (i = 0; i < 7; i = i + 1) begin
      {a, b, cin} = i;
      #10;
    end
  end
endmodule
 Simulation Log
ncsim> run
a=0 b=0 cin=0 sum=0 cout=0
a=0 b=0 cin=1 sum=1 cout=0
a=0 b=1 cin=0 sum=1 cout=0
a=0 b=1 cin=1 sum=0 cout=1
a=1 b=0 cin=0 sum=1 cout=0
a=1 b=0 cin=1 sum=0 cout=1
a=1 b=1 cin=0 sum=0 cout=1
ncsim: *W,RNQUIE: Simulation is complete.

Example #4: 2x1 Multiplexer

The simple 2x1 multiplexer uses a ternary operator to decide which input should be assigned to the output c . If sel is 1, output is driven by a and if sel is 0 output is driven by b .


module mux_2x1 (input 	a, b, sel,
								output 	c);
				
	assign c = sel ? a : b;
endmodule
2x1 multiplexer

Testbench


module tb;
	// Declare testbench variables
  reg a, b, sel;
  wire c;
  integer i;
  
  // Instantiate the design and connect design inputs/outputs with
  // testbench variables  
  mux_2x1 u0 ( .a(a), .b(b), .sel(sel), .c(c));
  
  initial begin
  	// At the beginning of time, initialize all inputs of the design
  	// to a known value, in this case we have chosen it to be 0.    
    a <= 0;
    b <= 0;
    sel <= 0;
    
    $monitor("a=%0b b=%0b sel=%0b c=%0b", a, b, sel, c);

    for (i = 0; i < 3; i = i + 1) begin
      {a, b, sel} = i;
      #10;
    end
  end
endmodule
 Simulation Log
ncsim> run
a=0 b=0 sel=0 c=0
a=0 b=0 sel=1 c=0
a=0 b=1 sel=0 c=1
ncsim: *W,RNQUIE: Simulation is complete.

Example #5: 1x4 Demultiplexer

The demultiplexer uses a combination of sel and f inputs to drive the different output signals. Each output signal is driven by a separate assign statement. Note that the same signal is generally not recommended to be driven by different assign statements.


module demux_1x4 (	input 				f,
										input [1:0]	 	sel,
										output 				a, b, c, d);

	assign a = f & ~sel[1] & ~sel[0];
	assign b = f &  sel[1] & ~sel[0];
	assign c = f & ~sel[1] &  sel[0];
	assign d = f &  sel[1] &  sel[0];

endmodule
1x4 demultiplexer

Testbench


module tb;
	// Declare testbench variables
  reg f;
  reg [1:0] sel;
  wire a, b, c, d;
  integer i;
  
  // Instantiate the design and connect design inputs/outputs with
  // testbench variables  
  demux_1x4 u0 ( .f(f), .sel(sel), .a(a), .b(b), .c(c), .d(d));
  
  // At the beginning of time, initialize all inputs of the design
  // to a known value, in this case we have chosen it to be 0.  
  initial begin
    f <= 0;
    sel <= 0;
    
    $monitor("f=%0b sel=%0b a=%0b b=%0b c=%0b d=%0b", f, sel, a, b, c, d);
    
    // Because there are 3 inputs, there can be 8 different input combinations
    // So use an iterator "i" to increment from 0 to 8 and assign the value
    // to testbench variables so that it drives the design inputs    
    for (i = 0; i < 8; i = i + 1) begin
      {f, sel} = i;
      #10;
    end
  end
endmodule
 Simulation Log
ncsim> run
f=0 sel=0 a=0 b=0 c=0 d=0
f=0 sel=1 a=0 b=0 c=0 d=0
f=0 sel=10 a=0 b=0 c=0 d=0
f=0 sel=11 a=0 b=0 c=0 d=0
f=1 sel=0 a=1 b=0 c=0 d=0
f=1 sel=1 a=0 b=0 c=1 d=0
f=1 sel=10 a=0 b=1 c=0 d=0
f=1 sel=11 a=0 b=0 c=0 d=1
ncsim: *W,RNQUIE: Simulation is complete.

Example #6: 4x16 Decoder


module dec_3x8 ( 	input 					en,
									input 	[3:0] 	in,
									output  [15:0] 	out);

	assign out = en ? 1 << in: 0;
endmodule
4x16 decoder

Testbench


module tb;
  reg en;
  reg [3:0] in;
  wire [15:0] out;
  integer i;
  
  dec_3x8 u0 ( .en(en), .in(in), .out(out));
  
  initial begin
    en <= 0;
    in <= 0;
    
    $monitor("en=%0b in=0x%0h out=0x%0h", en, in, out);
    
    for (i = 0; i < 32; i = i + 1) begin
      {en, in} = i;
      #10;
    end
  end
endmodule
 Simulation Log
ncsim> run
en=0 in=0x0 out=0x0
en=0 in=0x1 out=0x0
en=0 in=0x2 out=0x0
en=0 in=0x3 out=0x0
en=0 in=0x4 out=0x0
en=0 in=0x5 out=0x0
en=0 in=0x6 out=0x0
en=0 in=0x7 out=0x0
en=0 in=0x8 out=0x0
en=0 in=0x9 out=0x0
en=0 in=0xa out=0x0
en=0 in=0xb out=0x0
en=0 in=0xc out=0x0
en=0 in=0xd out=0x0
en=0 in=0xe out=0x0
en=0 in=0xf out=0x0
en=1 in=0x0 out=0x1
en=1 in=0x1 out=0x2
en=1 in=0x2 out=0x4
en=1 in=0x3 out=0x8
en=1 in=0x4 out=0x10
en=1 in=0x5 out=0x20
en=1 in=0x6 out=0x40
en=1 in=0x7 out=0x80
en=1 in=0x8 out=0x100
en=1 in=0x9 out=0x200
en=1 in=0xa out=0x400
en=1 in=0xb out=0x800
en=1 in=0xc out=0x1000
en=1 in=0xd out=0x2000
en=1 in=0xe out=0x4000
en=1 in=0xf out=0x8000
ncsim: *W,RNQUIE: Simulation is complete.

UVM Register Backdoor Access

  1. What are backdoor accesses ?
  2. Define backdoor HDL path
  3. Example
-->

UVM register model allows access to the DUT registers using the front door as we have seen before in the register environment.

This means that all register read and write operations in the environment are converted into bus transactions that get driven into the bus interface of the design, just like any other hardware component in a typical system would do. Because these accesses are sent as valid bus transactions, it consumes bus cycles. This is the recommended way to verify registers in any design as it closely resembles with what happens in the system.

What are backdoor accesses ?

UVM also allows backdoor accesses which uses a simulator database to directly access the signals within the DUT. Write operations deposit a value onto the signal and read operations sample the current value from the register signal. Since registers are the leaf nodes in a digital system, depositing a new value in the middle of any design transitions should not cause any problem. However, the control logic that becomes active during a typical bus write operation will not be active and hence any logic connected to this may not work as expected.

uvm register backdoor access

A backdoor access takes zero simulation time since the HDL values are directly accessed and do not consume a bus transaction. This is not the recommended way to verify register acesses in any design, but under certain circumstances, backdoor accesses help to enhance verification efforts using frontdoor mechanism.

Define backdoor HDL path

The simulator has to know the HDL path to the signal for which it is trying to do a backdoor operation. So, it is the responsibility of the user to specify the signal path in the register model for every register or for those registers which required backdoor access.

To enhance reuse and portability, the whole path is broken into multiple hierarchies. For example, the path to a given register can be split into the path from testbench top to subsystem top level, and from subsystem top to block level top and block level top to register block top and finally register block top to the given register. If the subsystem is reused in another design, only the section of HDL path from testbench top to the subsystem top level needs to be updated.

Example

Click here to refresh the design example in frontdoor access !

We'll add HDL paths for the traffic controller design example seen in frontdoor access example, and then perform some backdoor access writes and reads. HDL paths are added during register model construction. Basically they are added by certain function calls and hence can be done later after register model construction.

Read more: UVM Register Backdoor Access

  1. UVM Object Print
  2. UVM Register Model Example
  3. SystemVerilog 'integer' and 'byte'
  4. SystemVerilog Data Types
  5. SystemVerilog Array Manipulation

Page 56 of 63

  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
Interview Questions
  Verilog Interview Set 1
  Verilog Interview Set 2
  Verilog Interview Set 3
  Verilog Interview Set 4
  Verilog Interview Set 5

  SystemVerilog Interview Set 1
  SystemVerilog Interview Set 2
  SystemVerilog Interview Set 3
  SystemVerilog Interview Set 4
  SystemVerilog Interview Set 5

  UVM Interview Set 1
  UVM Interview Set 2
  UVM Interview Set 3
  UVM Interview Set 4
Related Topics
  Digital Fundamentals
  Verilog Tutorial

  Verification
  SystemVerilog Tutorial
  UVM Tutorial
  • Verilog Testbench
  • Verilog Coding Style Effect
  • Verilog Conditional Statements
  • Verilog Interview Set 10
  • Synchronous FIFO
  • SystemVerilog Interview Set 10
  • SystemVerilog Interview Set 9
  • SystemVerilog Interview Set 8
  • SystemVerilog Interview Set 7
  • SystemVerilog Interview Set 6
  • UVM Singleton Object
  • UVM Component [uvm_component]
  • UVM Object [uvm_object]
  • UVM Root [uvm_root]
  • UVM Interview Set 4
© 2015 - 2023 ChipVerify
Terms and Conditions | DMCA.com Protection Status