Design
module tff ( input clk,
input rstn,
input t,
output reg q);
always @ (posedge clk) begin
if (!rstn)
q <= 0;
else
if (t)
q <= ~q;
else
q <= q;
end
endmodule
Testbench
module tb;
reg clk;
reg rstn;
reg t;
tff u0 ( .clk(clk),
.rstn(rstn),
.t(t),
.q(q));
always #5 clk = ~clk;
initial begin
{rstn, clk, t} <= 0;
$monitor ("T=%0t rstn=%0b t=%0d q=%0d", $time, rstn, t, q);
repeat(2) @(posedge clk);
rstn <= 1;
for (integer i = 0; i < 20; i = i+1) begin
reg [4:0] dly = $random;
#(dly) t <= $random;
end
#20 $finish;
end
endmodule
ncsim> run T=0 rstn=0 t=0 q=x T=5 rstn=0 t=0 q=0 T=15 rstn=1 t=0 q=0 T=19 rstn=1 t=1 q=0 T=25 rstn=1 t=1 q=1 T=35 rstn=1 t=1 q=0 T=43 rstn=1 t=0 q=0 T=47 rstn=1 t=1 q=0 T=55 rstn=1 t=0 q=1 T=59 rstn=1 t=1 q=1 T=65 rstn=1 t=1 q=0 T=67 rstn=1 t=0 q=0 T=71 rstn=1 t=1 q=0 T=75 rstn=1 t=0 q=1 T=79 rstn=1 t=1 q=1 T=83 rstn=1 t=0 q=1 T=87 rstn=1 t=1 q=1 T=95 rstn=1 t=0 q=0 Simulation complete via $finish(1) at time 115 NS + 0
uvm_object
has many common functions like print, copy and compare that are available to all its child classes and can be used out of the box if UVM automation macros are used inside the class definition. In a previous article , copy
, do_copy
and use of automation macros to print were discussed.
Using automation macros
UVM automation macros can be used for automatic implementation of copy
, print
, compare
and more. In this example, a class called Packet is defined with a variable called m_addr that is registered with automation macros `uvm_field_int
to be included for all the default functions like print, copy, compare, etc. An object of this class is used inside another class Object which has a bunch of other variables of different data types that are also registered using appropriate macros. For example, m_bool is of enum
type and is registered with `uvm_field_enum
macro.
typedef enum {FALSE, TRUE} e_bool;
class Packet extends uvm_object;
rand bit[15:0] m_addr;
virtual function string convert2string();
string contents;
contents = $sformatf("m_addr=0x%0h", m_addr);
endfunction
`uvm_object_utils_begin(Packet)
`uvm_field_int(m_addr, UVM_DEFAULT)
`uvm_object_utils_end
function new(string name = "Packet");
super.new(name);
endfunction
endclass
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
string m_name;
rand Packet m_pkt;
function new(string name = "Object");
super.new(name);
m_name = name;
m_pkt = Packet::type_id::create("m_pkt");
m_pkt.randomize();
endfunction
`uvm_object_utils_begin(Object)
`uvm_field_enum(e_bool, m_bool, UVM_DEFAULT)
`uvm_field_int (m_mode, UVM_DEFAULT)
`uvm_field_string(m_name, UVM_DEFAULT)
`uvm_field_object(m_pkt, UVM_DEFAULT)
`uvm_object_utils_end
endclass
Let us create a base class and create two objects obj1 and obj2 , randomize both and print them. Then compare both objects to see what the result is. Then copy each variable from obj2 from obj1 including the nested class object of type Packet until the comparison errors are resolved.
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);
// Create two objects, randomize them and print the contents
Object obj1 = Object::type_id::create("obj1");
Object obj2 = Object::type_id::create("obj2");
obj1.randomize();
obj1.print();
obj2.randomize();
obj2.print();
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_bool", UVM_LOW)
obj2.m_bool = obj1.m_bool;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_mode", UVM_LOW)
obj2.m_mode = obj1.m_mode;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_name", UVM_LOW)
obj2.m_name = obj1.m_name;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_pkt.m_addr", UVM_LOW)
obj2.m_pkt.m_addr = obj1.m_pkt.m_addr;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
endfunction
function void _compare(Object obj1, obj2);
if (obj2.compare(obj1))
`uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
else
`uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
UVM_INFO @ 0: reporter [RNTST] Running test base_test... ---------------------------------- Name Type Size Value ---------------------------------- obj1 Object - @1903 m_bool e_bool 32 TRUE m_mode integral 4 'h9 m_name string 4 obj1 m_pkt Packet - @1905 m_addr integral 16 'h3cb6 ---------------------------------- ---------------------------------- Name Type Size Value ---------------------------------- obj2 Object - @1907 m_bool e_bool 32 FALSE m_mode integral 4 'hf m_name string 4 obj2 m_pkt Packet - @1908 m_addr integral 16 'h64c1 ---------------------------------- UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_bool: lhs = FALSE : rhs = TRUE UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907 UVM_INFO testbench.sv(71) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(73) @ 0: uvm_test_top [TEST] Copy m_bool UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_mode: lhs = 'hf : rhs = 'h9 UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907 UVM_INFO testbench.sv(78) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(80) @ 0: uvm_test_top [TEST] Copy m_mode UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_name: lhs = "obj2" : rhs = "obj1" UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907 UVM_INFO testbench.sv(85) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(87) @ 0: uvm_test_top [TEST] Copy m_name UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_pkt.m_addr: lhs = 'h64c1 : rhs = 'h3cb6 UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907 UVM_INFO testbench.sv(92) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(94) @ 0: uvm_test_top [TEST] Copy m_pkt.m_addr UVM_INFO testbench.sv(97) @ 0: uvm_test_top [TEST] obj1 and obj2 are same UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
Automation macros introduce a lot of additional code and is not generally recommended
Using do_compare
Another way is for the user to implement do_compare
method inside the child class and compare the values of each variable and return the result. Note in the following example that uvm_object_utils_*
macros are not used and hence the print()
method we used above will not work as is. Instead another method called convert2string
is implemented for both classes so that it returns the contents in a string format when called.
Just like the way print
calls do_print
method, a compare
calls the do_compare
method and implementation of the function is all that is required to be done.
typedef enum {FALSE, TRUE} e_bool;
class Packet extends uvm_object;
rand bit[15:0] m_addr;
virtual function string convert2string();
string contents;
contents = $sformatf("m_addr=0x%0h", m_addr);
return contents;
endfunction
`uvm_object_utils(Packet)
virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
bit res;
Packet _pkt;
$cast(_pkt, rhs);
super.do_compare(_pkt, comparer);
res = super.do_compare(_pkt, comparer) &
m_addr == _pkt.m_addr;
`uvm_info(get_name(), $sformatf("In Packet::do_compare(), res=%0b", res), UVM_LOW)
return res;
endfunction
function new(string name = "Packet");
super.new(name);
endfunction
endclass
class Object extends uvm_object;
rand e_bool m_bool;
rand bit[3:0] m_mode;
string m_name;
rand Packet m_pkt;
function new(string name = "Object");
super.new(name);
m_name = name;
m_pkt = Packet::type_id::create("m_pkt");
m_pkt.randomize();
endfunction
// This task is used to print contents
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);
$sformat(contents, "%s %s", contents, m_pkt.convert2string());
return contents;
endfunction
`uvm_object_utils(Object)
// "rhs" does not contain m_bool, m_mode, etc since its a parent
// handle. So cast into child data type and access using child handle
// Copy each field from the casted handle into local variables
virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
bit res;
Object _obj;
$cast(_obj, rhs);
res = super.do_compare(_obj, comparer) &
m_name == _obj.m_name &
m_mode == _obj.m_mode &
m_bool == _obj.m_bool &
m_pkt.do_compare(_obj.m_pkt, comparer);
`uvm_info(get_name(), $sformatf("In Object::do_compare(), res=%0b", res), UVM_LOW)
return res;
endfunction
endclass
UVM automation macros were used in the previous example and hence the print
method implementation was already done and available to be used. However, when automation is not used, user has to implement either do_print
or atleast fill the convert2string
function so that contents can be printed. We have chosen to do the latter as evident in the code above. So convert2string
function is called in a UVM info statement instead of calling print
.
Then compare the variables, copy each variable from obj1 into obj2 and keep comparing until both objects are the same.
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 obj1 = Object::type_id::create("obj1");
Object obj2 = Object::type_id::create("obj2");
obj1.randomize();
`uvm_info("TEST", $sformatf("Obj1.print: %s", obj1.convert2string()), UVM_LOW)
obj2.randomize();
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_bool", UVM_LOW)
obj2.m_bool = obj1.m_bool;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_mode", UVM_LOW)
obj2.m_mode = obj1.m_mode;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_name", UVM_LOW)
obj2.m_name = obj1.m_name;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
`uvm_info("TEST", "Copy m_pkt.m_addr", UVM_LOW)
obj2.m_pkt.m_addr = obj1.m_pkt.m_addr;
`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
_compare(obj1, obj2);
endfunction
function void _compare(Object obj1, obj2);
if (obj2.compare(obj1))
`uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
else
`uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)
endfunction
endclass
module tb;
initial begin
run_test("base_test");
end
endmodule
UVM_INFO @ 0: reporter [RNTST] Running test base_test... UVM_INFO testbench.sv(96) @ 0: uvm_test_top [TEST] Obj1.print: m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x3cb6 UVM_INFO testbench.sv(98) @ 0: uvm_test_top [TEST] Obj2.print: m_name=obj2 m_bool=FALSE m_mode=0xf m_addr=0x64c1 UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0 UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0 UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(102) @ 0: uvm_test_top [TEST] Copy m_bool UVM_INFO testbench.sv(104) @ 0: uvm_test_top [TEST] Obj2.print: m_name=obj2 m_bool=TRUE m_mode=0xf m_addr=0x64c1 UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0 UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0 UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(107) @ 0: uvm_test_top [TEST] Copy m_mode UVM_INFO testbench.sv(109) @ 0: uvm_test_top [TEST] Obj2.print: m_name=obj2 m_bool=TRUE m_mode=0x9 m_addr=0x64c1 UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0 UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0 UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(112) @ 0: uvm_test_top [TEST] Copy m_name UVM_INFO testbench.sv(114) @ 0: uvm_test_top [TEST] Obj2.print: m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x64c1 UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0 UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0 UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different UVM_INFO testbench.sv(117) @ 0: uvm_test_top [TEST] Copy m_pkt.m_addr UVM_INFO testbench.sv(119) @ 0: uvm_test_top [TEST] Obj2.print: m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x3cb6 UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=1 UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=1 UVM_INFO testbench.sv(126) @ 0: uvm_test_top [TEST] obj1 and obj2 are same UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
What is a single port RAM ?
A single-port RAM (Random Access Memory) is a type of digital memory component that allows data to be read from and written to a single memory location (address) at a time. It is a simple form of memory that provides a basic storage mechanism for digital systems. Each memory location in a single-port RAM can store a fixed number of bits (usually a power of 2, such as 8, 16, 32, etc.).
During a read operation, the data stored at a specific address is retrieved. During a write operation, new data is stored at a specific address, replacing the previous data.
We have seen in previous sessions that by instantiating the environment inside each testcase, we have the ability to tweak environment parameters to enable multiple scenarios. A test writer may not have the knowledge of how verification components are hooked-up with each other or how they interact with each other. Hence, it becomes important to hide those details and only present the test writer with ways to tweak the environment for each testcase and here are a few ways to do that.
What is testbench top module ?
All verification components, interfaces and DUT are instantiated in a top level module
called testbench. It is a static container to hold everything required to be simulated and becomes the root node in the hierarchy. This is usually named tb or tb_top although it can assume any other name.