Skip to content

UVM RAL · Chapter 5 · Adapters

APB Adapter — Working Example

This page assembles a complete APB register environment end to end so you can see every piece in place and watch a register access travel all the way to the APB wire. APB is the ideal first worked example: a simple two-phase bus with a select and enable handshake, a ready signal that lets a slave insert wait states, optional byte strobes for byte-enabled writes, and a single error response. You will see the APB sequence item, the full adapter that translates the error response into status and the strobes into byte enables, the responses flag matched to how the driver returns results, the binding to the agent, and a waveform of a write on the bus. It ends with the responses-flag mismatch bug, where the driver returns results the adapter never reads, so reads come back with stale request data.

Foundation13 min readUVM RALAPBAdapterprovides_responsesPSLVERRWaveform

Chapter 5 · Section 5.4 · Adapters

1. Why Should I Learn This?

An adapter is not correct in isolation — it is correct in an environment, wired to a specific bus agent whose driver returns results in a specific way. The pieces that the codec pages could not show — how the adapter plugs into the map and agent, and how provides_responses must match the driver's behaviour — are exactly where a theoretically-correct adapter fails to work in practice. Seeing a complete APB environment, and one integration bug, is what connects the methods you now understand to a running register test.

Learning the full APB example gives you a template you can copy for the most common register bus, and — through the provides_responses bug — the single most important integration fact: the adapter and the driver must agree on where results come from, or reads return the wrong data even when the codec is flawless. It is the theory of 5.1–5.3 made into a working environment.

2. Industry Story — the reads that returned the request

A team brings up an APB register environment. The adapter's reg2bus and bus2reg are correct — verified by a round-trip test — and writes work perfectly. But reads return wrong values: instead of the data the DUT drove on prdata, a read returns something that looks like the address or the write data of the request.

The cause is a mismatch between the adapter and the APB driver about how results come back. The APB driver, like many, completes each transfer by putting the result — prdata on a read, pslverr — into a response item it returns to the sequence (a response-based driver). But the adapter was written with provides_responses = 0, telling RAL that status and read data live on the request item the adapter originally built. So RAL reads the result from the request item, which the driver never back-filled with prdata, and gets the stale request contents instead of the DUT's read data. Writes were unaffected because a write carries its data outbound; only reads, whose data comes back, were corrupted. The bug is invisible to a codec round-trip test (which never involves the real driver) and appears only when the adapter meets the actual APB agent. The fix is one flag: set provides_responses = 1 to match the response-based driver. The lesson: the adapter and the bus driver must agree on where results come from — provides_responses tells RAL whether to read status and read-data from a returned response item or from the request item — and a mismatch corrupts reads even when the codec is perfect.

3. Concept — the APB environment, piece by piece

A complete APB register environment has these pieces working together:

  • The APB sequence item. Carries paddr, pwrite, pwdata, prdata, pstrb, and pslverr — the fields the driver drives and samples.
  • The adapter. reg2bus sets paddr/pwrite/pwdata/pstrb from the generic op; bus2reg reconstructs the op and translates pslverr into status and picks prdata/pwdata for the data by kind. supports_byte_enable = 1 (APB4 has pstrb); provides_responses set to match the driver.
  • The APB driver's result convention. Either it returns a response item per transfer (response-based → provides_responses = 1), or it back-fills the request item with prdata/pslverr (request-based → provides_responses = 0). The adapter must match.
  • The binding. In connect_phase, map.set_sequencer(apb_agent.sequencer, apb_adapter), and the predictor connected to the APB monitor (Chapter 6).
  • The APB protocol on the wire. A write is a two-phase transfer: a setup phase (psel=1, penable=0, address/data/control driven) and an access phase (penable=1), completing when pready=1; the slave can hold pready low to insert wait states, and assert pslverr for an error.

Here is a register write on the APB wire — the two-phase handshake the adapter's transaction becomes:

psel / penable / pready

APB register write — setup, access, pready

8 cycles
APB register write — setup, access, preadysetup phaseaccess phasepready=1 — data sampledpready=1 — data sampledpclkpselpenablepwritepaddrXX0x080x080x080x08XXpwdataXX0x130x130x130x13XXpreadyt0t1t2t3t4t5t6t7
Figure 1 — a register write on the APB bus. reg.write(0x08, 0x13) becomes an APB transfer: the SETUP phase asserts psel with the address (paddr=0x08) and write data (pwdata=0x13) and pwrite=1; the ACCESS phase asserts penable; the transfer completes when the slave drives pready=1, sampling the data (a slave can hold pready low to insert wait states). A pslverr here would become UVM_NOT_OK via bus2reg. This is what the adapter's reg2bus item turns into on the wire.

4. Mental Model — the adapter is one piece of a contract with the driver

5. Working Example — a complete APB register environment

The APB adapter, with status, byte enables, and provides_responses matched to a response-based driver:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class reg2apb_adapter extends uvm_reg_adapter;
  `uvm_object_utils(reg2apb_adapter)
  function new(string name = "reg2apb_adapter");
    super.new(name);
    supports_byte_enable = 1;   // APB4 PSTRB
    provides_responses   = 1;   // MATCH the driver: it returns a response item per transfer
  endfunction
 
  virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    apb_item item = apb_item::type_id::create("item");
    item.paddr  = rw.addr;                    // APB is byte-addressed -> use as-is
    item.pwrite = (rw.kind == UVM_WRITE);
    item.pwdata = rw.data;
    item.pstrb  = rw.byte_en;
    return item;
  endfunction
 
  virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    apb_item item;
    if (!$cast(item, bus_item)) begin `uvm_fatal("ADPT","bad item") return; end
    rw.addr   = item.paddr;
    rw.kind   = item.pwrite ? UVM_WRITE : UVM_READ;
    rw.data   = item.pwrite ? item.pwdata : item.prdata;   // read data on a read
    rw.status = item.pslverr ? UVM_NOT_OK : UVM_IS_OK;      // translate the error response
  endfunction
endclass

The environment wires the model to the APB agent, and a register test runs over real APB:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// connect_phase: bind the map to the APB agent via the adapter; connect the predictor.
reg_model.default_map.set_sequencer(apb_agent.sequencer, apb_adapter);
apb_predictor.map = reg_model.default_map;  apb_predictor.adapter = apb_adapter;
apb_agent.monitor.ap.connect(apb_predictor.bus_in);
 
// A register access now drives a real APB transfer:
uvm_status_e s;  logic [31:0] got;
cfg.write(s, 32'h0000_0013);   // APB setup+access, pready handshake; mirror synced
cfg.read (s, got);             // APB read; bus2reg picks prdata; read() auto-checks vs mirror
if (s != UVM_IS_OK) `uvm_error("REG", "APB access returned PSLVERR")

Every piece is present: the item, the adapter (codec + status + byte enables), the provides_responses that matches the driver, the binding, and the predictor. The read returns the DUT's prdata because provides_responses matches — the next section is what happens when it does not.

6. Debugging Session — the provides_responses mismatch

1

An adapter whose provides_responses disagrees with the driver reads results from the wrong place, so reads return stale request data

PROVIDES_RESPONSES MISMATCH
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The APB driver is RESPONSE-based: it returns a response item carrying prdata/pslverr.
// But the adapter is configured as if results live on the REQUEST item:
provides_responses = 0;   // BUG: driver provides responses, but adapter set to request-based
// Writes work (data goes out with the request). But a READ:
cfg.read(s, got);
//   The DUT drives prdata into the DRIVER'S RESPONSE item, which the adapter never reads;
//   RAL reads 'result' from the request item, which was never back-filled -> got = stale data.
Symptom

Writes work, but reads return wrong values — often the address or the write data of the request rather than the prdata the DUT drove. read()'s auto-check then mismatches against the mirror (or, if the stale value coincidentally matches, passes and hides the problem). The codec round-trip test passes (it never involves the driver), so the adapter looks correct in isolation; the failure appears only when reads run against the real APB agent, which makes it look like a DUT read-data bug rather than an adapter/driver-agreement bug.

Root Cause

The adapter's provides_responses does not match the APB driver's result convention. The driver is response-based — it completes each transfer by returning a response item carrying prdata and pslverr — but the adapter is configured provides_responses = 0, telling RAL that status and read data live on the request item the adapter built. So RAL reads the read result from the request item, which the driver never populated with prdata, and gets the stale request contents. Writes are unaffected because their data travels outbound on the request; only reads, whose data must come back, expose the mismatch. The codec (reg2bus/bus2reg) is perfectly correct — the fault is the agreement between adapter and driver about where results appear.

Fix

Set provides_responses to match the driver: provides_responses = 1 for a response-based APB driver (as in Section 5), so RAL reads status and read data from the returned response item. Confirm it by testing reads against the real agent, not just a codec round-trip — the round-trip cannot catch this because it never runs the driver. The rule the bug teaches: a correct codec is necessary but not sufficient; the adapter and the driver must agree on where results come from, and provides_responses encodes that agreement — a mismatch stales reads even when reg2bus/bus2reg are flawless. When writes work but reads return stale data against the real bus, check provides_responses against the driver.

7. Common Mistakes

  • provides_responses not matching the driver. Reads return stale request data (response-based driver, adapter set to request-based) — writes still work, hiding it.
  • Assuming a correct codec is a complete adapter. The adapter also has a result-flow contract with the driver.
  • Testing only the codec round-trip. It never runs the driver, so it cannot catch a provides_responses mismatch — test reads against the real agent.
  • Forgetting pstrb/supports_byte_enable. A byte-enabled APB4 write needs the strobe set and the flag true (3.3).
  • Not translating pslverr into status. Bus errors reported as OK (5.2).

8. Industry Best Practices

  • Match provides_responses to the driver's result convention. Response-based → 1; request-back-fill → 0; verify against the actual driver.
  • Test reads against the real agent. Codec round-trips prove the methods; only a real read proves the result-flow agreement.
  • Set supports_byte_enable = 1 for APB4 with PSTRB. So sub-word writes and individually_accessible work (3.3).
  • Translate pslverr into status in bus2reg. Honest error reporting (5.2).
  • Reuse one proven APB adapter. Write it once, integration-test it with the APB agent, and reuse across register environments on APB.

9. Interview / Review Questions

10. Key Takeaways

  • A complete APB register environment is the APB item + the adapter (codec + pslverr status + pstrb byte enables) + provides_responses matched to the driver + the map binding + the predictor.
  • On the wire, a register write is a two-phase APB transfer: a setup phase (psel, address/data/control), an access phase (penable), completing on the pready handshake (which can insert wait states), with pslverr for errors.
  • provides_responses encodes the adapter-driver agreement about where results appear — response-based driver → 1, request-back-fill → 0.
  • A provides_responses mismatch stales reads while writes still work (read data comes back; write data goes out), and it survives a codec round-trip test — so test reads against the real agent.
  • A correct codec is necessary but not sufficient: a working adapter is a correct reg2bus/bus2reg and a provides_responses that matches the driver.