UVM RAL – Adapter Methods

Understanding reg_to_bus() and bus_to_reg() in Detail

How Write() Works Internally

When we call a write method inside a register sequence:


temp_reg.write(status, 4);

We provide:

  • status → Output status of transaction
  • data → Value to be written

We do not provide the address explicitly because it is retrieved automatically from the register block address map.

Updating uvm_reg_bus_op Structure

Calling write() updates the internal structure:

  • addr → Taken from address map
  • data → Value passed to write()
  • kind → UVM_WRITE
  • status → Updated after transaction

Example:

  • Register offset = 0
  • write(status, 4)

Then:

  • addr = 0
  • data = 4
  • kind = UVM_WRITE

reg_to_bus() Method

This method converts the register structure into a bus transaction.


function uvm_sequence_item reg_to_bus(const ref uvm_reg_bus_op rw);

  my_transaction tr;
  tr = my_transaction::type_id::create("tr");

  // Convert operation type
  tr.WR = (rw.kind == UVM_WRITE) ? 1 : 0;

  // Map address
  tr.address = rw.addr;

  // Map write data
  if (tr.WR)
    tr.DIN = rw.data;

  return tr;

endfunction
What Happens Here?
  • Create bus transaction object
  • Convert kind → write signal
  • Pass address directly
  • During write, pass data to DIN
  • Return transaction to driver

During read request, only address is driven. Driver does not process DOUT.

Read Operation Flow

When we call:


temp_reg.read(status, data);

The structure gets:

  • addr → from map
  • kind → UVM_READ
  • data → initially 0

The adapter must convert the bus response back into a register transaction.

bus_to_reg() Method

Converts bus response → register structure.


function void bus_to_reg(uvm_sequence_item bus_item,
                         ref uvm_reg_bus_op rw);

  my_transaction tr;

  // Cast to correct type
  assert($cast(tr, bus_item));

  // Update operation type
  rw.kind = (tr.WR) ? UVM_WRITE : UVM_READ;

  // Update address
  rw.addr = tr.address;

  // Capture read data
  rw.data = tr.DOUT;

  // Update status
  rw.status = UVM_IS_OK;

endfunction
What Happens Here?
  • Cast generic sequence item to bus transaction
  • Convert write signal → kind
  • Copy address
  • Capture DOUT into rw.data
  • Set transaction status

Complete Adapter Data Flow

Write Flow:

write() → uvm_reg_bus_op → reg_to_bus() → Driver → DUT

Read Flow:

DUT → Monitor → Predictor → bus_to_reg() → Update Mirror

The adapter ensures both directions remain synchronized.

Example: Native Memory Interface

Suppose DUT has:

  • WR → write enable
  • address → register address
  • DIN → input data
  • DOUT → output data

Adapter simply maps:

  • kind → WR
  • addr → address
  • data (write) → DIN
  • DOUT → rw.data (read)

Whether using native memory signals or protocol-based interfaces (APB/AXI), the principle remains the same: correctly map operation type, address, and data.

Summary
  • write() and read() update uvm_reg_bus_op.
  • reg_to_bus() converts structure → bus transaction.
  • bus_to_reg() converts bus response → structure.
  • Driver applies transactions to DUT.
  • Monitor + predictor update mirror values.
  • Adapter is pure translation logic.