UVM like TB using SV
- Details
- Last Updated: Thursday, 18 May 2023 15:58
- Published: Thursday, 18 May 2023 15:58
- Hits: 274
UVM like tb framework using SV: (from sample project)
Here, we build a testbench framework using all these SV constructs that we learned earlier, i.e class, program, env, interface, etc. This is how UVM framework is developed that we'll learn in other section.
usbpd_tb.sv:
`include testbench.sv //this has the program for all classes
module usbpd_tb();
wire, reg, ...;
function/task func1() .. endfunction //functions/tasks
initial/always @(...) begin .... end //build osc, release porz, checkers for clk
digtop I_digtop (.in1(tb_in1), out1(tb_out1)); //DUT inst
i2c_bfm I_i2c_bfm ( ...);
sram_monitor i_mon (.addr(s_addr), ...);
cm0_tarmac u_tarmac(.en(1'b1),...); //tarmac inst for M0
specify $width(...); ....; endspecify //we can define our own clk pulse width check, etc (similar to what's in gate models)
//property checkers included here
property hready_checker;
@(posedge clk) disable iff (hresetn) !hready |=> hready_i; //checker disabled if hresetn=0, else hready_i should match !hready on next clk posedge
endproperty
ass_prop_chk : assert property (hready_checker) //property asserted here
begin $display("OK"); end
else begin $display("ERR"); end
cov_prop_chk : cover property (hready_checker) //property covered here
tb_intf I_tb_if(); //interface defined below in testbench.sv
endmodule
testbench.sv:
typedef class register_base;
typedef class register_field_base;
typedef class register_block_base;
typedef class ahb_collector;
interface tb_intf(); //i/f defn
logic data, addr, ...;
task wrt_addr(...);
endinterface
//start of register_base class => has all reg attributes as name, value, etc.
class register_base;
string parent_module;
string name;
enum {READ, WRITE} rw;
bit [31:0] address;
bit [31:0] value; // same as mirrored_value in UVM registers
bit [31:0] rand_value; // same as value or desired value in UVM registers
bit [31:0] rd_mask; //similarly wr_mask, rd_to_clr, wr0_to_clr, wr1_to_clr, wr1_to_set.
register_block_base parent_reg_blk;
//covergroups
covergroup cg_bit_31 @(bit_31_wr_done_ev or bit_31_rd_done_ev); //sampled whenever these events happen
cp_bit_31 : coverpoint {rw,value[31] } { //coverpoint cp_bit_31 defined for rw and value[31] bits
wildcard bins tran_r0_w0_r = ({READ,1'b0} => {WRITE,1'b0} => {READ,1'bx}); // bin for transition from rd to wrt to rd
wildcard bins trans_r1_r = ({READ,1'b1} => {READ,1'bx});
}
endgroup //similar covergroup for other bits
register_field_base reg_fld_q[$]; // queue of register fields
function new ( string module_name, string reg_name, bit [31:0] reg_address,
bit [31:0] rd_mask, bit [31:0] wr_value, bit [31:0] rd_value, register_block_base register_block_base_inst);
this.parent_module = module_name;
this.nmame = reg_name;
this.address = reg_address;
this.value = rst_value;
this.rd_mask = rd_mask;
this.parent_reg_blk = register_block_base_inst;
this.d_value = this.value;
register_block_base_inst.add_reg(module_name, this); //func defined in reg block base
endfunction
virtual function string get_name(); //string implies return value
return (name); //returns name and so on for other func
endfunction
virtual function void reset(); //to reset fields, return value is nothing, so "void" used
value = rst_value;
foreach (reg_fld_q[i]) void'(reg_fld_q[i].reset());
endfunction
endclass //end of register_base class
//create a class for each reg
class SPARE_REG_type extends register_base;
rand register_field #(6) RESERVED;
rand register_field #(6) STICKY_STS_SET;
rand register_field #(20) RESERVED1;
function new ( string module_name, string reg_name, bit [31:0] reg_address, bit [31:0] rst_value,
bit [31:0] rd_mask, bit [31:0] wr_value, bit [31:0] rd_value, register_block_base reg_blk_inst);
super.new(module_name, reg_name, reg_address, rst_value, rd_mask, wr_value, rd_value, reg_blk_inst);
endfunction
endclass
class AUX_REG ... endclass // and so on for all regs
//end of class for all regs in design
//start of register_field_base
class register_field_base;
string name;
bit [7:0] start_bit;
bit [7:0] length;
register_base parent_reg;
function new ();
endfunction // note: nothing defined in new func, similarly other func defined with nothing in them, as they will be defined in register_field class
endclass
class register_field #(int FIELD_LENGTH = 32) extends register_field_base;
rand bit [FIELD_LENGTH-1:0] rand_value; // desired_value // parameterized
bit [FIELD_LENGTH-1:0] value; // mirrrored_value // parameterized
covergroup reg_field_cg @(parent_reg.wr_done_ev or parent_reg.rd_done_ev);
option.name = name;
cp_reg_field_value: coverpoint (value) { // coverpoint for all values of that reg field
bins field_val[] = {[0: max_value]}; //array of bins created from field_val_0 to field_val[max_value]. So, if 0 sampled, then bin fiel_val_0 is covered and so on. advantage of array of bins is that we can see which values are missing from coverage
}
endgroup // reg_field_cg
function new ( string fld_name, int start_bit, int fld_len, register_base parent_register);
this.name = fld_name; //name, start_bit, length properties come from reg field base
this.start_bit = start_bit;
this.length = fld_len;
this.parent_reg = parent_register;
parent_register.add_reg_field(this);
endfunction // new
virtual function void reset();
// get the reset value from the parent register and copy to the rest of values
value = ((parent_reg.rst_value >> start_bit) & max_value);
prev_value = value;
endfunction // reset
endclass
//class reg block base => largest class in terms of code => all checkers implemented here as tasks
class register_block_base;
register_base reg_q[string][$]; //contains queue of reg base
mailbox reg_tr_mbx;
SPARE_REG_type SPARE_REG; //and so on for all other regs
covergroup custom_SPI_cg; //specify covergroups for regs and bits which we want sampled when C pgm accesses those
cp_mon: coverpoint SPARE_REG.MODE.value;
...
endgroup
task ... forever ... endtask
task automatic cfg_assert(); //all IO pin checks are in this task, similarly frs_assert, gpio_assert etc
config_assert: assert(SPARE_REG.SPARE_OUT.value === `TB.pin[12]) else $display("ASSERTION ERROR"); //similarly for all other IO pins
endtask
function void add_reg (string module_name, register_base reg_inst);
foreach (reg_q[module_name][i]) // check existing registers to make sure there is no duplicate address
if (reg_q[module_name][i].get_address() == reg_inst.get_address()) //do not add reg
begin
$display("ERROR: module_name.substr %s", module_name.substr(0,2));
$finish;
end
reg_q[module_name].push_back(reg_inst); // push reg at end of queue,
endfunction // add_reg
function cg_sample ( ...) //called from detect_rand_request (whenever C pgm writes to loc indicating it wants sampling)
if (testname.substr(0, 2) == "spi") custom_SPI_cg.sample();
else if (...) custom_UART0_cg.sample();
function void build();
create_reg_space(); // instantiate register model and covergroups etc (defined in function below)
CONFIG_SPARE_WAKEUP_REG.rd_mask = 32'hFFF81FFF;
endfunction // build
task start (); //impl as task since they have always, forever etc
apply_hw_lock_mask(); //func defined in this class
apply_trim_lock_mask(); //func defined in this class
fork
register_adapter(); forever task in reg_block_base. At every FCLK edge, it samples AHB tran, and calls gpio_sample, config_sample, etc, depending on addr.
detect_rand_request(); //forever task (@ posedge FCLK, HWRITE=1 and HADDR=RAND_REQ_ADDR and then look at HWDATA[15:0]) to detect random req wrt to different regs (wrt rand val to reg model and copy that to sram)
detect_incoming_rand_xfer(); //to detect tb_if.req to wrt incoming addr/data to sram + detect tb_if.done to wrt end signature once done
test_end_detect(); //this is a forever task to detect "EXIT" inst being written
output_checker(); //it's a fork forever task that calls bunch of other assert tasks as "cfg_assert", on event @config_debug_sampled, etc. these events are triggered in func "config_sample" (called in register_adapter task) when addr match for wrt happens on AHB bus.
join_none //above forked process keep running for ever
endtask // start
function void create_reg_space();
SPARE_REG = new("CONFIG", "SPARE_REG", 32'h01F0, 32'h00000000, 32'hffffffff, 32'hffffffff, 32'h00000000, 32'h00000000, 32'h00000000, 32'h00000000, 32'h00000000, 32'h00000000, 32'h00000000, this); // 0x01F0 - SPARE_WAKEUP_SET_REG
SPARE_REG.RESERVED = new("RESERVED", 26, 6, SPARE_REG);
SPARE_REG.STICKY_STS_SET = new("STICKY_STS_SET", 20, 6, SPARE_REG);
SPARE_REG.RESERVED1 = new("RESERVED1", 0, 20, SPARE_REG);
AUX_REG = new("...); //similarly for all other regs and corresponding bits
endfunction
task register_adapter(); //samples transaction on AHB, and depending on reg addr, puts deglitch values, etc on tb_pin_intf signals
forever begin
@(posedge `CPUTOP.FCLK);
if (reg_tr_mbx.try_get(curr_tran) == 1'b0) $display ("NO AHB transaction"); //get AHB msg that was put in collector below
else begin (if curr_tran.addr == 16'h0F30) reg_h = config_sample(curr_tran.addr[15:2], curr_tran.wr_data, curr_tran.rd_data, 0);
else (if curr_tran.addr == 16'h0A50) reg_h = gpio_sample(...) and so on
endtask
endclass //end of reg block base
//AHB collector
class ahb_collector; //this collects 1 AHB transaction with addr, rddata, wrdata, etc
string name;
mailbox tr_mbx;
ahb_transaction ahb_tran; //a class defined with addr, wrdata, etc
function new (string name, mailbox tr_mbx);
this.name = name;
this.tr_mbx = tr_mbx;
endfunction // new
task body();
forever begin
@(negedge `CPUTOP.FCLK);
ahb_tran = new(`CPUTOP.HWDATA, `CPUTOP.HRDATA, ... );
if (tr_mbx.try_put(ahb_tran) == 1'b0) $display(ERROR"); //ahb_tran class object pushed into mailbox
end
endtask
endclass
//class env
class env;
register_block_base reg_blk_inst;
mailbox tr_mbx; //mailbox to store 1 AHB transaction from M0
ahb_collector ahb_collector_0;
function new();
tr_mbx = new(1); // each module mailbox is one deep
ahb_collector_0 = new("AHB Collector for M0p", tr_mbx);
reg_blk_inst = new(tr_mbx);
endfunction // new
virtual task build();
reg_blk_inst.build(); //creates reg class for all regs and their fields by calling "create_reg_space" func
endtask // build
virtual task start();
fork
reg_blk_inst.start(); //task start, which itself is bunch of forked proc, called from reg block_base. all checkers, etc here
ahb_collector_0.body(); //collects all AHB transactions
misc_collector_0.body();
dbg_fifo_sb_0.body();
join_none; //above forked proc run forever
endtask // start
endclass // env
//testbench starts from here. this is another top level module besides usbpd_tb. It's not called from anywhere.
program testbench();
env env_inst;
initial begin
env_inst = new();
env_inst.build();
env_inst.start();
wait(`TB.tb_finish_test == 1); // wait for test bench to finish test (happens on receiving "EXIT" from main pgm
end
property .... endproperty
endprogram