A UVM RAL adapter is a class that converts register transactions from the UVM register model into corresponding bus transactions that can be understood by the DUT. The adapter ensures that the read/write operations performed on the register model are translated into appropriate sequences on the DUT’s bus interface (such as AXI, APB, AHB, or custom protocols). In other words, the UVM RAL Adapter acts as a bridge between the UVM register model and the actual bus interface used to communicate with the DUT (Device Under Test).
Key Responsibilities of UVM RAL Adapter:
- Converts UVM register transactions (uvm_reg_bus_op) into bus-specific transactions.
- Converts bus responses back into UVM register model format.
- Helps in communication between the register model and the bus sequencer.
To implement a UVM RAL adapter, we extend the uvm_reg_adapter base class and override the reg2bus and bus2reg methods.

1. reg2bus Method
- Converts a UVM register transaction (uvm_reg_bus_op) into a bus-specific transaction (e.g., uvm_sequence_item for AXI, APB, AHB, etc.).
- Called when a register operation (read/write) is triggered from the RAL model.
Function Prototype:
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
How It Works
- The register model initiates a register read/write.
reg2bustranslates the genericuvm_reg_bus_opinto a protocol-specific bus transaction.- The translated transaction is sent to the bus driver/sequencer.
2. bus2reg Method
- Converts a bus transaction response back into a UVM register transaction (uvm_reg_bus_op).
- Used when the bus interface returns a response, and the data needs to be written back to the RAL model.
Function Prototype:
virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
How It Works
- The bus interface completes a transaction and returns the response.
bus2regextracts the response (e.g., read data, error status) and updates uvm_reg_bus_op.- The RAL model is updated accordingly.
Implementing a UVM RAL Adapter (Example)
Let’s consider an example where we are verifying a DUT that uses an APB (Advanced Peripheral Bus) interface. We will implement a UVM RAL adapter that translates UVM register transactions into APB transactions.
Step 1: Define the UVM RAL Adapter
Below is the implementation of a UVM RAL adapter for an APB interface:
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
// Indicating that this adapter works with non-sequenced registers
virtual function bit supports_byte_enable();
return 0; // APB does not support byte enables
endfunction
// Convert UVM register transactions to APB transactions
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
apb_transfer bus_tr;
bus_tr = apb_transfer::type_id::create("bus_tr");
bus_tr.addr = rw.addr; // Set the APB address
bus_tr.write = rw.kind == UVM_WRITE; // Determine if it is a read or write
bus_tr.data = rw.data; // Set the data for write operations
bus_tr.strb = '1; // APB does not use byte enables, so set to full access
return bus_tr;
endfunction
// Convert APB transactions to UVM register transactions
virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
apb_transfer bus_tr;
if (!$cast(bus_tr, bus_item)) begin
`uvm_error("APB_ADAPTER", "Failed to cast bus_item to apb_transfer")
return;
end
rw.kind = bus_tr.write ? UVM_WRITE : UVM_READ; // Determine if it's a read or write
rw.addr = bus_tr.addr; // Get the address
rw.data = bus_tr.data; // Get the read data
rw.status = UVM_IS_OK; // Assume the transaction was successful
endfunction
endclass
Step 2: Connecting the Adapter to UVM RAL Model
Once the adapter is implemented, it needs to be connected to the UVM register model and the APB sequencer in the testbench.
class my_reg_block extends uvm_reg_block;
rand uvm_reg my_register;
`uvm_object_utils(my_reg_block)
function new(string name = "my_reg_block");
super.new(name, build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
my_register = uvm_reg::type_id::create("my_register", this);
my_register.configure(this, "MY_REGISTER", 32, UVM_READ_WRITE);
my_register.build();
default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
default_map.add_reg(my_register, 0, "RW");
endfunction
endclass
Now, in the testbench, we bind the register model, adapter, and the APB sequencer inside the UVM Testbench Environment:
class my_env extends uvm_env;
`uvm_component_utils(my_env)
// Declare the Register Model, Adapter, and Predictor
my_reg_block reg_model;
apb_reg_adapter apb_adapter;
uvm_reg_predictor#(apb_transfer) reg_predictor;
apb_sequencer apb_seqr;
function new(string name = "my_env", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create register model, adapter, and predictor
reg_model = my_reg_block::type_id::create("reg_model", this);
apb_adapter = apb_reg_adapter::type_id::create("apb_adapter", this);
reg_predictor = uvm_reg_predictor#(apb_transfer)::type_id::create("reg_predictor", this);
// Create APB sequencer
apb_seqr = apb_sequencer::type_id::create("apb_seqr", this);
// Link the adapter with the register model and sequencer
reg_model.default_map.set_sequencer(apb_seqr, apb_adapter);
// Build the register model
reg_model.build();
endfunction
endclass
Step 3: Running Register Read/Write Operations
Once the adapter and register model are properly connected, we can initiate read and write transactions as follows:
// Write to register
reg_model.my_register.write(status, 32'hA5A5A5A5, .parent(this));
// Read from register
reg_model.my_register.read(status, read_data, .parent(this));
`uvm_info("TEST", $sformatf("Read Data: 0x%0X", read_data), UVM_MEDIUM)