Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
    • RAL Overview
    • Register Access
      • The Access API: write, read, set, get, update, mirror, predict, peek, poke
      • The Adapter Contract: reg2bus, bus2reg and provides_responses
      • Prediction: Auto, Explicit and Passive
      • Field Access Policies and Callbacks
      • Volatile Fields and Mirror Checks
      • Backdoor Access: HDL Paths and User Backdoors
      • User-Defined Frontdoors: Indirect and Paged Registers
    • Registers in Verification
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Register Access
  1. Topics
  2. UVM Verification
  3. UVM Register Abstraction Layer (RAL)
  4. Register Access
  5. The Adapter Contract: reg2bus, bus2reg and provides_responses

Part 9 · Register Model (RAL) · Intermediate

The Adapter Contract: reg2bus, bus2reg and provides_responses

The bench's APB adapter line by line: provides_responses, byte enables, get_item() and extensions, PSLVERR as UVM_NOT_OK and what happens to the mirror, and the cast failure policy.

On this page

    A team swaps their in-house APB agent for a purchased one. Register writes still work, but every register read now returns 0, even from registers whose reset value is not 0. Nothing in the register model changed. The new driver sends read data back in a separate response object, and the adapter was still configured for a driver that writes the data into the request. One bit in the adapter's constructor decided which object the model read the data from.

    The idea: a translator with a written contract

    The register map speaks in abstract operations: write 0xCAFEF00D to address 0x0C, 32 bits. The APB agent speaks in apb_items. The adapter is the interpreter between them, and like a court interpreter it works under a contract: it must translate both ways without adding or dropping meaning, and it must say in advance how the other side answers. The abstract operation is a small struct, uvm_reg_bus_op, with six members: kind (UVM_READ or UVM_WRITE), addr, data, n_bits, byte_en and status.

    The contract has two functions and two flags. reg2bus() turns an operation into a bus item. bus2reg() turns a completed bus item back into an operation, filling in read data and status. provides_responses tells the map whether the completed item is the request itself or a separate response. supports_byte_enable tells the model whether a single field can be written without touching its neighbours.

    The picture: one frontdoor read

    Sequence diagram: the map calls reg2bus, sends the item through the sequencer to the driver, then calls bus2reg either on a separate response (provides_responses=1) or on the request itself (provides_responses=0), and returns data and status to the test.
    A frontdoor read through the map. The flag decides which item bus2reg reads: the request the driver filled in, or a response the driver sent separately.

    Read the alt box carefully: the map calls bus2reg() on exactly one object. If the flag points at the wrong one, the model reads stale data or waits for an object that never comes.

    Build it: the bench adapter, line by line

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // snippet: from the course DMA bench
    // Translates between register operations and APB items. The APB driver writes
    // read data and PSLVERR back into the request item, so there is no separate
    // response: provides_responses = 0.
    class apb_reg_adapter extends uvm_reg_adapter;
      `uvm_object_utils(apb_reg_adapter)
    
      function new(string name = "apb_reg_adapter");
        super.new(name);
        supports_byte_enable = 0;
        provides_responses   = 0;
      endfunction
    
      virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
        apb_item it = apb_item::type_id::create("it");
        it.addr  = rw.addr;
        it.write = (rw.kind == UVM_WRITE) || (rw.kind == UVM_BURST_WRITE);
        it.data  = rw.data;
        return it;
      endfunction
    
      virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
        apb_item it;
        if (!$cast(it, bus_item))
          `uvm_fatal("NOT_APB", "bus2reg got an item that is not an apb_item")
        rw.kind   = it.write ? UVM_WRITE : UVM_READ;
        rw.addr   = it.addr;
        rw.data   = it.write ? it.data : it.rdata;
        rw.status = it.slverr ? UVM_NOT_OK : UVM_IS_OK;   // a bus error is a status, not a fatal
      endfunction
    endclass

    Line

    What it promises

    Why it is right for this bench

    supports_byte_enable = 0

    No bus access can touch only some byte lanes.

    APB3 has no PSTRB. Field writes become whole-register writes (lesson 1).

    provides_responses = 0

    Read data and status come back inside the request.

    apb_driver writes rdata and slverr into req and calls item_done() with no argument.

    reg2bus: it.addr = rw.addr

    The map's address goes on the bus unchanged.

    One map at base 0; the map has already added the register offset.

    reg2bus: it.data = rw.data

    Write data passes through.

    For reads the driver ignores data.

    bus2reg: $cast or uvm_fatal

    Only APB items are translated.

    A wrong item type is a wiring bug in the testbench, not a DUT behaviour.

    bus2reg: rw.data = it.write ? it.data : it.rdata

    Reads return what the slave drove.

    The predictor also calls bus2reg, on monitor items, so write data must come back too.

    bus2reg: slverr ? UVM_NOT_OK : UVM_IS_OK

    A bus error is reported as a status.

    The test decides what an error means; the adapter only reports it.

    Notice that bus2reg() serves two callers. The map calls it on the driver's completed request, and the explicit predictor calls it on every item the monitor publishes. That is why it must decode writes as well as reads: for the predictor, the write data is the whole point.

    provides_responses: when a separate response exists

    Some drivers answer with a second object: item_done(rsp) or put_response(rsp), with rsp.set_id_info(req). Pipelined drivers and most purchased VIP work this way. For those, set provides_responses = 1, and the map calls get_base_response() after finish_item() returns, matching on the transaction ID. Get the flag wrong and you see one of two failures:

    • Flag 1, driver never responds: the register task blocks forever in get_base_response(). It looks like a hang in the test, not in the adapter.

    • Flag 0, driver does respond: bus2reg() reads the request, whose rdata the driver never filled, so reads return 0 or old data. Worse, the unread responses pile up in the sequence's response queue (8 deep by default). UVM 1.2 reports an overflow error on the ninth; the IEEE 1800.2-2020 reference library drops them silently unless you turn the report on. That is the opening story.

    Match the flag to the driver, and check it the first time you integrate any agent: one frontdoor read of a register with a known non-zero reset value is enough.

    supports_byte_enable and byte_en

    A field write can stay a field write only if the bus can write a subset of bytes. With supports_byte_enable = 1, the map fills rw.byte_en with one bit per byte lane, and the adapter must carry it to the bus. On APB4 that is PSTRB. The library still falls back to a whole-register write if the field is not individually_accessible, if it shares a byte lane with another field, or if the register has a user frontdoor. The bench's APB3 item has no strobe, so this is a sketch:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet: an APB4 adapter that carries byte enables to PSTRB
    virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
      apb4_item it = apb4_item::type_id::create("it");
      it.addr  = rw.addr;
      it.write = (rw.kind inside {UVM_WRITE, UVM_BURST_WRITE});   // bursts arrive as UVM_BURST_WRITE
      it.data  = rw.data;
      it.strb  = it.write ? rw.byte_en[3:0] : 4'h0;                 // PSTRB is low for reads
      return it;
    endfunction

    In bus2reg(), copy the strobe back into rw.byte_en. The predictor passes it to do_predict, so a monitored partial write updates only the bytes that were really written. The predictor sets byte_en to all ones before calling bus2reg(), so an adapter that ignores it is treated as full-word.

    get_item(): the abstract access behind the bus op

    reg2bus() receives a flat struct, but sometimes the bus item needs more: a protection level, a secure bit, a master ID. Every register task takes an extension argument (any uvm_object), and inside reg2bus() the adapter can call get_item() to reach the whole uvm_reg_item for the current access: the register (element), the extension, the parent sequence, the priority. The map sets that item just before calling reg2bus() and clears it just after, so get_item() is valid only there. The adapter also has a parent_sequence member: if you set it, the map clones that sequence to start the bus items under it.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // snippet: verified in a scratch run with the DMA bench
    class apb_tag_ext extends uvm_object;
      `uvm_object_utils(apb_tag_ext)
      bit [2:0] pprot;
      function new(string name = "apb_tag_ext"); super.new(name); endfunction
    endclass
    
    class apb_reg_adapter_ext extends apb_reg_adapter;
      `uvm_object_utils(apb_reg_adapter_ext)
      function new(string name = "apb_reg_adapter_ext"); super.new(name); endfunction
      virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
        uvm_reg_item item = get_item();          // the abstract access behind this bus op
        apb_tag_ext  ext;
        reg2bus = super.reg2bus(rw);
        if (item != null && item.extension != null && $cast(ext, item.extension))
          `uvm_info("EXT", $sformatf("addr %02h: %s carries pprot=%0d", rw.addr,
                    item.element.get_full_name(), ext.pprot), UVM_LOW)
      endfunction
    endclass
    
    // In the test: a factory override, then an access that carries the extension.
    //   apb_reg_adapter::type_id::set_type_override(apb_reg_adapter_ext::get_type());
    //   ext.pprot = 3'b010;
    //   renv.regmodel.key.write(st, 32'h1234_5678, .extension(ext));

    Status: PSLVERR, UVM_NOT_OK and the mirror

    The adapter turns PSLVERR into UVM_NOT_OK. What the model then does with the mirror depends on the library version. The stock uvm_reg_predictor passes every observed transfer to uvm_reg::do_predict, skipping only its read check on UVM_NOT_OK. In IEEE 1800.2, do_predict itself then skips the update and warns PREDICT_NOK, whichever path called it: the explicit predictor, auto-prediction, peek() or poke(). In UVM 1.2, do_predict has no status check, so the mirror is updated even after a slave error. To keep the mirror unchanged there, filter in the predictor's pre_predict() or in a derived predictor. The DMA answers PSLVERR for any unmapped address, so a scratch run adds a model register at 0x14 and accesses it:

    Diagram
    scratch run: +UVM_TESTNAME=s_slverr_test (a model register at 0x14, where the DMA has none)
    UVM_WARNING reg/uvm_reg.svh(1664) @ 75000: reporter [PREDICT_NOK] status UVM_NOT_OK; skip prediction.
    UVM_INFO sacc_tb.sv(183) @ 76000: uvm_test_top [NOK] write: status=UVM_NOT_OK desired=a5a5a5a5 mirrored=00000000
    UVM_WARNING reg/uvm_reg.svh(1664) @ 115000: reporter [PREDICT_NOK] status UVM_NOT_OK; skip prediction.
    UVM_INFO sacc_tb.sv(187) @ 116000: uvm_test_top [NOK] read: status=UVM_NOT_OK value=00000000 mirrored=00000000

    Both accesses return UVM_NOT_OK and the mirror stays 0, with one PREDICT_NOK warning per transfer from the explicit predictor. The desired value, though, already holds A5A5A5A5: write() sets it before the bus access, so needs_update() is now true and a later update() would retry the write.

    A cast failure: fatal or status?

    If bus2reg() receives the wrong item type, the predictor is connected to the wrong monitor or the map to the wrong sequencer. That is a testbench wiring bug, so the bench stops with uvm_fatal. Returning UVM_NOT_OK instead would disguise the wiring bug as a DUT bus error, and a test that expects bus errors could pass on it. The library's own uvm_reg_tlm_adapter reports uvm_error and returns; either is defensible, but pick one policy for every adapter in the bench and never map a wiring bug to a DUT status.

    Watch it run

    With the extended adapter swapped in by a factory override, a KEY write carries pprot=2 and a LEN write carries no extension:

    Diagram
    scratch run: +UVM_TESTNAME=s_ext_test (with the null check)
    UVM_INFO sacc_tb.sv(462) @ 0: reporter [EXT] addr 0c: regmodel.key carries pprot=2
    UVM_INFO sacc_tb.sv(480) @ 115000: uvm_test_top [EXT] adapter type: apb_reg_adapter_ext
    UVM_ERROR :    0
    UVM_FATAL :    0

    The first line is printed at time 0, before any bus activity: reg2bus() runs when the map builds the item, not when the driver drives it. The LEN write printed nothing, which is correct.

    What breaks: $cast succeeds on null

    The first version of that adapter checked only $cast(ext, item.extension). The KEY write worked. The LEN write, which passed no extension, crashed the simulation:

    Diagram
    scratch run: +UVM_TESTNAME=s_ext_test (first version)
    UVM_INFO sacc_tb.sv(462) @ 0: reporter [EXT] addr 0c: regmodel.key carries pprot=2
    %Error: sacc_tb.sv:462: Null pointer dereferenced
    Aborting...

    SystemVerilog's $cast succeeds when the source handle is null: it assigns null and returns 1. So the condition was true, ext was null, and ext.pprot dereferenced it. Most accesses in a real bench carry no extension, so the bug appears on the first ordinary register access after someone adds the feature. The fix is the explicit item.extension != null in the listing above. The same rule applies anywhere you $cast an optional object: test for null first.

    Interview

    "What does `provides_responses` do in a register adapter?"

    A weak answer: "It says whether the bus returns data."

    A strong answer: "It tells the map which object to pass to bus2reg() after finish_item() returns. With 0, the map decodes the request, so the driver must write read data and status into it and call item_done() with no argument. With 1, the map calls get_base_response() and decodes the separate response. A mismatch either hangs the register task waiting for a response that never comes, or returns stale read data while responses overflow the queue. I check it at integration with one read of a register whose reset value is non-zero."

    Follow-ups: Why must `bus2reg()` decode writes? (The explicit predictor calls it on monitored writes.) What happens to the mirror on PSLVERR? (In IEEE 1800.2, do_predict skips the update and warns PREDICT_NOK; in UVM 1.2 the mirror is updated unless you filter in pre_predict() or a derived predictor.)

    Check yourself A driver calls item_done(rsp) with a cloned response holding rdata, and the adapter has provides_responses = 0. The first eight register reads return 0. What happens on the ninth, and how do you fix it?

    Each item_done(rsp) queues a response the sequence never takes. On the ninth, UVM 1.2 reports a response queue overflow error; the IEEE 1800.2-2020 reference library drops it silently by default. The reads return 0 because bus2reg() decodes the request. Set provides_responses = 1, or change the driver to fill the request and call item_done() with no argument.

    Check yourself Your APB4 adapter sets supports_byte_enable = 1, but a write to one 4-bit field still writes the whole register. Name two reasons the library might fall back.

    Any of: the field was configured with individually_accessible = 0; the field shares a byte lane with another field, so a byte strobe would also hit the neighbour; the register has a user-defined frontdoor. The library prints a RegModel warning naming the reason.

    Check yourself The adapter's bus2reg() returns UVM_NOT_OK when the cast fails, instead of a fatal. Why can that hide a bug?

    The cast fails only when the testbench is wired wrong (the predictor on the wrong monitor, or the map on the wrong sequencer). Reported as UVM_NOT_OK, it looks like a DUT bus error. A test that expects or tolerates bus errors would then pass while no register access was ever translated correctly.

    Next: the adapter told the model what happened on the bus. Prediction: Auto, Explicit and Passive is about when the model hears it, and why a correct DMA failed a bit-bash on 94 bits.

    Related topics

    • Up: Register Access

    • get_next_item and item_done

    • Responses and pipelined drivers

    Previous

    155 / 280

    Next: Prediction: Auto, Explicit and Passive

    Up next

    Up nextPrediction: Auto, Explicit and Passive

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Field Access Policies and Callbacks
    • Volatile Fields and Mirror Checks
    • Backdoor Access: HDL Paths and User Backdoors