Skip to content

UVM RAL · Chapter 5 · Adapters

AXI-Lite Adapter — Working Example

AXI-Lite is the next step up in realism from APB, and its adapter shows how the same generic RAL operation maps onto a more elaborate protocol. AXI-Lite is a five-channel bus: a write uses three channels for address, data, and response, and a read uses two, each with its own valid and ready handshake. So reg2bus must populate the right channels for the kind of access, carry WSTRB byte enables, and bus2reg must reconstruct status from the response channels. AXI differs from APB in one key way: its response is a two-bit code with two distinct error values, SLVERR and DECERR, alongside OKAY. This page builds a complete AXI-Lite environment, walks a write on the wire, and breaks an adapter whose bus2reg maps SLVERR but leaves DECERR as OK, so accesses to unmapped addresses pass.

Foundation13 min readUVM RALAXI-LiteAdapterBRESPDECERRWaveform

Chapter 5 · Section 5.5 · Adapters

1. Why Should I Learn This?

Most SoC register interfaces are AXI-Lite (or AXI) rather than APB, so the AXI-Lite adapter is the one you will most often write in practice — and its extra structure introduces mistakes APB does not have: mapping to the wrong channel, and, above all, handling the two-code response incompletely. AXI's DECERR is exactly the response a bus returns for an access to an unmapped address — the very error a register test most wants to catch — so an adapter that ignores DECERR passes the accesses that matter most to fail.

Learning the AXI-Lite example gives you the template for the common case, shows how one generic operation maps onto a multi-channel protocol, and — through the DECERR bug — drives home that bus2reg must translate the complete response, including every error code the bus can return. It is the APB example scaled up to the protocol you will actually use.

2. Industry Story — the decode errors that all passed

A team ports a block's register environment from APB to AXI-Lite by writing a new adapter (exactly the one-adapter port RAL promises, 5.1). The reg2bus correctly drives the AW/W channels for writes and AR for reads; the bus2reg reconstructs address, kind, and data. For status, it translates the response: rw.status = (rresp == SLVERR) ? UVM_NOT_OK : UVM_IS_OK.

That looks complete, and register accesses to valid addresses work. But AXI-Lite has two error responses, and the team only handled one. When a refactor introduces an access to an unmapped address, the AXI interconnect answers with DECERR (a decode error), not SLVERR — and the adapter's status logic, checking only for SLVERR, falls through to UVM_IS_OK. So the access to the unmapped address is reported as a success, and the errored (garbage) read data is used as valid. Every decode error in the environment is invisible, which is precisely the class of error — accessing a register that is not there — that a register interface test exists to catch. The bug ships and surfaces as a driver reaching a non-existent register in the field. The post-mortem: AXI encodes multiple error responses (SLVERR and DECERR), and bus2reg must map all of them to UVM_NOT_OK; recognizing only SLVERR lets DECERR — the unmapped-address error — pass as success.

3. Concept — the AXI-Lite environment and its channels

A complete AXI-Lite register environment has these pieces:

  • The five channels. A write uses AW (awaddr, awprot), W (wdata, wstrb), and B (bresp); a read uses AR (araddr, arprot) and R (rdata, rresp). Each channel handshakes with its own valid/ready.
  • The adapter's reg2bus. For a write, populate the AW address and the W data + wstrb (byte enables). For a read, populate the AR address. The item carries the fields for the kind of access.
  • The adapter's bus2reg. Reconstruct address, kind, and data (read data from R's rdata), and status from the response channel for that kind: bresp for a write, rresp for a read. The response is a two-bit code: OKAY (0), EXOKAY (1, exclusive — not used by Lite), SLVERR (2), DECERR (3). Map both SLVERR and DECERR to UVM_NOT_OK.
  • supports_byte_enable = 1 (AXI has wstrb); provides_responses matched to the driver (5.4) — the B and R channels are naturally responses.
  • The binding and predictor, as always.

A write completes across three handshakes — AW, W, then B — unlike APB's single two-phase transfer. Here is that flow on the wire:

valid / ready (per channel)

AXI-Lite register write — AW, W, B handshakes

12 cycles
AXI-Lite register write — AW, W, B handshakesaddress (AW)data (W)response (B)awaddr acceptedawaddr acceptedwdata acceptedwdata acceptedbresp acceptedbresp acceptedaclkawvalidawreadyawaddrXX0x080x08XXXXXXXXwvalidwreadywdataXXXX0x130x13XXXXXXwlastbvalidbreadybrespXXXXXXXXOKOKXXt0t1t2t3t4t5t6t7t8t9t10t11
Figure 1 — an AXI-Lite register write, three handshakes. reg.write(0x08, 0x13) drives the write-address channel (awaddr=0x08 transfers when awvalid and awready are both high), the write-data channel (wdata=0x13 with wstrb, on its handshake), and completes only when the subordinate returns the response on the B channel (bresp, on bvalid and bready). bus2reg reads status from bresp, mapping SLVERR and DECERR to UVM_NOT_OK. Unlike APB's single two-phase transfer, an AXI write is three independent channel handshakes.

4. Mental Model — one operation, many channels, a two-code response

5. Working Example — a complete AXI-Lite adapter

The AXI-Lite adapter routes the generic op onto the channels and reads status from the correct response, mapping both error codes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class reg2axil_adapter extends uvm_reg_adapter;
  `uvm_object_utils(reg2axil_adapter)
  function new(string name = "reg2axil_adapter");
    super.new(name);
    supports_byte_enable = 1;   // AXI WSTRB
    provides_responses   = 1;   // B / R channels are responses
  endfunction
 
  virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    axil_item item = axil_item::type_id::create("item");
    item.kind = rw.kind;
    if (rw.kind == UVM_WRITE) begin
      item.awaddr = rw.addr;    // write-address channel
      item.wdata  = rw.data;    // write-data channel
      item.wstrb  = rw.byte_en; // byte enables
    end else begin
      item.araddr = rw.addr;    // read-address channel
    end
    return item;
  endfunction
 
  virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    axil_item item;  axil_resp_e resp;
    if (!$cast(item, bus_item)) begin `uvm_fatal("ADPT","bad item") return; end
    rw.kind = item.kind;
    if (item.kind == UVM_WRITE) begin
      rw.addr = item.awaddr;  rw.data = item.wdata;  resp = item.bresp;   // write -> BRESP
    end else begin
      rw.addr = item.araddr;  rw.data = item.rdata;  resp = item.rresp;   // read  -> RRESP
    end
    // Map EVERY error code: SLVERR and DECERR both fail; only OKAY/EXOKAY are OK.
    rw.status = (resp == AXI_OKAY || resp == AXI_EXOKAY) ? UVM_IS_OK : UVM_NOT_OK;
  endfunction
endclass

Bound like any adapter, it drives real AXI-Lite — and reports decode errors honestly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg_model.default_map.set_sequencer(axil_agent.sequencer, axil_adapter);
uvm_status_e s;  logic [31:0] got;
cfg.write(s, 32'h0000_0013);   // AW + W + B handshakes; status from BRESP
cfg.read (s, got);             // AR + R handshakes; read data + status from R (RRESP)
// An access to an UNMAPPED address returns DECERR on the response -> s == UVM_NOT_OK.
if (s != UVM_IS_OK) `uvm_error("REG", "AXI-Lite access errored (SLVERR or DECERR)")

Status comes from the response channel matching the access kind, and every non-OKAY code — including DECERR — becomes UVM_NOT_OK. The next section forgets the second code.

6. Debugging Session — the DECERR that was never mapped

1

A bus2reg that maps only SLVERR leaves DECERR as OK, so every access to an unmapped address is reported as a success

UNMAPPED ERROR CODE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// bus2reg translates the response, but only checks for SLVERR:
rw.status = (item.rresp == AXI_SLVERR) ? UVM_NOT_OK : UVM_IS_OK;   // BUG: DECERR falls through to OK
// An access to an UNMAPPED address returns DECERR (decode error), not SLVERR:
cfg.read(s, got);   // rresp == DECERR -> (DECERR != SLVERR) -> status = UVM_IS_OK (wrong)
// The unmapped-address access is reported as SUCCESS; garbage data used as valid.
Symptom

Accesses to valid addresses work, and SLVERR-type slave errors are caught — but accesses to unmapped addresses, which the AXI interconnect answers with DECERR, are reported as UVM_IS_OK. So the exact class of error a register interface test most wants to catch — reaching a register that is not there — is invisible, and the errored access's garbage data is used as valid. A test that deliberately probes an unmapped address to confirm it errors will pass (seeing OK), which is the opposite of correct. It looks like the interconnect is not flagging decode errors, when in fact it is — the adapter just does not recognize the code.

Root Cause

AXI encodes its response as a two-bit code with two distinct error values — SLVERR (slave error) and DECERR (decode error) — alongside OKAY, but the adapter's bus2reg only recognizes SLVERR as a failure and treats everything else, including DECERR, as success. DECERR is the response for an access to an unmapped address, so every such access falls through the SLVERR-only check to UVM_IS_OK. The codec is otherwise correct and slave errors are caught; the fault is an incomplete response translation that omits one of the two error codes — and the omitted one happens to be the decode error that register tests specifically target. The interconnect is behaving correctly; the adapter simply does not map the whole response.

Fix

Map every non-OKAY response code to UVM_NOT_OK, not just SLVERR: rw.status = (resp == AXI_OKAY || resp == AXI_EXOKAY) ? UVM_IS_OK : UVM_NOT_OK; (as in Section 5), so both SLVERR and DECERR become failures. Test an access to an unmapped address and confirm it now reports UVM_NOT_OK. The rule the bug teaches: bus2reg must translate the complete bus response — every error code the bus can return — into status; AXI has both SLVERR and DECERR, and mapping only one lets the other pass as success, with DECERR (the unmapped-address error) being exactly the one register tests care about. When decode errors on bad addresses pass, check that bus2reg maps all the response codes, not just the obvious one.

7. Common Mistakes

  • Mapping only SLVERR and forgetting DECERR. Accesses to unmapped addresses pass — the error register tests most want to catch.
  • Reading status from the wrong response channel. Write status is bresp, read status is rresp; mixing them misreports.
  • Populating the wrong channels in reg2bus. A write needs AW + W; a read needs AR — filling the wrong channel for the kind fails the access.
  • Reading write data on a read. Read data comes back on R's rdata; reconstruct data by kind.
  • Wrong provides_responses or supports_byte_enable. As in APB (5.4, 3.3), match the driver and the wstrb capability.

8. Industry Best Practices

  • Map the complete response in bus2reg. Treat anything not OKAY/EXOKAY as UVM_NOT_OKSLVERR and DECERR both fail.
  • Read status from the response channel matching the kind. bresp for writes, rresp for reads.
  • Route by kind in reg2bus. Write → AW + W (+ wstrb); read → AR.
  • Test an unmapped-address access. Confirm it returns UVM_NOT_OK (DECERR) — the decode-error path.
  • Reuse one proven AXI-Lite adapter. The common SoC register bus; write it once, integration-test it, reuse it.

9. Interview / Review Questions

10. Key Takeaways

  • AXI-Lite is a five-channel bus: a write uses AW (address), W (data + wstrb), and B (response); a read uses AR (address) and R (data + response).
  • reg2bus routes by kind — write → AW + W, read → AR; bus2reg reads status from the response channel matching the kind (bresp for writes, rresp for reads) and read data from R's rdata.
  • AXI's response is a two-bit code — map every non-OKAY value (SLVERR and DECERR) to UVM_NOT_OK; recognizing only one lets the other pass.
  • DECERR is the unmapped-address error — the one register tests most want to catch — so omitting it silently reports accesses to non-existent registers as success.
  • A write is three handshakes (AW, W, B) versus APB's single two-phase transfer; otherwise the adapter rules are the same — inverse codec, complete status, supports_byte_enable/provides_responses matched to the bus and driver.