Universal Verification Methodology (UVM) is a standard to enable faster development and reuse of verification environments and verification IP (VIP) throughout the industry.
It is a set of class libraries defined using the syntax and semantics of SystemVerilog (IEEE 1800) and is now an IEEE standard. The main idea behind UVM is to help companies develop modular, reusable, and scalable testbench structures by providing an API framework that can be deployed across multiple projects.
UVM is mainly derived from Open Verification Methodology (OVM) and is supported by multiple EDA vendors like Synopsys, Cadence, Mentor and Aldec. The UVM class library provides generic utilities like configuration databases, TLM and component hierarchy in addition to data automation features like copy, print, and compare. It brings in a layer of abstraction where every component in the verification environment has a specific role. For example, a driver class object will be responsible only for driving signals to the design, while a monitor simply monitors the design interface and does not drive signals to that interface.
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.

It also goes through 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 become overwhelming for new users because of the extensive API available for implementation. So, it requires a more disciplined approach to understand the framework part by part. Hopefully, you'll find the information in these pages useful.
Click here to refresh SystemVerilog before diving into UVM.
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 Logncsim> 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 Logncsim> 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.
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.

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

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

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

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

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

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 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.

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.
A uvm_object
is the base class from which all other UVM classes for data and components are derived. So it is logical for this class to have a common set of functions and features that can be availed by all its derived classes.
Some of the common functions usually required is the ability to print its contents, copy contents from one object to another, and possibly compare two objects. UVM has many automation macros that get expanded into full code during compilation and implements these functions for all classes with ease.
Example
The purpose of this example is to show how UVM automation macros can be used to print variable contents easily.
A class called Object is derived from uvm_object
thereby inheriting all functions within the parent class. A few variables of different data types are declared and an attempt is made to print the content of these variables after randomization.
There are different variations of `uvm_field_*
macros for each data type and the variables have to be registered with the macro corresponding to its data type. UVM_DEFAULT
indicates that the given variable should be used for all the default UVM automation macros implemented by the object which are copy, print, compare and record.
typedef enum {FALSE, TRUE} e_bool;
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
rand byte m_data[4];
rand shortint m_queue[$];
string m_name;
constraint c_queue { m_queue.size() == 3; }
function new(string name = "Object");
super.new(name);
m_name = name;
endfunction
// Each variable has to be registered with a macro corresponding to its data
// type. For example, "int" types use `uvm_field int, "enum" types use
// `uvm_field_enum, and "string" use `uvm_field_string
`uvm_object_utils_begin(Object)
`uvm_field_enum(e_bool, m_bool, UVM_DEFAULT)
`uvm_field_int (m_mode, UVM_DEFAULT)
`uvm_field_sarray_int(m_data, UVM_DEFAULT)
`uvm_field_queue_int(m_queue, UVM_DEFAULT)
`uvm_field_string(m_name, UVM_DEFAULT)
`uvm_object_utils_end
endclass
To test the above code, we'll simply use a base test class that will print contents of the object.
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
// In the build phase, create an object, randomize it and print
// its contents
function void build_phase(uvm_phase phase);
Object obj = Object::type_id::create("obj");
obj.randomize();
obj.print();
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
By default the UVM printer prints content of any object in a table format in which it specifies the name of the variable, data type of the variable, its size and the value. In the simulation output shown below, it can be seen that the object obj was randomized to the given values. It's quite interesting to note that even arrays (both static and queues) are printed with content of all their indices.
Simulation Logncsim> run UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES] ---------------------------------------------------------------- UVM-1.2 (C) 2007-2014 Mentor Graphics Corporation (C) 2007-2014 Cadence Design Systems, Inc. (C) 2006-2014 Synopsys, Inc. (C) 2011-2013 Cypress Semiconductor Corp. (C) 2013-2014 NVIDIA Corporation ---------------------------------------------------------------- UVM_INFO @ 0: reporter [RNTST] Running test base_test... ------------------------------------- Name Type Size Value ------------------------------------- obj Object - @1899 m_bool e_bool 32 TRUE m_mode integral 4 'hd m_data sa(integral) 4 - [0] integral 8 'h6c [1] integral 8 'hf4 [2] integral 8 'he [3] integral 8 'h58 m_queue da(integral) 3 - [0] integral 16 'h3cb6 [1] integral 16 'h9ae9 [2] integral 16 'hd31d m_name string 3 obj ------------------------------------- UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
Using do_print()
Using automation macros are not recommended these days because it introduces quite a lot of additional code and reduces simulator performance. Instead it is recommended to use the do_*
callback hooks to implement the requirement. For example, to print the contents, the user can implement the function called do_print
inside the derived object and not use the automation macro at all.
The do_print
function is called by the print
function by default and hence whatever is defined in do_print
will be printed out. In this case we'll simply print three selected variables although you can print all variables as required.
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
rand byte m_data[4];
rand shortint m_queue[$];
string m_name;
constraint c_queue { m_queue.size() == 3; }
function new(string name = "Object");
super.new(name);
m_name = name;
endfunction
// Use "do_print" instead of the automation macro
`uvm_object_utils(Object)
// This function simply uses the printer functions to print variables based on their
// data types. For example, "int" variables are printed using function "print_field_int"
virtual function void do_print(uvm_printer printer);
super.do_print(printer);
printer.print_string("m_bool", m_bool.name());
printer.print_field_int("m_mode", m_mode, $bits(m_mode), UVM_HEX);
printer.print_string("m_name", m_name);
endfunction
endclass
This results in a very similar output like the one by `uvm_object_utils_*
. Note that it prints only three variables because only three variables were implemented inside the do_print
function.
ncsim> run UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES] ---------------------------------------------------------------- UVM-1.2 (C) 2007-2014 Mentor Graphics Corporation (C) 2007-2014 Cadence Design Systems, Inc. (C) 2006-2014 Synopsys, Inc. (C) 2011-2013 Cypress Semiconductor Corp. (C) 2013-2014 NVIDIA Corporation ---------------------------------------------------------------- UVM_INFO @ 0: reporter [RNTST] Running test base_test... ------------------------------- Name Type Size Value ------------------------------- obj Object - @1898 m_bool string 4 TRUE m_mode integral 4 'hd m_name string 3 obj ------------------------------- UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
Using sprint
UVM objects have another function called sprint
that returns the formatted contents in a string format. It is a substitute of the print
function and can be used as shown in the example below. Assume the class declaration is the same as the first example shown above without the use of UVM automation macros.
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
function void build_phase(uvm_phase phase);
Object obj = Object::type_id::create("obj");
obj.randomize();
// Instead of calling print() function, let us call "sprint"
`uvm_info(get_type_name(), $sformatf("Contents: %s", obj.sprint()), UVM_LOW)
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
As you can see, it produced the same table format as a string but prefixed by the custom text called "Contents:".
Simulation Log
ncsim> run
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES]
----------------------------------------------------------------
UVM-1.2
(C) 2007-2014 Mentor Graphics Corporation
(C) 2007-2014 Cadence Design Systems, Inc.
(C) 2006-2014 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
(C) 2013-2014 NVIDIA Corporation
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(98) @ 0: uvm_test_top [base_test] Contents:
-------------------------------
Name Type Size Value
-------------------------------
obj Object - @1902
m_bool string 4 TRUE
m_mode integral 4 'he
m_name string 3 obj
-------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
Using convert2string
There is another function available in uvm_object
called convert2string
that will return a string instead of printing the contents in a predefined format. This allows you to define the output into a format you like. For example, we can simply print contents into a single line as shown.
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
rand byte m_data[4];
rand shortint m_queue[$];
string m_name;
constraint c_queue { m_queue.size() == 3; }
function new(string name = "Object");
super.new(name);
m_name = name;
endfunction
// Use "do_print" instead of the automation macro
`uvm_object_utils(Object)
virtual function string convert2string();
string contents = "";
$sformat(contents, "%s m_name=%s", contents, m_name);
$sformat(contents, "%s m_bool=%s", contents, m_bool.name());
$sformat(contents, "%s m_mode=0x%0h", contents, m_mode);
foreach(m_data[i]) begin
$sformat(contents, "%s m_data[%0d]=0x%0h", contents, i, m_data[i]);
end
return contents;
endfunction
In the test class we will have to call convert2string
instead of print
or sprint
. But remember that this function returns a string and hence it has to be within a `uvm_info
or similar report macro.
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
function void build_phase(uvm_phase phase);
Object obj = Object::type_id::create("obj");
obj.randomize();
`uvm_info(get_type_name(), $sformatf("convert2string: %s", obj.convert2string()), UVM_LOW)
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
Simulation Log
ncsim> run
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES]
----------------------------------------------------------------
UVM-1.2
(C) 2007-2014 Mentor Graphics Corporation
(C) 2007-2014 Cadence Design Systems, Inc.
(C) 2006-2014 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
(C) 2013-2014 NVIDIA Corporation
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(99) @ 0: uvm_test_top [base_test] convert2string:
m_name=obj m_bool=TRUE m_mode=0xe m_data[0]=0xf4 m_data[1]=0xe m_data[2]=0x58 m_data[3]=0xbd
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---