What are classes ?
class
is a user-defined datatype, an OOP construct, that can be used to encapsulate data (property) and tasks/functions (methods) which operate on the data. Here's an example:
class myPacket;
bit [2:0] header;
bit encode;
bit [2:0] mode;
bit [7:0] data;
bit stop;
function new (bit [2:0] header = 3'h1, bit [2:0] mode = 5);
this.header = header;
this.encode = 0;
this.mode = mode;
this.stop = 1;
endfunction
function display ();
$display ("Header = 0x%0h, Encode = %0b, Mode = 0x%0h, Stop = %0b",
this.header, this.encode, this.mode, this.stop);
endfunction
endclass
What is an Interface ?
An Interface is a way to encapsulate signals into a block. All related signals are grouped together to form an interface block so that the same interface
can be re-used for other projects. Also it becomes easier to connect with the DUT and other verification components.
Example
APB bus protocol signals are put together in the given interface. Note that signals are declared within interface
and endinterface
.
interface apb_if (input pclk);
logic [31:0] paddr;
logic [31:0] pwdata;
logic [31:0] prdata;
logic penable;
logic pwrite;
logic psel;
endinterface
Why are signals declared logic
?
logic
is a new data type that lets you drive signals of this type via assign statements and in a procedural block. Remember that in verilog, you could drive a reg
only in procedural block and a wire
only in assign statement. But this is only one reason.
Signals connected to the DUT should support 4-states so that X/Z values can be caught. If these signals were bit
then the X/Z would have shown up as 0, and you would have missed that DUT had a X/Z value.
break
module tb;
initial begin
// This for loop increments i from 0 to 9 and exit
for (int i = 0 ; i < 10; i++) begin
$display ("Iteration [%0d]", i);
// Let's create a condition such that the
// for loop exits when i becomes 7
if (i == 7)
break;
end
end
endmodule
ncsim> run Iteration [0] Iteration [1] Iteration [2] Iteration [3] Iteration [4] Iteration [5] Iteration [6] Iteration [7] ncsim: *W,RNQUIE: Simulation is complete.
continue
module tb;
initial begin
// This for loop increments i from 0 to 9 and exit
for (int i = 0 ; i < 10; i++) begin
// Let's create a condition such that the
// for loop
if (i == 7)
continue;
$display ("Iteration [%0d]", i);
end
end
endmodule
ncsim> run Iteration [0] Iteration [1] Iteration [2] Iteration [3] Iteration [4] Iteration [5] Iteration [6] Iteration [8] Iteration [9] ncsim: *W,RNQUIE: Simulation is complete.
An unpacked array is used to refer to dimensions declared after the variable name.
Unpacked arrays may be fixed-size arrays, dynamic arrays, associative arrays or queues.
Single Dimensional Unpacked Array
module tb;
byte stack [8]; // depth = 8, 1 byte wide variable
initial begin
// Assign random values to each slot of the stack
foreach (stack[i]) begin
stack[i] = $random;
$display ("Assign 0x%0h to index %0d", stack[i], i);
end
// Print contents of the stack
$display ("stack = %p", stack);
end
endmodule
Assign 0x24 to index 0 Assign 0x81 to index 1 Assign 0x9 to index 2 Assign 0x63 to index 3 Assign 0xd to index 4 Assign 0x8d to index 5 Assign 0x65 to index 6 Assign 0x12 to index 7 stack = '{'h24, 'h81, 'h9, 'h63, 'hd, 'h8d, 'h65, 'h12}
Multidimensional Unpacked Array
module tb;
byte stack [2][4]; // 2 rows, 4 cols
initial begin
// Assign random values to each slot of the stack
foreach (stack[i])
foreach (stack[i][j]) begin
stack[i][j] = $random;
$display ("stack[%0d][%0d] = 0x%0h", i, j, stack[i][j]);
end
// Print contents of the stack
$display ("stack = %p", stack);
end
endmodule
ncsim> run stack[0][0] = 0x24 stack[0][1] = 0x81 stack[0][2] = 0x9 stack[0][3] = 0x63 stack[1][0] = 0xd stack[1][1] = 0x8d stack[1][2] = 0x65 stack[1][3] = 0x12 stack = '{'{'h24, 'h81, 'h9, 'h63}, '{'hd, 'h8d, 'h65, 'h12}} ncsim: *W,RNQUIE: Simulation is complete.
Packed + Unpacked Array
The example shown below illustrates a multidimensional packed + unpacked array.
module tb;
bit [3:0][7:0] stack [2][4]; // 2 rows, 4 cols
initial begin
// Assign random values to each slot of the stack
foreach (stack[i])
foreach (stack[i][j]) begin
stack[i][j] = $random;
$display ("stack[%0d][%0d] = 0x%0h", i, j, stack[i][j]);
end
// Print contents of the stack
$display ("stack = %p", stack);
// Print content of a given index
$display("stack[0][0][2] = 0x%0h", stack[0][0][2]);
end
endmodule
ncsim> run
stack[0][0] = 0x12153524
stack[0][1] = 0xc0895e81
stack[0][2] = 0x8484d609
stack[0][3] = 0xb1f05663
stack[1][0] = 0x6b97b0d
stack[1][1] = 0x46df998d
stack[1][2] = 0xb2c28465
stack[1][3] = 0x89375212
stack = '{'{'h12153524, 'hc0895e81, 'h8484d609, 'hb1f05663}, '{'h6b97b0d, 'h46df998d, 'hb2c28465, 'h89375212}}
stack[0][0][2] = 0x15
ncsim: *W,RNQUIE: Simulation is complete.
In a multidimensional declaration, the dimensions declared before the name vary more faster than the dimensions following the name.
bit [1:4] m_var [1:5] // 1:4 varies faster than 1:5
bit m_var2 [1:5] [1:3] // 1:3 varies faster than 1:5
bit [1:3] [1:7] m_var3; // 1:7 varies faster than 1:3
bit [1:3] [1:2] m_var4 [1:7] [0:2] // 1:2 varies most rapidly, followed by 1:3, then 0:2 and then 1:7
UVM automation macros also include mechanisms to pack class variables into a bit or byte stream, and unpack a bit stream and populate the class contents. This is particularly useful when dealing with serial forms of communication like SPI, I2C and RS-232.
There are three main functions available to pack and unpack.
Method | Description |
---|---|
pack | Performs a bit-wise concatenation of the class contents into an array of type bit |
pack_bytes | Performs a bit-wise concatenation of the class contents into an array of type byte |
pack_ints | Performs a bit-wise concatenation of the class contents into an array of type int |
unpack | Extracts property values from an array of type bit and stores into correct class variable |
unpack_bytes | Extracts property values from an array of type byte and stores into correct class variable |
unpack_ints | Extracts property values from an array of type int and stores into correct class variable |
In the following examples, usage of all the three types of functions will be explored.
Using automation macros
Pack
A class called Packet is defined with some variables to store address and data, and is registered with `uvm_field_int
macros to enable automation. UVM_DEFAULT specifies that all the default automation methods should be applied to the given variable.
Note that the order of variables as listed within `uvm_object_utils_*
will be used for both packing and unpacking data.
class Packet extends uvm_object;
rand bit [3:0] m_addr;
rand bit [3:0] m_wdata;
rand bit [3:0] m_rdata;
rand bit m_wr;
`uvm_object_utils_begin(Packet)
`uvm_field_int(m_addr, UVM_DEFAULT)
`uvm_field_int(m_wdata, UVM_DEFAULT)
`uvm_field_int(m_rdata, UVM_DEFAULT)
`uvm_field_int(m_wr, UVM_DEFAULT)
`uvm_object_utils_end
function new(string name = "Packet");
super.new(name);
endfunction
endclass
Let us create a test class called pack_test where an object of Packet is created, randomized and packed.
class pack_test extends uvm_test;
`uvm_component_utils(pack_test)
function new(string name = "pack_test", uvm_component parent=null);
super.new(name, parent);
endfunction
// Declare some arrays to store packed data output from different pack functions
bit m_bits[];
byte unsigned m_bytes[];
int unsigned m_ints[];
virtual function void build_phase(uvm_phase phase);
// First create an object of class "Packet", randomize and print its contents
Packet m_pkt = Packet::type_id::create("Packet");
m_pkt.randomize();
m_pkt.print();
// Now, call "pack", "pack_bytes", and "pack_ints" and pass appropriate array type
m_pkt.pack(m_bits);
m_pkt.pack_bytes(m_bytes);
m_pkt.pack_ints(m_ints);
// Print the array contents
`uvm_info(get_type_name(), $sformatf("m_bits=%p", m_bits), UVM_LOW)
`uvm_info(get_type_name(), $sformatf("m_bytes=%p", m_bytes), UVM_LOW)
`uvm_info(get_type_name(), $sformatf("m_ints=%p", m_ints), UVM_LOW)
endfunction
endclass
module tb;
initial begin
run_test("pack_test");
end
endmodule
See that the function pack
returned an array of bits, pack_bytes
returned an array of bytes and pack_ints
returned an array of ints. Note that pack_bytes
padded 0 to the content from m_wr to turn it from 1 to 8.
UVM_INFO @ 0: reporter [RNTST] Running test pack_test... -------------------------------- Name Type Size Value -------------------------------- Packet Packet - @1907 m_addr integral 4 'hd m_wdata integral 4 'h9 m_rdata integral 4 'h6 m_wr integral 1 'h1 -------------------------------- UVM_INFO testbench.sv(74) @ 0: uvm_test_top [pack_test] m_bits='{'h1, 'h1, 'h0, 'h1, 'h1, 'h0, 'h0, 'h1, 'h0, 'h1, 'h1, 'h0, 'h1} UVM_INFO testbench.sv(75) @ 0: uvm_test_top [pack_test] m_bytes='{'hd9, 'h68} UVM_INFO testbench.sv(76) @ 0: uvm_test_top [pack_test] m_ints='{3647471616} UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]
Unpack
Let us create a different test class called unpack_test which will create two Packet objects, but randomize only one of them leaving the other empty. Then pack contents of the first object into an array, and then unpack it into the second object and display its contents. Repeat the same thing with other types of pack functions.
class unpack_test extends uvm_test;
`uvm_component_utils(unpack_test)
function new(string name = "unpack_test", uvm_component parent=null);
super.new(name, parent);
endfunction
// Declare a few arrays to hold packed data for each type of "pack" function
bit m_bits[];
byte unsigned m_bytes[];
int unsigned m_ints[];
// Variables to hold return value from pack function
int m_val1, m_val2, m_val3;
virtual function void build_phase(uvm_phase phase);
Packet m_pkt = Packet::type_id::create("Packet");
Packet m_pkt2 = Packet::type_id::create("Packet");
`uvm_info(get_type_name(), $sformatf("Start pack"), UVM_LOW)
// Randomize the first object, print and pack into bit array, then display
m_pkt.randomize();
m_pkt.print();
m_pkt.pack(m_bits);
`uvm_info(get_type_name(), $sformatf("packed m_bits=%p", m_bits), UVM_LOW)
// Randomize the first object, print and pack into byte array, then display
m_pkt.randomize();
m_pkt.print();
m_pkt.pack_bytes(m_bytes);
`uvm_info(get_type_name(), $sformatf("packed m_bytes=%p", m_bytes), UVM_LOW)
// Randomize the first object, print and pack into int array, then display
m_pkt.randomize();
m_pkt.print();
m_pkt.pack_ints(m_ints);
`uvm_info(get_type_name(), $sformatf("packed m_ints=%p", m_ints), UVM_LOW)
`uvm_info(get_type_name(), $sformatf("Start unpack"), UVM_LOW)
// Now unpack the packed bit array into the second object, and display
m_val1 = m_pkt2.unpack(m_bits);
`uvm_info(get_type_name(), $sformatf("unpacked m_val1=0x%0h", m_val1), UVM_LOW)
m_pkt2.print();
// Now unpack the packed byte array into the second object, and display
m_val2 = m_pkt2.unpack_bytes(m_bytes);
`uvm_info(get_type_name(), $sformatf("unpacked m_val2=0x%0h", m_val2), UVM_LOW)
m_pkt2.print();
// Now unpack the packed int array into the second object, and display
m_val3 = m_pkt2.unpack_ints(m_ints);
`uvm_info(get_type_name(), $sformatf("unpacked m_val3=0x%0h", m_val3), UVM_LOW)
m_pkt2.print();
endfunction
endclass
module tb;
initial begin
run_test("unpack_test");
end
endmodule
Remember that the order in which it is packed should be same as the order in which it is unpacked. Any change in packer policy regarding order may cause the results to be different.
UVM_INFO @ 0: reporter [RNTST] Running test unpack_test... UVM_INFO testbench.sv(93) @ 0: uvm_test_top [unpack_test] Start pack -------------------------------- Name Type Size Value -------------------------------- Packet Packet - @1907 m_addr integral 4 'hd m_wdata integral 4 'h9 m_rdata integral 4 'h6 m_wr integral 1 'h1 -------------------------------- UVM_INFO testbench.sv(95) @ 0: uvm_test_top [unpack_test] packed m_bits='{'h1, 'h1, 'h0, 'h1, 'h1, 'h0, 'h0, 'h1, 'h0, 'h1, 'h1, 'h0, 'h1} -------------------------------- Name Type Size Value -------------------------------- Packet Packet - @1907 m_addr integral 4 'h8 m_wdata integral 4 'he m_rdata integral 4 'h4 m_wr integral 1 'h0 -------------------------------- UVM_INFO testbench.sv(100) @ 0: uvm_test_top [unpack_test] packed m_bytes='{'h8e, 'h40} -------------------------------- Name Type Size Value -------------------------------- Packet Packet - @1907 m_addr integral 4 'h9 m_wdata integral 4 'he m_rdata integral 4 'he m_wr integral 1 'h1 -------------------------------- UVM_INFO testbench.sv(105) @ 0: uvm_test_top [unpack_test] packed m_ints='{2666004480} UVM_INFO testbench.sv(109) @ 0: uvm_test_top [unpack_test] Start unpack UVM_INFO testbench.sv(109) @ 0: uvm_test_top [unpack_test] unpacked m_val1=0xd -------------------------------- Name Type Size Value -------------------------------- Packet Packet - @1909 m_addr integral 4 'hd m_wdata integral 4 'h9 m_rdata integral 4 'h6 m_wr integral 1 'h1 -------------------------------- UVM_INFO testbench.sv(113) @ 0: uvm_test_top [unpack_test] unpacked m_val2=0xd -------------------------------- Name Type Size Value -------------------------------- Packet Packet - @1909 m_addr integral 4 'h8 m_wdata integral 4 'he m_rdata integral 4 'h4 m_wr integral 1 'h0 -------------------------------- UVM_INFO testbench.sv(117) @ 0: uvm_test_top [unpack_test] unpacked m_val3=0xd -------------------------------- Name Type Size Value -------------------------------- Packet Packet - @1909 m_addr integral 4 'h9 m_wdata integral 4 'he m_rdata integral 4 'he m_wr integral 1 'h1 -------------------------------- 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 new code and is not generally recommemded to be used
Using do_pack/do_unpack
Just like print, copy and compare, pack and unpack also has user definable hooks called do_pack
and do_unpack
.
class Packet extends uvm_object;
rand bit [3:0] m_addr;
rand bit [3:0] m_wdata;
rand bit [3:0] m_rdata;
rand bit m_wr;
`uvm_object_utils(Packet)
// Define do_print, so that print method displays contents of this class
virtual function void do_print(uvm_printer printer);
super.do_print(printer);
printer.print_field_int("m_addr", m_addr, $bits(m_addr), UVM_HEX);
printer.print_field_int("m_wdata", m_wdata, $bits(m_wdata), UVM_HEX);
printer.print_field_int("m_rdata", m_rdata, $bits(m_rdata), UVM_HEX);
printer.print_field_int("m_wr", m_wr, $bits(m_wr), UVM_HEX);
endfunction
virtual function void do_pack(uvm_packer packer);
super.do_pack(packer);
packer.pack_field_int(m_addr, $bits(m_addr));
packer.pack_field_int(m_wdata, $bits(m_wdata));
packer.pack_field_int(m_rdata, $bits(m_rdata));
packer.pack_field_int(m_wr, $bits(m_wr));
endfunction
virtual function void do_unpack(uvm_packer packer);
super.do_pack(packer);
m_addr = packer.unpack_field_int($bits(m_addr));
m_wdata = packer.unpack_field_int($bits(m_wdata));
m_rdata = packer.unpack_field_int($bits(m_rdata));
m_wr = packer.unpack_field_int($bits(m_wr));
endfunction
function new(string name = "Packet");
super.new(name);
endfunction
endclass
The same set of tests shown above can be run with this class and will yield the same results.