在使用ral model时,会用到write 和read 操作,今天就讲一下~~
首先讲一下task write & read:
virtual task write (output uvm_status_e status,
input uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);
// create an abstract transaction for this operation
uvm_reg_item rw;
XatomicX(1);
set(value);
rw = uvm_reg_item::type_id::create("write_item",,get_full_name());
rw.element = this;
rw.element_kind = UVM_REG;
rw.kind = UVM_WRITE;
rw.value[0] = value;
rw.path = path;
rw.map = map;
rw.parent = parent;
rw.prior = prior;
rw.extension = extension;
rw.fname = fname;
rw.lineno = lineno;
do_write(rw);
status = rw.status;
XatomicX(0);
endtask
write task 会调用do_write,其中path default value是UVM_FRONTDOOR,如果是UVM_BACKDOOR,在找不到对应的reg时,会使用FRONTDOOR访问该register。
task uvm_reg_map::do_write(uvm_reg_item rw);
uvm_reg_map system_map = get_root_map();
uvm_reg_adapter adapter = system_map.get_adapter();
uvm_sequencer_base sequencer = system_map.get_sequencer();
if (adapter != null && adapter.parent_sequence != null) begin
uvm_object o;
uvm_sequence_base seq;
o = adapter.parent_sequence.clone();
assert($cast(seq,o));
seq.set_parent_sequence(rw.parent);
rw.parent = seq;
end
if (rw.parent == null)
rw.parent = new("default_parent_seq");
if (adapter == null) begin
rw.set_sequencer(sequencer);
rw.parent.start_item(rw,rw.prior);
rw.parent.finish_item(rw);
rw.end_event.wait_on();
end
else begin
do_bus_write(rw, sequencer, adapter);
end
endtask
do_wtite 会调用do_bus_write;
ask uvm_reg_map::do_bus_write (uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
uvm_reg_addr_t addrs[$];
uvm_reg_map system_map = get_root_map();
int unsigned bus_width = get_n_bytes();
uvm_reg_byte_en_t byte_en = -1;
uvm_reg_map_info map_info;
int n_bits;
int lsb;
int skip;
int unsigned curr_byte;
int n_access_extra, n_access;
Xget_bus_infoX(rw, map_info, n_bits, lsb, skip);
`UVM_DA_TO_QUEUE(addrs,map_info.addr)
// if a memory, adjust addresses based on offset
if (rw.element_kind == UVM_MEM)
foreach (addrs[i])
addrs[i] = addrs[i] + map_info.mem_range.stride * rw.offset;
foreach (rw.value[val_idx]) begin: foreach_value
uvm_reg_data_t value = rw.value[val_idx];
/* calculate byte_enables */
if (rw.element_kind == UVM_FIELD) begin
int temp_be;
int idx;
n_access_extra = lsb%(bus_width*8);
n_access = n_access_extra + n_bits;
temp_be = n_access_extra;
value = value << n_access_extra;
while(temp_be >= 8) begin
byte_en[idx++] = 0;
temp_be -= 8;
end
temp_be += n_bits;
while(temp_be > 0) begin
byte_en[idx++] = 1;
temp_be -= 8;
end
byte_en &= (1<<idx)-1;
for (int i=0; i<skip; i++)
void'(addrs.pop_front());
while (addrs.size() > (n_bits/(bus_width*8) + 1))
void'(addrs.pop_back());
end
foreach(addrs[i]) begin: foreach_addr
uvm_sequence_item bus_req;
uvm_reg_bus_op rw_access;
uvm_reg_data_t data;
data = (value >> (curr_byte*8)) & ((1'b1 << (bus_width * 8))-1);
`uvm_info(get_type_name(),
$sformatf("Writing 'h%0h at 'h%0h via map \"%s\"...",
data, addrs[i], rw.map.get_full_name()), UVM_FULL);
if (rw.element_kind == UVM_FIELD) begin
for (int z=0;z<bus_width;z++)
rw_access.byte_en[z] = byte_en[curr_byte+z];
end
rw_access.kind = rw.kind;
rw_access.addr = addrs[i];
rw_access.data = data;
rw_access.n_bits = (n_bits > bus_width*8) ? bus_width*8 : n_bits;
rw_access.byte_en = byte_en;
adapter.m_set_item(rw);
bus_req = adapter.reg2bus(rw_access);//通过调用reg2bus,把reg item转化为要发送的bus item
adapter.m_set_item(null);
if (bus_req == null)
`uvm_fatal("RegMem",{"adapter [",adapter.get_name(),"] didnt return a bus transaction"});
bus_req.set_sequencer(sequencer);
rw.parent.start_item(bus_req,rw.prior);//把bus item do 给sequencer
if (rw.parent != null && i == 0)
rw.parent.mid_do(rw);
rw.parent.finish_item(bus_req);
bus_req.end_event.wait_on();
if (adapter.provides_responses) begin
uvm_sequence_item bus_rsp;
uvm_access_e op;
// TODO: need to test for right trans type, if not put back in q
rw.parent.get_base_response(bus_rsp);
adapter.bus2reg(bus_rsp,rw_access);
end
else begin
adapter.bus2reg(bus_req,rw_access);
end
if (rw.parent != null && i == addrs.size()-1)
rw.parent.post_do(rw);
rw.status = rw_access.status;
`uvm_info(get_type_name(),
$sformatf("Wrote 'h%0h at 'h%0h via map \"%s\": %s...",
data, addrs[i], rw.map.get_full_name(), rw.status.name()), UVM_FULL)
if (rw.status == UVM_NOT_OK)
break;
curr_byte += bus_width;
n_bits -= bus_width * 8;
end: foreach_addr
foreach (addrs[i])
addrs[i] = addrs[i] + map_info.mem_range.stride;
end: foreach_value
endtask: do_bus_write
其中,provides_responses的作用介绍一下:
对于支持outstanding的bus agent,需要将provides_responses置为1,对于读操作ral model需要获得data,所以需要等response回过来才能采到数据。
task uvm_reg_map::do_bus_read (uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
uvm_reg_addr_t addrs[$];
uvm_reg_map system_map = get_root_map();
int unsigned bus_width = get_n_bytes();
uvm_reg_byte_en_t byte_en = -1;
uvm_reg_map_info map_info;
int size, n_bits;
int skip;
int lsb;
int unsigned curr_byte;
int n_access_extra, n_access;
Xget_bus_infoX(rw, map_info, n_bits, lsb, skip);
`UVM_DA_TO_QUEUE(addrs,map_info.addr)
size = n_bits;
// if a memory, adjust addresses based on offset
if (rw.element_kind == UVM_MEM)
foreach (addrs[i])
addrs[i] = addrs[i] + map_info.mem_range.stride * rw.offset;
foreach (rw.value[val_idx]) begin: foreach_value
/* calculate byte_enables */
if (rw.element_kind == UVM_FIELD) begin
int temp_be;
int idx;
n_access_extra = lsb%(bus_width*8);
n_access = n_access_extra + n_bits;
temp_be = n_access_extra;
while(temp_be >= 8) begin
byte_en[idx++] = 0;
temp_be -= 8;
end
temp_be += n_bits;
while(temp_be > 0) begin
byte_en[idx++] = 1;
temp_be -= 8;
end
byte_en &= (1<<idx)-1;
for (int i=0; i<skip; i++)
void'(addrs.pop_front());
while (addrs.size() > (n_bits/(bus_width*8) + 1))
void'(addrs.pop_back());
end
rw.value[val_idx] = 0;
foreach (addrs[i]) begin
uvm_sequence_item bus_req;
uvm_reg_bus_op rw_access;
uvm_reg_data_logic_t data;
`uvm_info(get_type_name(),
$sformatf("Reading address 'h%0h via map \"%s\"...",
addrs[i], get_full_name()), UVM_FULL);
if (rw.element_kind == UVM_FIELD)
for (int z=0;z<bus_width;z++)
rw_access.byte_en[z] = byte_en[curr_byte+z];
rw_access.kind = rw.kind;
rw_access.addr = addrs[i];
rw_access.data = 'h0;
rw_access.byte_en = byte_en;
rw_access.n_bits = (n_bits > bus_width*8) ? bus_width*8 : n_bits;
adapter.m_set_item(rw);
bus_req = adapter.reg2bus(rw_access);
adapter.m_set_item(null);
if (bus_req == null)
`uvm_fatal("RegMem",{"adapter [",adapter.get_name(),"] didnt return a bus transaction"});
bus_req.set_sequencer(sequencer);
rw.parent.start_item(bus_req,rw.prior);
if (rw.parent != null && i == 0) begin
rw.parent.mid_do(rw);
end
rw.parent.finish_item(bus_req);
bus_req.end_event.wait_on();
if (adapter.provides_responses) begin
uvm_sequence_item bus_rsp;
uvm_access_e op;
// TODO: need to test for right trans type, if not put back in q
rw.parent.get_base_response(bus_rsp);//等待response回过来
adapter.bus2reg(bus_rsp,rw_access);
end
else begin
adapter.bus2reg(bus_req,rw_access);
end
data = rw_access.data & ((1<<bus_width*8)-1);
rw.status = rw_access.status;
if (rw.status == UVM_IS_OK && (^data) === 1'bx)
rw.status = UVM_HAS_X;
`uvm_info(get_type_name(),
$sformatf("Read 'h%0h at 'h%0h via map \"%s\": %s...", data,
addrs[i], get_full_name(), rw.status.name()), UVM_FULL);
if (rw.status == UVM_NOT_OK)
break;
rw.value[val_idx] |= data << curr_byte*8;
if (rw.parent != null && i == addrs.size()-1)
rw.parent.post_do(rw);
curr_byte += bus_width;
n_bits -= bus_width * 8;
end
if (rw.element_kind == UVM_FIELD)
rw.value[val_idx] = (rw.value[val_idx] >> (n_access_extra)) & ((1<<size)-1);
end
endtask: do_bus_read
summary:
write –> do_write –> do_bus_write –> reg2bus –> do item –> wait response –>bus2reg
backdoor的用法
加入hdl path的方法
法1:
1. 在reg_block中设置好uvm_reg的configure函数的第三个参数.
class reg_model extends uvm_reg_block;
rand reg_invert invert;
rand reg_counter_high counter_high;
rand reg_counter_low counter_low;
virtual function void build();
invert.configure(this,null,"invert");
counter_high.configure(this,null,"counter[31:16]");
counter_low.configure(this,null,"counter[15:0]");
endfunction
endclass
- 在ral model集成到验证平台时,设置好根路径.
function void base_test::build_phase(uvm_phase phase);
rm = reg_model::type_id::create("rm",this);
rm.configure(null,"");
rm.build();
rm.lock_model();
rm.reset();
rm.set_hdl_path_root("top.my_dut");
endfunction
法2:
1. 在reg_block中不设置uvm_reg的configure函数的第三个参数.
class reg_model extends uvm_reg_block;
rand reg_invert invert;
rand reg_counter_high counter_high;
rand reg_counter_low counter_low;
virtual function void build();
invert.configure(this,null,"");//第三个参数为空
counter_high.configure(this,null,"");
counter_low.configure(this,null,"");
add_hdl_path("top.my_dut");
invert.add_hdl_path_slice("invert",0,16);
//invert.add_hdl_path('{ '{"invert", -1, -1} });
counter_high.add_hdl_path_slice("count[31:16]",16,16);
counter_low.add_hdl_path_slice("count[15:0]",0,16);
endfunction
endclass
关于add_hdl_path
function void add_hdl_path ( uvm_hdl_path_slice slices[],
string kind = "RTL" )
Add an HDL path
Add the specified HDL path to the register instance for the specified design abstraction. This method may be called more than once for the same design abstraction if the register is physically duplicated in the design abstractild be specified using the following literal value
1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
Bits: 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+-+---+-------------+---+-------+
|A|xxx| B |xxx| C |
+-+---+-------------+---+-------+
add_hdl_path('{ '{"A_reg", 15, 1},
'{"B_reg", 6, 7},
'{'C_reg", 0, 4}
}
);
If the register is implementd using a single HDL variable, The array should specify a single slice with its offset and size specified as -1. For example:
r1.add_hdl_path('{ '{"r1", -1, -1} });
uvm_reg::peek()/poke()
这两个方法只针对后门访问。其不管DUT的行为,比如一个只读的寄存器进行写操作,其可以写进去。
class reg_seq extends uvm_sequence;
task body();
uvm_status_e status;
uvm_reg_data_t data;
p_sequencer.p_rm.counter_low.poke(status,16'hFFFD);
p_sequencer.p_rm.counter_low.peek(status,data);
endtask
endclass