The Register Abstraction Layer (RAL) is a powerful feature in UVM that provides a standardized way to model, access, and verify hardware registers. It abstracts the physical register access mechanism, enabling reusable verification code across different bus interfaces and simplifying register verification.
Key Learning: Register model hierarchy, field types, access policies
| Access Type | Description | Read Behavior | Write Behavior |
|---|---|---|---|
| RW | Read-Write | Returns current value | Updates value |
| RO | Read-Only | Returns current value | No effect |
| WO | Write-Only | Returns 0 | Updates value |
| W1C | Write-1-to-Clear | Returns current value | Clears bits written as 1 |
| W1S | Write-1-to-Set | Returns current value | Sets bits written as 1 |
| RC | Read-to-Clear | Returns and clears | No effect |
Key Learning: Register definition, field configuration, address mapping
We'll model a simple peripheral with the following registers:
| Register | Offset | Fields |
|---|---|---|
| CTRL_REG | 0x00 | EN[0], MODE[2:1], IRQ_EN[3] |
| STATUS_REG | 0x04 | BUSY[0], ERROR[1], DONE[2] (RO) |
| DATA_REG | 0x08 | DATA[31:0] |
| IRQ_REG | 0x0C | IRQ_STATUS[3:0] (W1C) |
// Control Register Fields
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
rand uvm_reg_field EN;
rand uvm_reg_field MODE;
rand uvm_reg_field IRQ_EN;
rand uvm_reg_field RESERVED;
function new(string name = "ctrl_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build();
// Create fields
EN = uvm_reg_field::type_id::create("EN");
MODE = uvm_reg_field::type_id::create("MODE");
IRQ_EN = uvm_reg_field::type_id::create("IRQ_EN");
RESERVED = uvm_reg_field::type_id::create("RESERVED");
// Configure: parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand
EN.configure (this, 1, 0, "RW", 0, 1'b0, 1, 1, 0);
MODE.configure (this, 2, 1, "RW", 0, 2'b0, 1, 1, 0);
IRQ_EN.configure (this, 1, 3, "RW", 0, 1'b0, 1, 1, 0);
RESERVED.configure(this, 28, 4, "RO", 0, 28'b0, 1, 0, 0);
endfunction
endclass
class status_reg extends uvm_reg;
`uvm_object_utils(status_reg)
rand uvm_reg_field BUSY;
rand uvm_reg_field ERROR;
rand uvm_reg_field DONE;
function new(string name = "status_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build();
BUSY = uvm_reg_field::type_id::create("BUSY");
ERROR = uvm_reg_field::type_id::create("ERROR");
DONE = uvm_reg_field::type_id::create("DONE");
// Read-Only fields
BUSY.configure (this, 1, 0, "RO", 1, 1'b0, 1, 0, 0);
ERROR.configure(this, 1, 1, "RO", 1, 1'b0, 1, 0, 0);
DONE.configure (this, 1, 2, "RO", 1, 1'b0, 1, 0, 0);
endfunction
endclass
class irq_reg extends uvm_reg;
`uvm_object_utils(irq_reg)
rand uvm_reg_field IRQ_STATUS;
function new(string name = "irq_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build();
IRQ_STATUS = uvm_reg_field::type_id::create("IRQ_STATUS");
// Write-1-to-Clear field
IRQ_STATUS.configure(this, 4, 0, "W1C", 1, 4'b0, 1, 1, 0);
endfunction
endclass
class peripheral_reg_block extends uvm_reg_block;
`uvm_object_utils(peripheral_reg_block)
rand ctrl_reg CTRL;
rand status_reg STATUS;
rand uvm_reg DATA;
rand irq_reg IRQ;
uvm_reg_map reg_map;
function new(string name = "peripheral_reg_block");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
// Create registers
CTRL = ctrl_reg::type_id::create("CTRL");
STATUS = status_reg::type_id::create("STATUS");
DATA = uvm_reg::type_id::create("DATA");
IRQ = irq_reg::type_id::create("IRQ");
// Build and configure
CTRL.configure(this, null, "");
CTRL.build();
STATUS.configure(this, null, "");
STATUS.build();
DATA.configure(this, null, "");
DATA.build();
IRQ.configure(this, null, "");
IRQ.build();
// Create address map
reg_map = create_map("reg_map", 'h0, 4, UVM_LITTLE_ENDIAN);
// Add registers to map
reg_map.add_reg(CTRL, 'h00, "RW");
reg_map.add_reg(STATUS, 'h04, "RO");
reg_map.add_reg(DATA, 'h08, "RW");
reg_map.add_reg(IRQ, 'h0C, "RW");
// Lock model
lock_model();
endfunction
endclass
Key Learning: Bus protocol abstraction, register prediction
class apb_reg_adapter extends uvm_reg_adapter;
`uvm_object_utils(apb_reg_adapter)
function new(string name = "apb_reg_adapter");
super.new(name);
// Support byte enables
supports_byte_enable = 1;
// Provide responses
provides_responses = 1;
endfunction
// Convert RAL transaction to bus transaction
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
apb_transaction txn = apb_transaction::type_id::create("txn");
txn.addr = rw.addr;
txn.wr_en = (rw.kind == UVM_WRITE);
txn.wr_data = rw.data;
return txn;
endfunction
// Convert bus transaction to RAL transaction
virtual function void bus2reg(uvm_sequence_item bus_item,
ref uvm_reg_bus_op rw);
apb_transaction txn;
if (!$cast(txn, bus_item)) begin
`uvm_fatal("CAST", "Failed to cast bus item")
end
rw.addr = txn.addr;
rw.kind = txn.wr_en ? UVM_WRITE : UVM_READ;
rw.data = txn.wr_en ? txn.wr_data : txn.rd_data;
rw.status = UVM_IS_OK;
endfunction
endclass
class reg_predictor extends uvm_reg_predictor #(apb_transaction);
`uvm_component_utils(reg_predictor)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
reg2bus() converts RAL operations to bus transactionsbus2reg() converts bus responses back to RAL formatsupports_byte_enable for partial register accessKey Learning: Register access methods, built-in sequences
class ral_basic_sequence extends uvm_sequence;
`uvm_object_utils(ral_basic_sequence)
peripheral_reg_block reg_model;
function new(string name = "ral_basic_sequence");
super.new(name);
endfunction
task body();
uvm_status_e status;
uvm_reg_data_t data;
// ========== WRITE Operations ==========
// Method 1: write() - Full register write
reg_model.CTRL.write(status, 32'h0000_000F);
// Method 2: set() + update() - Modify and sync
reg_model.CTRL.set(32'h0000_0005);
reg_model.CTRL.update(status);
// Method 3: Field-level access
reg_model.CTRL.EN.set(1);
reg_model.CTRL.MODE.set(2'b11);
reg_model.CTRL.update(status);
// ========== READ Operations ==========
// Method 1: read() - Read from DUT
reg_model.STATUS.read(status, data);
`uvm_info("RAL", $sformatf("STATUS = 0x%0h", data), UVM_LOW)
// Method 2: get() - Get model value (no DUT access)
data = reg_model.CTRL.get();
// Method 3: mirror() - Read DUT and update model
reg_model.STATUS.mirror(status, UVM_CHECK);
// ========== Compare/Check ==========
// Compare model value with expected
if (reg_model.CTRL.EN.get() != 1) begin
`uvm_error("RAL", "EN field mismatch")
end
endtask
endclass
class ral_builtin_tests extends uvm_sequence;
`uvm_object_utils(ral_builtin_tests)
peripheral_reg_block reg_model;
function new(string name = "ral_builtin_tests");
super.new(name);
endfunction
task body();
uvm_reg_hw_reset_seq reset_seq;
uvm_reg_bit_bash_seq bit_bash_seq;
uvm_reg_access_seq access_seq;
// ===== Reset Test =====
// Verify all registers have correct reset values
reset_seq = uvm_reg_hw_reset_seq::type_id::create("reset_seq");
reset_seq.model = reg_model;
reset_seq.start(null);
// ===== Bit Bash Test =====
// Write walking 1s and 0s to verify all bits work
bit_bash_seq = uvm_reg_bit_bash_seq::type_id::create("bit_bash_seq");
bit_bash_seq.model = reg_model;
bit_bash_seq.start(null);
// ===== Access Test =====
// Verify register access types (RW, RO, etc.)
access_seq = uvm_reg_access_seq::type_id::create("access_seq");
access_seq.model = reg_model;
access_seq.start(null);
endtask
endclass
class peripheral_config_sequence extends uvm_reg_sequence;
`uvm_object_utils(peripheral_config_sequence)
peripheral_reg_block reg_model;
// Configuration parameters
rand bit enable;
rand bit [1:0] mode;
rand bit irq_enable;
function new(string name = "peripheral_config_sequence");
super.new(name);
endfunction
task body();
uvm_status_e status;
`uvm_info("SEQ", $sformatf("Configuring: EN=%0b MODE=%0b IRQ_EN=%0b",
enable, mode, irq_enable), UVM_LOW)
// Configure control register
reg_model.CTRL.EN.set(enable);
reg_model.CTRL.MODE.set(mode);
reg_model.CTRL.IRQ_EN.set(irq_enable);
reg_model.CTRL.update(status);
// Verify configuration
reg_model.CTRL.mirror(status, UVM_CHECK);
// Wait for device to be ready
poll_for_ready();
endtask
task poll_for_ready();
uvm_status_e status;
int timeout = 100;
repeat (timeout) begin
reg_model.STATUS.read(status, data);
if (!reg_model.STATUS.BUSY.get()) begin
`uvm_info("SEQ", "Device ready", UVM_LOW)
return;
end
#10;
end
`uvm_error("SEQ", "Timeout waiting for device ready")
endtask
endclass
Key Learning: Automatic coverage, custom coverage extensions
class peripheral_reg_block extends uvm_reg_block;
`uvm_object_utils(peripheral_reg_block)
// ... register declarations ...
function new(string name = "peripheral_reg_block");
// Enable coverage
super.new(name, build_coverage(UVM_CVR_REG_BITS |
UVM_CVR_ADDR_MAP |
UVM_CVR_FIELD_VALS));
endfunction
virtual function void build();
// ... build registers ...
// Sample coverage on register access
CTRL.set_coverage(UVM_CVR_REG_BITS);
STATUS.set_coverage(UVM_CVR_REG_BITS);
endfunction
endclass
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
rand uvm_reg_field EN;
rand uvm_reg_field MODE;
rand uvm_reg_field IRQ_EN;
// Custom covergroup
covergroup ctrl_cov;
en_cp: coverpoint EN.value {
bins disabled = {0};
bins enabled = {1};
}
mode_cp: coverpoint MODE.value {
bins mode_0 = {0};
bins mode_1 = {1};
bins mode_2 = {2};
bins mode_3 = {3};
}
irq_cp: coverpoint IRQ_EN.value {
bins irq_disabled = {0};
bins irq_enabled = {1};
}
// Cross coverage
mode_x_en: cross en_cp, mode_cp;
endgroup
function new(string name = "ctrl_reg");
super.new(name, 32, UVM_CVR_REG_BITS);
ctrl_cov = new();
endfunction
// Override sample method
virtual function void sample(uvm_reg_data_t data,
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map);
super.sample(data, byte_en, is_read, map);
ctrl_cov.sample();
endfunction
endclass
Key Learning: Access methods, performance optimization
| Aspect | Frontdoor | Backdoor |
|---|---|---|
| Mechanism | Via bus interface | Direct hierarchy access |
| Timing | Real bus cycles | Zero time |
| Realism | Full protocol | No protocol |
| Use Case | Functional verification | Initialization, debug |
class peripheral_reg_block extends uvm_reg_block;
virtual function void build();
// ... register creation ...
// Configure backdoor paths (HDL hierarchy)
CTRL.add_hdl_path_slice("ctrl_reg", 0, 32);
STATUS.add_hdl_path_slice("status_reg", 0, 32);
DATA.add_hdl_path_slice("data_reg", 0, 32);
IRQ.add_hdl_path_slice("irq_reg", 0, 32);
// Set root path
add_hdl_path("tb.dut.reg_file");
endfunction
endclass
class backdoor_test_sequence extends uvm_sequence;
`uvm_object_utils(backdoor_test_sequence)
peripheral_reg_block reg_model;
task body();
uvm_status_e status;
uvm_reg_data_t data;
// ===== Backdoor Write =====
// Initialize registers quickly (no bus cycles)
reg_model.CTRL.poke(status, 32'h0000_000F);
// ===== Backdoor Read =====
// Read directly from RTL
reg_model.STATUS.peek(status, data);
`uvm_info("BD", $sformatf("STATUS = 0x%0h", data), UVM_LOW)
// ===== Mixed Access =====
// Use backdoor for init, frontdoor for verification
reg_model.DATA.poke(status, 32'hDEAD_BEEF); // Fast init
reg_model.DATA.read(status, data); // Verify via bus
if (data != 32'hDEAD_BEEF) begin
`uvm_error("BD", "Data mismatch after backdoor write")
end
endtask
endclass
Key Learning: Environment setup, model connection
class peripheral_env extends uvm_env;
`uvm_component_utils(peripheral_env)
apb_agent agent;
peripheral_reg_block reg_model;
apb_reg_adapter adapter;
reg_predictor predictor;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create agent
agent = apb_agent::type_id::create("agent", this);
// Create register model
reg_model = peripheral_reg_block::type_id::create("reg_model");
reg_model.build();
// Create adapter
adapter = apb_reg_adapter::type_id::create("adapter");
// Create predictor
predictor = reg_predictor::type_id::create("predictor", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connect register model to sequencer via adapter
reg_model.reg_map.set_sequencer(agent.sqr, adapter);
// Enable auto-prediction (alternative to explicit predictor)
reg_model.reg_map.set_auto_predict(1);
// OR use explicit predictor
// predictor.map = reg_model.reg_map;
// predictor.adapter = adapter;
// agent.mon.ap.connect(predictor.bus_in);
endfunction
endclass
class ral_test extends uvm_test;
`uvm_component_utils(ral_test)
peripheral_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = peripheral_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
peripheral_config_sequence config_seq;
ral_builtin_tests builtin_seq;
phase.raise_objection(this);
// Run configuration sequence
config_seq = peripheral_config_sequence::type_id::create("config_seq");
config_seq.reg_model = env.reg_model;
config_seq.enable = 1;
config_seq.mode = 2'b10;
config_seq.irq_enable = 1;
config_seq.start(null);
// Run built-in tests
builtin_seq = ral_builtin_tests::type_id::create("builtin_seq");
builtin_seq.reg_model = env.reg_model;
builtin_seq.start(null);
#1000;
phase.drop_objection(this);
endtask
endclass
Continue your VLSI learning journey with the complete blog series:
Or revisit previous chapters to reinforce your learning!