UVM Register Test Sequence
一、源码分析
1.1 uvm_reg_hw_reset_seq
利用uvm自带的uvm_reg_hw_reset_seq,可以完成对寄存器default value的check。此seq通过frontdoor读取寄存器的值与RAL模型中的mirror值做比较,不一样则报错。
从此seq的介绍中可以看到,在body函数中会检查modle是否为null,因为原始的seq中没有对model赋值,所以实例化sequence之后,首先需要对model赋值。为了以后测试的方便,例如有些寄存器是只写的,那么在检查复位值时,就需要exclude,因此,可以通过继承该sequence构建自定义的default value test sequence。
class uvm_reg_hw_reset_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));
`uvm_object_utils(uvm_reg_hw_reset_seq)
function new(string name="uvm_reg_hw_reset_seq");
super.new(name);
endfunction
// Variable: model
//
// The block to be tested. Declared in the base class.
//
//| uvm_reg_block model;
// Variable: body
//
// Executes the Hardware Reset sequence.
// Do not call directly. Use seq.start() instead.
virtual task body();
if (model == null) begin
`uvm_error("uvm_reg_hw_reset_seq", "Not block or system specified to run sequence on");
return;
end
uvm_report_info("STARTING_SEQ",{"\n\nStarting ",get_name()," sequence...\n"},UVM_LOW);
this.reset_blk(model);
model.reset();
do_block(model);
endtask: body
// Task: do_block
//
// Test all of the registers in a given ~block~
//
protected virtual task do_block(uvm_reg_block blk);
uvm_reg_map maps[$];
uvm_reg_map sub_maps[$];
if (uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
"NO_REG_HW_RESET_TEST", 0) != null ) begin
return;
end
blk.get_maps(maps);
// Iterate over all maps defined for the RegModel block
foreach (maps[d]) begin
uvm_reg regs[$];
maps[d].get_submaps(sub_maps);
if(sub_maps.size() !=0) begin
continue;
end
// Iterate over all registers in the map, checking accesses
// Note: if map were in inner loop, could test simulataneous
// access to same reg via different bus interfaces
regs.delete();
maps[d].get_registers(regs);
foreach (regs[i]) begin
uvm_status_e status;
// Registers with certain attributes are not to be tested
if (uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
"NO_REG_HW_RESET_TEST", 0) != null )
continue;
`uvm_info(get_type_name(),
$sformatf("Verifying reset value of register %s in map \"%s\"...",
regs[i].get_full_name(), maps[d].get_full_name()), UVM_LOW);
regs[i].mirror(status, UVM_CHECK, UVM_FRONTDOOR, maps[d], this);
if (status != UVM_IS_OK) begin
`uvm_error(get_type_name(),
$sformatf("Status was %s when reading reset value of register \"%s\" through map \"%s\".",
status.name(), regs[i].get_full_name(), maps[d].get_full_name()));
end
end
end
begin
uvm_reg_block blks[$];
blk.get_blocks(blks);
foreach (blks[i]) begin
do_block(blks[i]);
end
end
endtask:do_block
//
// task: reset_blk
// Reset the DUT that corresponds to the specified block abstraction class.
//
// Currently empty.
// Will rollback the environment's phase to the ~reset~
// phase once the new phasing is available.
//
// In the meantime, the DUT should be reset before executing this
// test sequence or this method should be implemented
// in an extension to reset the DUT.
//
virtual task reset_blk(uvm_reg_block blk);
endtask
endclass: uvm_reg_hw_reset_seq
1.2 uvm_reg_access_seq
class uvm_reg_single_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));
// Variable: rg
// The register to be tested
uvm_reg rg;
`uvm_object_utils(uvm_reg_single_access_seq)
function new(string name="uvm_reg_single_access_seq");
super.new(name);
endfunction
virtual task body();
uvm_reg_map maps[$];
if (rg == null) begin
`uvm_error("uvm_reg_access_seq", "No register specified to run sequence on")
return;
end
// Registers with some attributes are not to be tested
if (uvm_resource_db#(bit)::get_by_name({"REG::",rg.get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",rg.get_full_name()},
"NO_REG_ACCESS_TEST", 0) != null )
return;
// Can only deal with registers with backdoor access
if (rg.get_backdoor() == null && !rg.has_hdl_path()) begin
`uvm_error("uvm_reg_access_seq", {"Register '",rg.get_full_name(),
"' does not have a backdoor mechanism available"})
return;
end
// Registers may be accessible from multiple physical interfaces (maps)
rg.get_maps(maps);
// Cannot test access if register contains RO or OTHER fields
begin
uvm_reg_field fields[$];
rg.get_fields(fields);
foreach (maps[k]) begin
int ro;
ro=0;
foreach (fields[j]) begin
if (fields[j].get_access(maps[k]) == "RO") begin
ro++;
end
if (!fields[j].is_known_access(maps[k])) begin
`uvm_warning("uvm_reg_access_seq", {"Register '",rg.get_full_name(),
"' has field with unknown access type '",
fields[j].get_access(maps[k]),"', skipping"})
return;
end
end
if(ro==fields.size()) begin
`uvm_warning("uvm_reg_access_seq", {"Register '",
rg.get_full_name(),"' has only RO fields in map ",maps[k].get_full_name(),", skipping"})
return;
end
end
end
// Access each register:
// - Write complement of reset value via front door
// - Read value via backdoor and compare against mirror
// - Write reset value via backdoor
// - Read via front door and compare against mirror
foreach (maps[j]) begin
uvm_status_e status;
uvm_reg_data_t v, exp;
`uvm_info("uvm_reg_access_seq", {"Verifying access of register '",
rg.get_full_name(),"' in map '", maps[j].get_full_name(),
"' ..."}, UVM_LOW)
v = rg.get();
rg.write(status, ~v, UVM_FRONTDOOR, maps[j], this);
if (status != UVM_IS_OK) begin
`uvm_error("uvm_reg_access_seq", {"Status was '",status.name(),
"' when writing '",rg.get_full_name(),
"' through map '",maps[j].get_full_name(),"'"})
end
#1;
rg.mirror(status, UVM_CHECK, UVM_BACKDOOR, uvm_reg_map::backdoor(), this);
if (status != UVM_IS_OK) begin
`uvm_error("uvm_reg_access_seq", {"Status was '",status.name(),
"' when reading reset value of register '",
rg.get_full_name(), "' through backdoor"})
end
rg.write(status, v, UVM_BACKDOOR, maps[j], this);
if (status != UVM_IS_OK) begin
`uvm_error("uvm_reg_access_seq", {"Status was '",status.name(),
"' when writing '",rg.get_full_name(),
"' through backdoor"})
end
rg.mirror(status, UVM_CHECK, UVM_FRONTDOOR, maps[j], this);
if (status != UVM_IS_OK) begin
`uvm_error("uvm_reg_access_seq", {"Status was '",status.name(),
"' when reading reset value of register '",
rg.get_full_name(), "' through map '",
maps[j].get_full_name(),"'"})
end
end
endtask: body
endclass: uvm_reg_single_access_seq
class uvm_reg_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));
// Variable: model
//
// The block to be tested. Declared in the base class.
//
//| uvm_reg_block model;
// Variable: reg_seq
//
// The sequence used to test one register
//
protected uvm_reg_single_access_seq reg_seq;
`uvm_object_utils(uvm_reg_access_seq)
function new(string name="uvm_reg_access_seq");
super.new(name);
endfunction
// Task: body
//
// Executes the Register Access sequence.
// Do not call directly. Use seq.start() instead.
//
virtual task body();
if (model == null) begin
`uvm_error("uvm_reg_access_seq", "No register model specified to run sequence on")
return;
end
uvm_report_info("STARTING_SEQ",{"\n\nStarting ",get_name()," sequence...\n"},UVM_LOW);
reg_seq = uvm_reg_single_access_seq::type_id::create("single_reg_access_seq");
this.reset_blk(model);
model.reset();
do_block(model);
endtask: body
// Task: do_block
//
// Test all of the registers in a block
//
protected virtual task do_block(uvm_reg_block blk);
uvm_reg regs[$];
if (uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
"NO_REG_ACCESS_TEST", 0) != null )
return;
// Iterate over all registers, checking accesses
blk.get_registers(regs, UVM_NO_HIER);
foreach (regs[i]) begin
// Registers with some attributes are not to be tested
if (uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
"NO_REG_ACCESS_TEST", 0) != null )
continue;
// Can only deal with registers with backdoor access
if (regs[i].get_backdoor() == null && !regs[i].has_hdl_path()) begin
`uvm_warning("uvm_reg_access_seq", {"Register '",regs[i].get_full_name(),
"' does not have a backdoor mechanism available"})
continue;
end
reg_seq.rg = regs[i];
reg_seq.start(null,this);
end
begin
uvm_reg_block blks[$];
blk.get_blocks(blks);
foreach (blks[i]) begin
do_block(blks[i]);
end
end
endtask: do_block
// Task: reset_blk
//
// Reset the DUT that corresponds to the specified block abstraction class.
//
// Currently empty.
// Will rollback the environment's phase to the ~reset~
// phase once the new phasing is available.
//
// In the meantime, the DUT should be reset before executing this
// test sequence or this method should be implemented
// in an extension to reset the DUT.
//
virtual task reset_blk(uvm_reg_block blk);
endtask
endclass: uvm_reg_access_seq
1.3 uvm_reg_single_bit_bash_seq
class uvm_reg_single_bit_bash_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));
// Variable: rg
// The register to be tested
uvm_reg rg;
`uvm_object_utils(uvm_reg_single_bit_bash_seq)
function new(string name="uvm_reg_single_bit_bash_seq");
super.new(name);
endfunction
virtual task body();
uvm_reg_field fields[$];
string mode[`UVM_REG_DATA_WIDTH];
uvm_reg_map maps[$];
uvm_reg_data_t dc_mask;
uvm_reg_data_t reset_val;
int n_bits;
string field_access;
if (rg == null) begin
`uvm_error("uvm_reg_bit_bash_seq", "No register specified to run sequence on");
return;
end
// Registers with some attributes are not to be tested
if (uvm_resource_db#(bit)::get_by_name({"REG::",rg.get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",rg.get_full_name()},
"NO_REG_BIT_BASH_TEST", 0) != null )
return;
n_bits = rg.get_n_bytes() * 8;
// Let's see what kind of bits we have...
rg.get_fields(fields);
// Registers may be accessible from multiple physical interfaces (maps)
rg.get_maps(maps);
// Bash the bits in the register via each map
foreach (maps[j]) begin
uvm_status_e status;
uvm_reg_data_t val, exp, v;
int next_lsb;
next_lsb = 0;
dc_mask = 0;
foreach (fields[k]) begin
int lsb, w, dc;
field_access = fields[k].get_access(maps[j]);
dc = (fields[k].get_compare() == UVM_NO_CHECK);
lsb = fields[k].get_lsb_pos();
w = fields[k].get_n_bits();
// Ignore Write-only fields because
// you are not supposed to read them
case (field_access)
"WO", "WOC", "WOS", "WO1", "NOACCESS": dc = 1;
endcase
// Any unused bits on the right side of the LSB?
while (next_lsb < lsb) mode[next_lsb++] = "RO";
repeat (w) begin
mode[next_lsb] = field_access;
dc_mask[next_lsb] = dc;
next_lsb++;
end
end
// Any unused bits on the left side of the MSB?
while (next_lsb < `UVM_REG_DATA_WIDTH)
mode[next_lsb++] = "RO";
`uvm_info("uvm_reg_bit_bash_seq", $sformatf("Verifying bits in register %s in map \"%s\"...",
rg.get_full_name(), maps[j].get_full_name()),UVM_LOW);
// Bash the kth bit
for (int k = 0; k < n_bits; k++) begin
// Cannot test unpredictable bit behavior
if (dc_mask[k]) continue;
bash_kth_bit(rg, k, mode[k], maps[j], dc_mask);
end
end
endtask: body
task bash_kth_bit(uvm_reg rg,
int k,
string mode,
uvm_reg_map map,
uvm_reg_data_t dc_mask);
uvm_status_e status;
uvm_reg_data_t val, exp, v;
bit bit_val;
`uvm_info("uvm_reg_bit_bash_seq", $sformatf("...Bashing %s bit #%0d", mode, k),UVM_HIGH);
repeat (2) begin
val = rg.get();
v = val;
exp = val;
val[k] = ~val[k];
bit_val = val[k];
rg.write(status, val, UVM_FRONTDOOR, map, this);
if (status != UVM_IS_OK) begin
`uvm_error("uvm_reg_bit_bash_seq", $sformatf("Status was %s when writing to register \"%s\" through map \"%s\".",
status.name(), rg.get_full_name(), map.get_full_name()));
end
exp = rg.get() & ~dc_mask;
rg.read(status, val, UVM_FRONTDOOR, map, this);
if (status != UVM_IS_OK) begin
`uvm_error("uvm_reg_bit_bash_seq", $sformatf("Status was %s when reading register \"%s\" through map \"%s\".",
status.name(), rg.get_full_name(), map.get_full_name()));
end
val &= ~dc_mask;
if (val !== exp) begin
`uvm_error("uvm_reg_bit_bash_seq", $sformatf("Writing a %b in bit #%0d of register \"%s\" with initial value 'h%h yielded 'h%h instead of 'h%h",
bit_val, k, rg.get_full_name(), v, val, exp));
end
end
endtask: bash_kth_bit
endclass: uvm_reg_single_bit_bash_seq
uvm_reg_bit_bash_seq实现对寄存器中每个bit的遍历操作,通过frontdoor write、frontdoor read确认每个bit读写操作是否ok。此操作对RW寄存器有效,因为本身其就要支持读写操作。如果读写有问题就可以发现错误。
对于诸如RO、RC这样属性的寄存器,本身就不支持写操作,会导致误报错误,所以后续需要exclude掉.
class uvm_reg_bit_bash_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));
// Variable: model
//
// The block to be tested. Declared in the base class.
//
//| uvm_reg_block model;
// Variable: reg_seq
//
// The sequence used to test one register
//
protected uvm_reg_single_bit_bash_seq reg_seq;
`uvm_object_utils(uvm_reg_bit_bash_seq)
function new(string name="uvm_reg_bit_bash_seq");
super.new(name);
endfunction
// Task: body
//
// Executes the Register Bit Bash sequence.
// Do not call directly. Use seq.start() instead.
//
virtual task body();
if (model == null) begin
`uvm_error("uvm_reg_bit_bash_seq", "No register model specified to run sequence on");
return;
end
uvm_report_info("STARTING_SEQ",{"\n\nStarting ",get_name()," sequence...\n"},UVM_LOW);
reg_seq = uvm_reg_single_bit_bash_seq::type_id::create("reg_single_bit_bash_seq");
this.reset_blk(model);
model.reset();
do_block(model);
endtask
// Task: do_block
//
// Test all of the registers in a given ~block~
//
protected virtual task do_block(uvm_reg_block blk);
uvm_reg regs[$];
if (uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",blk.get_full_name()},
"NO_REG_BIT_BASH_TEST", 0) != null )
return;
// Iterate over all registers, checking accesses
blk.get_registers(regs, UVM_NO_HIER);
foreach (regs[i]) begin
// Registers with some attributes are not to be tested
if (uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
"NO_REG_TESTS", 0) != null ||
uvm_resource_db#(bit)::get_by_name({"REG::",regs[i].get_full_name()},
"NO_REG_BIT_BASH_TEST", 0) != null )
continue;
reg_seq.rg = regs[i];
reg_seq.start(null,this);
end
begin
uvm_reg_block blks[$];
blk.get_blocks(blks);
foreach (blks[i]) begin
do_block(blks[i]);
end
end
endtask: do_block
// Task: reset_blk
//
// Reset the DUT that corresponds to the specified block abstraction class.
//
// Currently empty.
// Will rollback the environment's phase to the ~reset~
// phase once the new phasing is available.
//
// In the meantime, the DUT should be reset before executing this
// test sequence or this method should be implemented
// in an extension to reset the DUT.
//
virtual task reset_blk(uvm_reg_block blk);
endtask
endclass: uvm_reg_bit_bash_seq