In the UVM register model, if you create a register or a field which is wider than the bus width there is no way to control what order the multiple bus operations occur to update the register/field value. In my case I was using BIG_ENDIAN addressing and the register model would always write the largest address first. My design spec required that the largest address be written last. I searched in vain for a way to change this but ended up overriding the 'do_bus_write' function in 'uvm_reg_map'. If you've run into a similar issue, here's my workaround.
reversed_reg_order_map.sv
`ifndef __REVERSED_REG_ORDER_MAP_SV__
`define __REVERSED_REG_ORDER_MAP_SV__
class reversed_reg_order_map extends uvm_reg_map;
`uvm_object_utils(reversed_reg_order_map)
function new(string name="reversed_reg_order_map");
super.new(name);
endfunction
extern virtual task do_write_bus_ops(uvm_reg_bus_op bus_ops[$],
uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
extern virtual task do_bus_write(uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
endclass
task reversed_reg_order_map::do_write_bus_ops(uvm_reg_bus_op bus_ops[$],
uvm_reg_item rw,
uvm_sequencer_base sequencer,
uvm_reg_adapter adapter);
foreach (bus_ops[i]) begin
uvm_sequence_item bus_req;
uvm_reg_bus_op rw_access;
adapter.m_set_item(rw);
bus_req = adapter.reg2bus(bus_ops[i]);
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)
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 == bus_ops.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...",
bus_ops[i].data, bus_ops[i].addr,
rw.map.get_full_name(), rw.status.name()), UVM_FULL)
if (rw.status == UVM_NOT_OK)
break;
end
endtask
task reversed_reg_order_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;
int n_bits_init;
uvm_reg_bus_op bus_ops[$];
Xget_bus_infoX(rw, map_info, n_bits_init, lsb, skip);
addrs = map_info.addr;
// `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];
curr_byte = 0;
/* 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_init;
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_init;
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_init/(bus_width*8) + 1))
void'(addrs.pop_back());
end
curr_byte=0;
n_bits= n_bits_init;
foreach(addrs[i]) begin: foreach_addr
uvm_reg_bus_op rw_access;
uvm_reg_data_t data;
data = (value >> (curr_byte*8)) & ((1'b1 << (bus_width * 8))-1);
curr_byte += bus_width;
`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;
n_bits -= bus_width * 8;
bus_ops.push_back(rw_access);
end: foreach_addr
foreach (addrs[i])
addrs[i] = addrs[i] + map_info.mem_range.stride;
end: foreach_value
// reverse the write order
begin
uvm_reg_bus_op reversed_bus_op_list[$];
foreach (bus_ops[i])
reversed_bus_op_list.push_front(bus_ops[i]);
do_write_bus_ops(reversed_bus_op_list, rw, sequencer, adapter);
end
endtask
`endif // `ifndef __REVERSED_REG_ORDER_MAP_SV__
Then I used a factory type override:
set_type_override_by_type(uvm_reg_map::get_type(), reversed_reg_order_map::get_type());