Part 8 · Checking & Coverage · Intermediate

Transaction-Level Reference Models

Memory models, predict() functions, and protocol response generation without cycle timing.

Protocol and memory state without cycle timing

Transaction-level models maintain logical state — memory arrays, register shadows, protocol response codes — and produce expected bus transactions in zero simulation time. They model what the DUT should return, not when cycles elapse. Use them when the verification plan checks data integrity, memory contents, and response semantics at the transaction boundary.

diagram
[CHECK] transaction-level model scope

  MODELS:                    DOES NOT MODEL:
  • Memory read/write data    • Pipeline latency (N cycles)
  • Response codes (OKAY)       • Credit/debit counters
  • Byte-enable semantics       • Arbitration priority timing
  • Ordering at txn boundary    • Clock-domain crossing delays
diagram
[UVM] memory ref model in check path

  [UVM] dut_mon.ap ──► mem_ref_model.req_imp.write(req)
                              │
                              │  update mem[addr] on write
                              │  lookup mem[addr] on read
                              │
                              ▼
                        mem_ref_model.ap.write(exp)
                              │
                              ▼
                        [UVM] scb.exp_imp

Sparse memory ref model

SRAM controllers, cache backends, and register files often need a backing memory model. A sparse associative array (bit [31:0] mem [bit [31:0]]) avoids allocating full address space. The model updates state on write, returns stored data or spec default on read.

systemverilog
class mem_ref_model extends uvm_component;
  `uvm_component_utils(mem_ref_model)
  bit [31:0] mem [bit [31:0]];
  uvm_analysis_imp #(bus_txn, mem_ref_model) req_imp;
  uvm_analysis_port #(bus_txn) ap;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    req_imp = new("req_imp", this);
    ap      = new("ap", this);
  endfunction

  function void write(bus_txn req);
    bus_txn exp = bus_txn::type_id::create("exp");
    exp.copy(req);
    exp.kind = RESPONSE;

    if (req.write) begin
      if (req.strb == 4'hF)
        mem[req.addr] = req.data;
      else
        mem[req.addr] = apply_strobe(mem.exists(req.addr) ? mem[req.addr] : 32'h0,
                                     req.data, req.strb);
      exp.resp = addr_mapped(req.addr) ? OKAY : SLVERR;
    end else begin
      if (!addr_mapped(req.addr)) begin
        exp.resp = SLVERR;
        exp.data = 32'h0;
      end else begin
        exp.data = mem.exists(req.addr) ? mem[req.addr] : 32'h0;
        exp.resp = OKAY;
      end
    end
    ap.write(exp);
  endfunction

  function bit addr_mapped(bit [31:0] addr);
    return (addr >= 32'h0000_0000 && addr < 32'h0010_0000);
  endfunction
endclass
  • Sparse array — only stores written addresses; unmapped reads return spec default.

  • Byte strobe handling — partial writes merge into existing word per spec.

  • Address map function encodes spec decode rules, not RTL decoder logic.

  • Response code (OKAY/SLVERR) from spec — document assumption in verification plan.

  • Clone req into exp and flip kind to RESPONSE for scoreboard pairing.


Standalone predict() function

For simpler blocks, a standalone predict() function outside the component hierarchy suffices. The component's write() delegates to it — same logic, two entry points for unit testing.

systemverilog
// Shared state and predict — testable without UVM
bit [31:0] mem [bit [31:0]];

function bus_txn predict(bus_txn req);
  bus_txn exp = bus_txn::type_id::create("exp");
  exp.copy(req);
  exp.kind = RESPONSE;
  if (req.write) begin
    mem[req.addr] = req.data;
    exp.resp = OKAY;
  end else begin
    exp.data = mem.exists(req.addr) ? mem[req.addr] : 32'h0;
    exp.resp = OKAY;
  end
  return exp;
endfunction

// Component wrapper
function void write(bus_txn req);
  ap.write(predict(req));
endfunction

Protocol outcome examples

  • APB slave — OKAY on mapped addr, SLVERR on decode miss; PREADY always 1 at transaction level.

  • AXI read — RDATA from memory model, RRESP=OKAY; ID preserved for out-of-order scoreboard.

  • DMA descriptor fetch — model returns linked-list next pointer from memory state.

diagram
[CHECK] transaction-level vs cycle-approximate boundary

  Transaction-level:  'read addr 0x100 returns 0xDEADBEEF with OKAY'
                    (immediate, no wait states)

  Cycle-approximate: 'read addr 0x100 returns 0xDEADBEEF after 3 cycles
                      when read credit available'
                    (requires credit model — see fidelity-matrix lesson)

Key takeaways

  • Transaction models: memory + protocol outcomes, no cycle delays.

  • Sparse associative arrays for memory; document unmapped-address behavior.

  • predict() as package function enables unit tests before env wiring.

Common pitfalls

  • Full memory array for 32-bit address space — simulation memory blow-up; use sparse map.

  • Ignoring byte strobes — false mismatches on partial-word writes.

  • Modeling wait states at transaction level — belongs in cycle-approximate fidelity.