This UVM testbench demonstrates register read/write operations using UVM Register Abstraction Layer (RAL) without an actual DUT.

Explanation of Components:
- Registers (apb_ctrl_reg, apb_status_reg):
- The register model represents memory-mapped registers in the DUT (even though we don’t have a DUT in this case).
- apb_ctrl_reg: Read/Write register with enable and mode fields. (at address 0x0)
- apb_status_reg: Read-Only register with ready and error fields. (at address 0x4)
- Register Block (apb_reg_block):
- Contains
ctrl_regat address0x00andstatus_regat0x04. - It extends
uvm_reg_blockand contains individual registers. - Uses
create_mapto set up register mapping. ctrl_regis read/write, whilestatus_regis read-only.- No DUT connection is defined, so reads return a default value.
- Contains
class reg_model extends uvm_reg_block;
uvm_reg ctrl_reg; // Control Register
uvm_reg status_reg; // Status Register
uvm_reg_map default_map;
function new(string name = "reg_model");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
set_default_map(null); // No bus interface (since no DUT)
// Create CTRL Register
ctrl_reg = uvm_reg::type_id::create("ctrl_reg");
ctrl_reg.configure(this, null, "");
ctrl_reg.build();
ctrl_reg.add_hdl_path_slice("CTRL", 0, 32);
// Create STATUS Register
status_reg = uvm_reg::type_id::create("status_reg");
status_reg.configure(this, null, "");
status_reg.build();
status_reg.add_hdl_path_slice("STATUS", 0, 32);
// Define register map (addr: reg instance)
default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
default_map.add_reg(ctrl_reg, 0, "RW"); // CTRL at address 0x0
default_map.add_reg(status_reg, 4, "RO"); // STATUS at address 0x4
endfunction
endclass
- APB Transaction (apb_transaction):
- Defines an APB protocol transaction with
addr,data, andreadfields.
- Defines an APB protocol transaction with
class apb_transaction extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit read;
function new(string name = "apb_transaction");
super.new(name);
endfunction
`uvm_object_utils(apb_transaction)
endclass
- Creating the driver:
- The driver receives register transactions from the sequencer and converts them into APB transactions.
- Implementing
uvm_driverto perform APB Read/Write operations. - The driver listens for APB transactions from the sequencer.
- Reads return
0xDEADBEEF, since there’s no DUT.
class apb_driver extends uvm_driver#(apb_transaction);
`uvm_component_utils(apb_driver)
apb_transaction tr;
function new(string name = "apb_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(tr); // Get transaction from sequencer
`uvm_info("DRV", $sformatf("Driving transaction: addr=0x%0h, data=0x%0h, read=%0b", tr.addr, tr.data, tr.read), UVM_MEDIUM)
// Simulate APB write or read operation
if (!tr.read) begin
`uvm_info("DRV", "Performing APB Write", UVM_MEDIUM)
// Apply signals to the APB interface for write
end else begin
`uvm_info("DRV", "Performing APB Read", UVM_MEDIUM)
// Apply signals for read operation and return data
tr.data = 32'hDEADBEEF; // Simulated read response
end
seq_item_port.item_done();
end
endtask
endclass
- Creating the Sequence :
- Writing
0x3to theCTRLregister. - Reading back the
CTRLregister. (addr=0x0). - Reading the
STATUSregister. (addr=0x4).
- Writing
class apb_reg_sequence extends uvm_reg_sequence;
`uvm_object_utils(apb_reg_sequence)
uvm_status_e status;
bit [31:0] data;
apb_reg_block reg_model;
function new(string name = "apb_reg_sequence");
super.new(name);
endfunction
virtual task body();
// Cast `model` to `apb_reg_block`
if (!$cast(reg_model, model)) begin
`uvm_error("SEQ", "Failed to cast model to apb_reg_block!")
return;
end
// Write to control register
reg_model.ctrl_reg.write(status, 3);
`uvm_info("SEQ", "Wrote 3 to CTRL register", UVM_LOW)
// Read from control register
reg_model.ctrl_reg.read(status, data);
`uvm_info("SEQ", $sformatf("Read CTRL register: %0d", data), UVM_LOW)
// Read from status register
reg_model.status_reg.read(status, data);
`uvm_info("SEQ", $sformatf("Read STATUS register: %0d", data), UVM_LOW)
endtask
endclass
- APB Adapter (apb_reg_adapter):
- Converts UVM register operations to APB transactions.
class apb_reg_adapter extends uvm_reg_adapter;
`uvm_object_utils(apb_reg_adapter)
function new(string name = "apb_reg_adapter");
super.new(name);
endfunction
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
apb_transaction apb_tr = apb_transaction::type_id::create("apb_tr");
apb_tr.addr = rw.addr;
apb_tr.data = rw.data;
apb_tr.read = (rw.kind == UVM_READ);
return apb_tr;
endfunction
virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
apb_transaction apb_tr;
if (!$cast(apb_tr, bus_item)) return;
rw.kind = apb_tr.read ? UVM_READ : UVM_WRITE;
rw.addr = apb_tr.addr;
rw.data = apb_tr.data;
endfunction
endclass
- APB Predictor (apb_reg_predictor):
- Predicts register values based on APB transactions.
class apb_reg_predictor extends uvm_reg_predictor#(apb_transaction);
`uvm_component_utils(apb_reg_predictor)
//Add analysis port
uvm_analysis_imp#(apb_transaction, apb_reg_predictor) ap;
function new(string name = "apb_reg_predictor", uvm_component parent);
super.new(name, parent);
ap = new("ap", this); //Instantiate analysis port
endfunction
virtual function void write(apb_transaction tr);
uvm_reg_bus_op rw;
rw.kind = tr.read ? UVM_READ : UVM_WRITE;
rw.addr = tr.addr;
rw.data = tr.data;
rw.status = UVM_IS_OK;
if (map != null) begin
uvm_reg r;
r = map.get_reg_by_offset(rw.addr); // Get register by address
if (r != null) begin
r.predict(
rw.data,
-1,
UVM_PREDICT_DIRECT,
UVM_FRONTDOOR,
null
);
end else begin
`uvm_error("PREDICTOR", $sformatf("No register found at address 0x%0h", rw.addr))
end
end else begin
`uvm_error("PREDICTOR", "Register map is NULL!")
end
endfunction
endclass
- Testbench (testbench):
- Initializes register model.
- Writes
3to CTRL_REG and reads it back. - Reads STATUS_REG.
class apb_test extends uvm_test;
`uvm_component_utils(apb_test)
apb_reg_sequence seq;
apb_reg_block reg_model;
apb_reg_adapter adapter;
apb_reg_predictor predictor;
apb_sequencer sequencer; //Declare the sequencer
apb_driver driver;
function new(string name = "apb_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
reg_model = apb_reg_block::type_id::create("reg_model");
reg_model.build();
sequencer = apb_sequencer::type_id::create("sequencer", this); //Create the sequencer
adapter = apb_reg_adapter::type_id::create("adapter");
predictor = apb_reg_predictor::type_id::create("predictor", this);
driver = apb_driver::type_id::create("driver", this);
predictor.map = reg_model.default_map;
//Connect sequencer and driver
driver.seq_item_port.connect(sequencer.seq_item_export);
//Connect register model to sequencer through adapter
reg_model.default_map.set_sequencer(sequencer, adapter);
endfunction
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
// Start register sequence
seq = apb_reg_sequence::type_id::create("seq");
seq.model = reg_model; //Attach register model
seq.start(sequencer); //Start sequence on sequencer
#50ns;
phase.drop_objection(this);
endtask
endclass
This UVM RAL implementation enables register modeling and interaction using the APB protocol.
Output:
UVM_INFO @ 0: reporter [RNTST] Running test apb_test...
UVM_INFO driver.sv(13) @ 0: uvm_test_top.driver [DRV] Driving transaction: addr=0x0, data=0x3, read=0
UVM_INFO driver.sv(17) @ 0: uvm_test_top.driver [DRV] Performing APB Write
UVM_INFO @ 0: reporter [RegModel] Wrote register via map reg_model.default_map: reg_model.ctrl_reg=0x3
UVM_INFO sequence.sv(22) @ 0: uvm_test_top.sequencer@@seq [SEQ] Wrote 3 to CTRL register
UVM_INFO driver.sv(13) @ 0: uvm_test_top.driver [DRV] Driving transaction: addr=0x0, data=0x0, read=1
UVM_INFO driver.sv(20) @ 0: uvm_test_top.driver [DRV] Performing APB Read
UVM_INFO @ 0: reporter [RegModel] Read register via map reg_model.default_map: reg_model.ctrl_reg=deadbeef
UVM_INFO sequence.sv(26) @ 0: uvm_test_top.sequencer@@seq [SEQ] Read CTRL register: 3735928559
UVM_INFO driver.sv(13) @ 0: uvm_test_top.driver [DRV] Driving transaction: addr=0x4, data=0x0, read=1
UVM_INFO driver.sv(20) @ 0: uvm_test_top.driver [DRV] Performing APB Read
UVM_INFO @ 0: reporter [RegModel] Read register via map reg_model.default_map: reg_model.status_reg=deadbeef
UVM_INFO sequence.sv(30) @ 0: uvm_test_top.sequencer@@seq [SEQ] Read STATUS register: 3735928559
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_objection.svh(1276) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_report_server.svh(904) @ 50: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
** Report counts by severity
UVM_INFO : 14
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[DRV] 6
[RNTST] 1
[RegModel] 3
[SEQ] 3
[TEST_DONE] 1
$finish called from file "/apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_root.svh", line 527.
$finish at simulation time 50
V C S S i m u l a t i o n R e p o r t
Next Step: Integrate with a DUT → If a DUT is added, the driver will communicate with actual hardware, and real register values will be returned instead of 0xDEADBEEF.