This post will add back-door access to the registers defined in Register Abstraction. With a few additional lines of code, you can access the registers through the back door.
DUT
We use the same DUT (jelly_bean_taster
) as defined in Register Abstraction. The DUT has two registers as shown below.
DUT Registers
The DUT defines each field of the registers as reg
(lines 4 to 8).
module jelly_bean_taster( jelly_bean_if.slave_mp jb_if );
import jelly_bean_pkg::*;
reg [1:0] taste; // TASTE register
reg [2:0] flavor; // RECIPE register
reg [1:0] color;
reg sugar_free;
reg sour;
reg [1:0] command;
initial begin
flavor = 0;
color = 0;
sugar_free = 0;
sour = 0;
command = 0;
taste = 0;
end
always @ ( posedge jb_if.clk ) begin
if ( jb_if.command == JB_WRITE ) begin
flavor < = jb_if.flavor;
color <= jb_if.color;
sugar_free <= jb_if.sugar_free;
sour <= jb_if.sour;
end else if ( jb_if.command == JB_READ ) begin
jb_if.taste <= #2ns taste;
end
end
always @ ( posedge jb_if.clk ) begin
if ( jb_if.flavor == CHOCOLATE && jb_if.sour ) taste <= YUCKY;
else if ( jb_if.flavor != NO_FLAVOR ) taste <= YUMMY;
end
endmodule: jelly_bean_taster
Testbench
The top-level testbench instantiates the jelly_bean_taster
as the dut
(line 6).
module top;
import uvm_pkg::*;
reg clk;
jelly_bean_if jb_if( clk );
jelly_bean_taster dut( jb_if ); // DUT
initial begin // clock
clk = 1;
forever #5ns clk = ! clk;
end
initial begin // waveform
$dumpfile( "dump.vcd" );
$dumpvars( 0, top );
end
initial begin
uvm_config_db#( virtual jelly_bean_if )::set( .cntxt( null ),
.inst_name( "uvm_test_top*" ),
.field_name( "jb_if" ),
.value( jb_if ) );
run_test();
end
endmodule: top
Register Block
To access the DUT registers through the back door, we need to inform the register block about its corresponding HDL path (line 27). In our case, the hierarchical HDL path corresponding to the register block is "top.dut"
.
class jelly_bean_reg_block extends uvm_reg_block;
`uvm_object_utils( jelly_bean_reg_block )
rand jelly_bean_recipe_reg jb_recipe_reg;
rand jelly_bean_taste_reg jb_taste_reg;
uvm_reg_map reg_map;
function new( string name = "jelly_bean_reg_block" );
super.new( .name( name ), .has_coverage( UVM_NO_COVERAGE ) );
endfunction: new
virtual function void build();
jb_recipe_reg = jelly_bean_recipe_reg::type_id::create( "jb_recipe_reg" );
jb_recipe_reg.configure( .blk_parent( this ) );
jb_recipe_reg.build();
jb_taste_reg = jelly_bean_taste_reg::type_id::create( "jb_taste_reg" );
jb_taste_reg.configure( .blk_parent( this ) );
jb_taste_reg.build();
reg_map = create_map( .name( "reg_map" ), .base_addr( 8'h00 ),
.n_bytes( 1 ), .endian( UVM_LITTLE_ENDIAN ) );
reg_map.add_reg( .rg( jb_recipe_reg ), .offset( 8'h00 ), .rights( "WO" ) );
reg_map.add_reg( .rg( jb_taste_reg ), .offset( 8'h01 ), .rights( "RO" ) );
// for back-door access
add_hdl_path( .path( "top.dut" ) );
lock_model(); // finalize the address mapping
endfunction: build
endclass: jelly_bean_reg_block
Registers
We also need to inform each register abstraction class about the HDL path to the register field (line 31). In our case, the taste
register field corresponds to the taste
reg
in the DUT.
class jelly_bean_taste_reg extends uvm_reg;
`uvm_object_utils( jelly_bean_taste_reg )
rand uvm_reg_field taste;
//----------------------------------------------------------------------------
// Function: new
//----------------------------------------------------------------------------
function new( string name = "jelly_bean_taste_reg" );
super.new( .name( name ), .n_bits( 2 ), .has_coverage( UVM_NO_COVERAGE ) );
endfunction: new
//----------------------------------------------------------------------------
// Function: build
//----------------------------------------------------------------------------
virtual function void build();
taste = uvm_reg_field::type_id::create( "taste" );
taste.configure( .parent ( this ),
.size ( 2 ),
.lsb_pos ( 0 ),
.access ( "RO" ),
.volatile ( 1 ),
.reset ( 0 ),
.has_reset ( 1 ),
.is_rand ( 0 ),
.individually_accessible( 0 ) );
// for back-door access
add_hdl_path_slice( .name( "taste" ), .offset( 0 ), .size( 2 ) );
endfunction: build
endclass: jelly_bean_taste_reg
We set the HDL paths to the RECIPE register, too (lines 66 to 69). In this case, we add four HDL paths (one path per DUT reg
).
class jelly_bean_recipe_reg extends uvm_reg;
`uvm_object_utils( jelly_bean_recipe_reg )
rand uvm_reg_field flavor;
rand uvm_reg_field color;
rand uvm_reg_field sugar_free;
rand uvm_reg_field sour;
constraint flavor_color_con {
flavor.value != NO_FLAVOR;
flavor.value == APPLE -> color.value != BLUE;
flavor.value == BLUEBERRY -> color.value == BLUE;
flavor.value < = CHOCOLATE;
}
function new( string name = "jelly_bean_recipe_reg" );
super.new( .name( name ), .n_bits( 7 ), .has_coverage( UVM_NO_COVERAGE ) );
endfunction: new
virtual function void build();
flavor = uvm_reg_field::type_id::create( "flavor" );
flavor.configure( .parent ( this ),
.size ( 3 ),
.lsb_pos ( 0 ),
.access ( "WO" ),
.volatile ( 0 ),
.reset ( 0 ),
.has_reset ( 1 ),
.is_rand ( 1 ),
.individually_accessible( 0 ) );
color = uvm_reg_field::type_id::create( "color" );
color.configure( .parent ( this ),
.size ( 2 ),
.lsb_pos ( 3 ),
.access ( "WO" ),
.volatile ( 0 ),
.reset ( 0 ),
.has_reset ( 1 ),
.is_rand ( 1 ),
.individually_accessible( 0 ) );
sugar_free = uvm_reg_field::type_id::create( "sugar_free" );
sugar_free.configure( .parent ( this ),
.size ( 1 ),
.lsb_pos ( 5 ),
.access ( "WO" ),
.volatile ( 0 ),
.reset ( 0 ),
.has_reset ( 1 ),
.is_rand ( 1 ),
.individually_accessible( 0 ) );
sour = uvm_reg_field::type_id::create( "sour" );
sour.configure( .parent ( this ),
.size ( 1 ),
.lsb_pos ( 6 ),
.access ( "WO" ),
.volatile ( 0 ),
.reset ( 0 ),
.has_reset ( 1 ),
.is_rand ( 1 ),
.individually_accessible( 0 ) );
// for back-door access
add_hdl_path_slice( .name( "flavor" ), .offset( 0 ), .size( 3 ) );
add_hdl_path_slice( .name( "color" ), .offset( 3 ), .size( 2 ) );
add_hdl_path_slice( .name( "sugar_free" ), .offset( 5 ), .size( 1 ) );
add_hdl_path_slice( .name( "sour" ), .offset( 6 ), .size( 1 ) );
endfunction: build
endclass: jelly_bean_recipe_reg
That’s about all you need. Let’s test the back door.
Register Sequence
This sequence demonstrates several ways to access the registers through the back door. As a refresher, we access the registers through the front door first.
- The line 24 uses the
write_reg
task of theuvm_reg_sequence
class to write to the RECIPE register. - The line 27 uses the
read_reg
task of theuvm_reg_sequence
class to read from the TASTE register.
Then, we write the RECIPE register through the back door in three different ways.
- The line 32 uses the
poke_reg
task of theuvm_reg_sequence
class. - The line 36 uses the
write_reg
task of theuvm_reg_sequence
class with theUVM_BACKDOOR
option. - The line 41 uses the
write
task of theuvm_reg
class with theUVM_BACKDOOR
option.
Similarly, we read the TASTE register through the back door in three different ways.
- The line 46 uses the
peek_reg
task of theuvm_reg_sequence
class. - The line 49 uses the
read_reg
task of theuvm_reg_sequence
class with theUVM_BACKDOOR
option. - The line 52 uses the
read
task of theuvm_reg
class with theUVM_BACKDOOR
option.
class jelly_bean_reg_sequence extends uvm_reg_sequence;
`uvm_object_utils( jelly_bean_reg_sequence )
function new( string name = "" );
super.new( name );
endfunction: new
virtual task body();
jelly_bean_reg_block jb_reg_block;
flavor_e flavor;
color_e color;
bit sugar_free;
bit sour;
uvm_status_e status;
uvm_reg_data_t value;
$cast( jb_reg_block, model );
flavor = APPLE;
color = GREEN;
sugar_free = 0;
sour = 1;
// front-door write
write_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor } );
// front-door read
read_reg( jb_reg_block.jb_taste_reg, status, value );
#20ns ;
// back-door writes
flavor = BLUEBERRY;
poke_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor } );
#10ns ;
flavor = BUBBLE_GUM;
write_reg( jb_reg_block.jb_recipe_reg, status, { sour, sugar_free, color, flavor },
UVM_BACKDOOR );
#10ns ;
flavor = CHOCOLATE;
jb_reg_block.jb_recipe_reg.write( status, { sour, sugar_free, color, flavor },
UVM_BACKDOOR, .parent( this ) );
#10ns ;
// back-door reads
peek_reg( jb_reg_block.jb_taste_reg, status, value );
assert( value == YUMMY );
read_reg( jb_reg_block.jb_taste_reg, status, value, UVM_BACKDOOR );
assert( value == YUMMY );
jb_reg_block.jb_taste_reg.read( status, value, UVM_BACKDOOR, .parent( this ) );
assert( value == YUMMY );
#10ns ;
endtask: body
endclass: jelly_bean_reg_sequence
Simulation
Here is an annotated waveform. The front-door access uses the jb_if
, whereas the back-door access directly updates the register value of the DUT. Note that back-door writing CHOCOLATE
to the flavor
field does not update the taste
field of the DUT even though the DUT is supposed to respond YUCKY
to the combination of sour
and CHOCOLATE
. This is because the DUT updates the taste
field in response to the value on the jb_if
, but not to the internal RECIPE register values.