Predict from observed bus traffic

Keep the register mirror right when other code writes the bus, by feeding a monitor's transfers to a uvm_reg_predictor and turning auto prediction off.

Lesson
U13.3
Time
30 min
Runs on
Verilator with UVM
Check
5 questions

A boot sequence writes CTRL straight on the APB bus, as the chip's firmware will, without calling the register model. Later, regs.ctrl.mirror(status, UVM_CHECK) reports a mismatch: the model expected 0x01 and the design returned 0xa5. The design is right. With auto prediction, the map updates the mirror only for accesses it makes itself, so the boot write never reached the model.

TimeBus activityDesign CTRLMirror (auto prediction)
0 nsModel reset0101
25 nsBoot sequence writes a5a501: the map did not make this access
Latermirror(UVM_CHECK) reads a5a5Error: expected 01

A write the register model never saw leaves its mirror stale.

Three ways to keep the mirror current

The mirror is only useful if it follows the hardware. UVM offers three ways to update it after a transfer. They differ in what the model learns from.

ModeHow it is set upUpdates the mirror fromSees other masters
Auto predictionmap.set_auto_predict(1)Each access the map itself makesNo
Explicit predictionMonitor to uvm_reg_predictor, auto prediction offEvery transfer the monitor observesYes
PassivePredictor only; no sequencer behind the mapEvery observed transfer; the model never drivesYes

Prediction modes. Explicit prediction is the usual choice once anything else can use the bus.

Auto prediction is simple, and U13.2 used it. But real buses have other masters: a boot sequence, a DMA engine, a processor model, or a test that drives raw bus items to provoke errors. Explicit prediction lets the model learn from what actually happened on the pins.

The predictor component

uvm_reg_predictor #(apb_item) is a component with an analysis input, bus_in, which is a uvm_analysis_imp. Connect the bus monitor's analysis port to it, and it receives every transfer the monitor sees, whoever started it.

The boot write reaches the mirror through the monitorArrows are items and calls for the boot write in the example. The register model never makes this access; it learns about it from the pins.
The boot write reaches the mirror through the monitorArrows are items and calls for the boot write in the example. The register model never makes this access; it learns about it from the pins. component boot_writesequence Raw apb_item, noregister call component APB sequencer +driver Write of a5 completesat 25 ns design incr_csr.ctrl a5 after the 25 nsedge component apb_monitor Samples the completedtransfer at 25 ns component uvm_reg_predictor bus2reg, then the mapfinds CTRL at address0 model regs.ctrl Mirror and desiredbecome a5 apb_itemAPB writepins sampledap.write(t)predict a5 (writepolicy)
  1. Start a raw item

    The boot sequence sends a raw item to the sequencer. The map took no part.

    More context

    No register call, no uvm_reg_bus_op, no adapter: the model does not know this access exists.

    Follow the calls, values and waits
    1. data boot apb_item: write 0 <- a5: boot_write sequence → APB sequencer + driver
    2. Waiting: boot_write sequence — in finish_item until item_done
  2. On the bus25 ns (illustrative)

    The design takes a5 at the 25 ns edge.

    More context

    The log line "25 ns APB write addr=0 data=a5". The design holds a5; the mirror does not know it yet.

    Follow the calls, values and waits
    1. data pwdata = a5 at address 0: APB sequencer + driver → incr_csr.ctrl
  3. Observed25 ns (illustrative)

    The monitor sees psel, penable and pready at the 25 ns edge and writes the transfer to its analysis port.

    More context

    The monitor builds its own item from the pins. It does not see the boot sequence's object.

    Follow the calls, values and waits
    1. data pwdata = a5 at address 0: incr_csr.ctrl → apb_monitor
    2. data monitor item t: write 0, a5: apb_monitor → uvm_reg_predictor
  4. Predicted25 ns (illustrative)

    The predictor converts the item with the adapter, asks the map which register sits at address 0 and predicts it with the field's write policy. The mirror is a5, and the prediction count is 1.

    More context

    ap.write(t) is a function call, so the prediction lands in the same time step the monitor publishes it. Desired and mirror both become a5.

    Follow the calls, values and waits
    1. data predict a5: uvm_reg_predictor → regs.ctrl
  5. The boot write returns30 ns (illustrative)

    The driver calls item_done and the boot sequence's finish_item returns. The mirror already holds a5.

    More context

    The log line "30 ns boot write done: mirrored=a5 predictions=1" is printed here.

    Follow the calls, values and waits
    1. return item_done: APB sequencer + driver → boot_write sequence
    2. Released: boot_write sequence
Read the diagram as text
  • boot_write sequence: Raw apb_item, no register call
  • APB sequencer + driver: Write of a5 completes at 25 ns
  • incr_csr.ctrl: a5 after the 25 ns edge
  • apb_monitor: Samples the completed transfer at 25 ns
  • uvm_reg_predictor: bus2reg, then the map finds CTRL at address 0
  • regs.ctrl: Mirror and desired become a5
  • boot_write sequence → APB sequencer + driver: apb_item
  • APB sequencer + driver → incr_csr.ctrl: APB write
  • incr_csr.ctrl → apb_monitor: pins sampled
  • apb_monitor → uvm_reg_predictor: ap.write(t)
  • uvm_reg_predictor → regs.ctrl: predict a5 (write policy)

Explicit prediction: every successful access the monitor observes to a mapped register reaches the mirror, in the same time step the monitor publishes it. Changes the hardware makes by itself do not.

A write is predicted with the field's access policy, so a W1C field is cleared just as the design would clear it.

Explicit prediction in connect_phase. The predictor is created in build_phase like any component.
function void connect_phase(uvm_phase phase);
  drv.seq_item_port.connect(sqr.seq_item_export);
  regs.default_map.set_sequencer(sqr, adapter);
  regs.default_map.set_auto_predict(0);   // one prediction path: the predictor
  predictor.map     = regs.default_map;   // finds the register from the address
  predictor.adapter = adapter;            // turns an apb_item into a register operation
  mon.ap.connect(predictor.bus_in);       // every observed transfer
endfunction

The monitor is the same component that feeds the scoreboard and coverage. One analysis port can drive several subscribers, so the predictor is simply one more listener on it.

The predictor needs three things: the map, the adapter, and the monitor's stream. Leave out the connection and it never runs, which is easy to miss because it reports nothing. A transfer to an address that no register occupies is ignored, with a message only at UVM_FULL verbosity, far above the default.

One prediction path per access

With a predictor in place, turn auto prediction off. Otherwise every access the map makes is predicted twice: once by the map when the access ends, and once by the predictor when the monitor reports it.

For a plain read-write field, the second prediction writes the same value again, and nothing looks wrong. That is the danger: the final value matches, so tests pass. Fields with side effects can then go wrong, and any callback that counts or reacts to predictions runs twice.

Both paths on

regs.default_map.set_auto_predict(1);
mon.ap.connect(predictor.bus_in);

Each map access is predicted twice. A read-write field ends with the right value, which hides the problem.

Predictor only

regs.default_map.set_auto_predict(0);
mon.ap.connect(predictor.bus_in);

Every transfer is predicted once, from the pins, whichever code started it.

Auto prediction is off by default in uvm-core, so leaving the call out also gives one path. Writing set_auto_predict(0) anyway tells the reader which choice the bench made.

Counting predictions

The example makes the double prediction visible. It registers a callback, an object whose post_predict method UVM calls each time the mode field is predicted from a write or a read, and counts the calls. Direct predict(v) calls and reset() do not call it.

The test runs three operations: the boot sequence writes 0xa5 straight on the bus, the model writes 0x3c, and a checked mirror reads it back. With auto prediction off, each transfer is predicted once, by the predictor.

The mirror and the prediction count, one column per transfer
0123operationresetboot write a5write(3c)mirror(check)design CTRL01a53cmirror01a53cpredictions0123not a model access

The mirror follows the design through all three transfers, including the one the model did not make.

The mirror check passes because the predictor already knew about 0xa5 and 0x3c. Turn auto prediction back on, and the count for the model's own write goes up by two.

Prediction at chip level

Lesson U11.1 made the incrementer agent passive at chip level, because another block drives its pins. The APB agent follows the same path. In the chip bench the processor model drives the register bus, and the bench's APB agent becomes passive: a monitor, no driver.

The register model still works there, through the predictor alone. Every access the processor makes reaches the monitor, then the predictor, then the mirror. The bench never drives a transfer, yet the model tracks every successful access to a mapped register. This is passive prediction, the third row of the table above.

So a register model set up with a predictor at block level moves to chip level unchanged. Only the map's sequencer goes away, and the checks built on the mirror keep working. A model that relied on auto prediction would fall silent at chip level, because it never makes an access there.

The register model inside the scoreboard

A scoreboard whose expected result depends on configuration reads the configuration from the mirror. The mirror records what the design was last seen to hold, which is what decides its behaviour. The desired value may be a request that never went out, and reading the design's own signals would copy a design bug into the expectation.

The scoreboard holds a handle to the same model; U8.3 used this pattern before register models were introduced.
// In the scoreboard, when a data transfer is observed
uvm_reg_data_t mode = regs.ctrl.mode.get_mirrored_value();
expected = (mode == 2) ? x + 2 : x + 1;   // the rule depends on the mode

Ordering matters here. Suppose the mode is 1, a configuration write changes it to 2, and x = 16 is accepted on the same clock edge. The design's specification must say which mode that request uses; say it is the old one, so the right result is 17. Analysis subscribers run one after another in the same time step, so if the predictor runs first, the scoreboard reads 2 and expects 18: a false failure.

How the bench orders one edgeMode given to x = 16Expected y
Whichever subscriber happens to run first1 or 217 or 18
Requests first, then configuration (an old-mode design)117
Configuration first, then requests (a new-mode design)218

The rule comes from the specification, and the bench must apply it explicitly. Reading the mirror in a different callback does not decide it.

Two ways make it explicit. A small coordinator collects the configuration and data observations of each cycle, gives every request the mode the specification names, then applies the cycle's configuration to its own copy. Or the test plan forbids configuration changes while requests are in flight, and an assertion checks that rule.

CheckReported when the mirror is out of dateStatus returnedMirror afterwards
mirror(status, UVM_CHECK)UVM_ERROR: value read does not match mirrored valueUVM_IS_OKa5, the value read
read after set_check_on_read(1)The same UVM_ERRORUVM_IS_OKa5
read with checking offNothing reportedUVM_IS_OKa5

Auto prediction only, after a boot write of a5 the model did not see. The first two rows are measured (a probe and the predict item below); the third follows from the same read path. The status reports the bus access; the mismatch is a separate error.

So a mismatch never shows up in status. A test that relies on mirror checks must let the error count decide the verdict, and a test that checks status alone must also compare the data itself.

What prediction cannot see

A predictor learns only from the bus. When the hardware changes a register by itself, such as a status level that rises as data arrives, no transfer happens and the mirror stays put. Mark such fields volatile in configure, as STATUS was in U13.1. In uvm-core that also sets the field's compare setting to UVM_NO_CHECK, so mirror(status, UVM_CHECK) skips it; set_compare controls that check directly. Read such fields, or check them against your own prediction.

Some benches handle such fields with a callback or a small model of their own. When the bench knows the design has received four items, it can call predict with the expected level. That keeps the checks exact, at the cost of a little extra code that must match the design.

A wrong prediction is also possible. If the monitor reports a transfer the design refused, the predictor still updates the mirror unless the failure reaches the register layer's status. The path is: the monitor copies pslverr into the item, bus2reg sets rw.status = UVM_NOT_OK from it, and the predictor then skips the prediction with a PREDICT_NOK warning. This small bus has no error signal, and its bus2reg always sets UVM_IS_OK. A real adapter must not, and the driver must return the same error for the model's own accesses.

Try it

Each line shows the mirror and the prediction count after one operation. The boot write raised the count even though the model did not make it, which is the point of a predictor.

Change set_auto_predict(0) to set_auto_predict(1) and run again: the model's own accesses are now predicted twice, so the count climbs faster while the mirror values stay the same. Then delete mon.ap.connect(predictor.bus_in); with auto prediction off, and watch the boot write vanish from the mirror and the mirror check fail.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: 25 ns APB write addr=0 data=a5 30 ns boot write done: mirrored=a5 predictions=1 55 ns APB write addr=0 data=3c 60 ns write(3c) done: mirrored=3c predictions=2 85 ns APB read addr=0 data=3c 90 ns mirror(check): status=UVM_IS_OK predictions=3 UVM_ERROR : 0

Check yourself

Predict the outputThe same bench, but auto prediction is turned on and the predictor stays connected. The test makes one write. What line does the $display print? UVM's own messages are not part of the answer.
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

// The incrementer's register slice on a small APB bus.
interface apb_if (input logic clk);
  logic        psel = 0, penable = 0, pwrite = 0;
  logic [31:0] paddr = 0, pwdata = 0;
  logic [31:0] prdata;
  logic        pready;
endinterface

// CTRL at address 0 (8 bits, read-write); every access completes at once.
module incr_csr (apb_if bus);
  logic [7:0] ctrl = 8'h01;
  assign bus.pready = 1;
  assign bus.prdata = (bus.paddr == 0) ? {24'b0, ctrl} : 32'b0;
  always @(posedge bus.clk)
    if (bus.psel && bus.penable && bus.pwrite && bus.paddr == 0)
      ctrl <= bus.pwdata[7:0];
endmodule

class apb_item extends uvm_sequence_item;
  `uvm_object_utils(apb_item)
  bit          write;
  bit   [31:0] addr, data;
  function new(string name = "apb_item"); super.new(name); endfunction
endclass

class ctrl_reg extends uvm_reg;
  `uvm_object_utils(ctrl_reg)
  rand uvm_reg_field mode;
  function new(string name = "ctrl_reg"); super.new(name, 32, UVM_NO_COVERAGE); endfunction
  function void build();
    mode = uvm_reg_field::type_id::create("mode");
    mode.configure(this, 8, 0, "RW", 0, 8'h01, 1, 1, 0);
  endfunction
endclass

class incr_regs extends uvm_reg_block;
  `uvm_object_utils(incr_regs)
  rand ctrl_reg ctrl;
  function new(string name = "incr_regs"); super.new(name, UVM_NO_COVERAGE); endfunction
  function void build();
    ctrl = ctrl_reg::type_id::create("ctrl");
    ctrl.configure(this);
    ctrl.build();
    default_map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
    default_map.add_reg(ctrl, 'h0, "RW");
    lock_model();
  endfunction
endclass

// Translates between a register operation and an APB item.
class apb_adapter extends uvm_reg_adapter;
  `uvm_object_utils(apb_adapter)
  function new(string name = "apb_adapter");
    super.new(name);
    supports_byte_enable = 0;
    provides_responses   = 0;   // the driver writes read data into the request itself
  endfunction
  virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    apb_item t = apb_item::type_id::create("t");
    t.write = (rw.kind inside {UVM_WRITE, UVM_BURST_WRITE});
    t.addr  = rw.addr;
    t.data  = rw.data;
    return t;
  endfunction
  virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    apb_item t;
    if (!$cast(t, bus_item)) `uvm_fatal("NOTAPB", "bus2reg needs an apb_item")
    rw.kind   = t.write ? UVM_WRITE : UVM_READ;
    rw.addr   = t.addr;
    rw.data   = t.data;
    rw.status = UVM_IS_OK;
  endfunction
endclass

class apb_driver extends uvm_driver #(apb_item);
  `uvm_component_utils(apb_driver)
  virtual apb_if vif;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(req);
      @(negedge vif.clk);                       // setup phase
      vif.psel <= 1; vif.penable <= 0;
      vif.pwrite <= req.write; vif.paddr <= req.addr; vif.pwdata <= req.data;
      @(negedge vif.clk);                       // access phase
      vif.penable <= 1;
      do @(posedge vif.clk); while (!vif.pready);
      if (!req.write) req.data = vif.prdata;    // read data goes back in the request
      @(negedge vif.clk);
      vif.psel <= 0; vif.penable <= 0;
      seq_item_port.item_done();
    end
  endtask
endclass

// Watches the pins and publishes every completed transfer.
class apb_monitor extends uvm_monitor;
  `uvm_component_utils(apb_monitor)
  virtual apb_if vif;
  uvm_analysis_port #(apb_item) ap;
  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
  endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (vif.psel && vif.penable && vif.pready) begin
        apb_item t = apb_item::type_id::create("t");
        t.write = vif.pwrite;
        t.addr  = vif.paddr;
        t.data  = vif.pwrite ? vif.pwdata : vif.prdata;
        ap.write(t);
      end
    end
  endtask
endclass

// Counts how often the model updates the mode field's mirror.
class count_predictions extends uvm_reg_cbs;
  int count;
  function new(string name = "count_predictions"); super.new(name); endfunction
  virtual function void post_predict(input uvm_reg_field fld, input uvm_reg_data_t previous,
                                     inout uvm_reg_data_t value, input uvm_predict_e kind,
                                     input uvm_door_e path, input uvm_reg_map map);
    count++;
  endfunction
endclass

// A boot sequence that writes CTRL on the bus without using the register model.
class boot_write extends uvm_sequence #(apb_item);
  `uvm_object_utils(boot_write)
  function new(string name = "boot_write"); super.new(name); endfunction
  task body();
    apb_item t = apb_item::type_id::create("t");
    start_item(t);
    t.write = 1; t.addr = 0; t.data = 'ha5;
    finish_item(t);
  endtask
endclass

class predict_test extends uvm_test;
  `uvm_component_utils(predict_test)
  incr_regs                    regs;
  apb_adapter                  adapter;
  apb_driver                   drv;
  apb_monitor                  mon;
  uvm_sequencer #(apb_item)    sqr;
  uvm_reg_predictor #(apb_item) predictor;
  count_predictions            counter;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    virtual apb_if vif;
    regs = incr_regs::type_id::create("regs");
    regs.build();
    adapter   = apb_adapter::type_id::create("adapter");
    sqr       = uvm_sequencer #(apb_item)::type_id::create("sqr", this);
    drv       = apb_driver::type_id::create("drv", this);
    mon       = apb_monitor::type_id::create("mon", this);
    predictor = uvm_reg_predictor #(apb_item)::type_id::create("predictor", this);
    if (!uvm_config_db #(virtual apb_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "no virtual interface")
    drv.vif = vif;
    mon.vif = vif;
    counter = new("counter");
    uvm_reg_field_cb::add(regs.ctrl.mode, counter);
  endfunction
  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(sqr.seq_item_export);
    regs.default_map.set_sequencer(sqr, adapter);
    regs.default_map.set_auto_predict(1);      // the map predicts too
    predictor.map     = regs.default_map;      // finds the register from the address
    predictor.adapter = adapter;               // turns an apb_item into a register operation
    mon.ap.connect(predictor.bus_in);
  endfunction
  task run_phase(uvm_phase phase);
    uvm_status_e status;
    phase.raise_objection(this);
    regs.reset();
    regs.ctrl.write(status, 'h3c);
    $display("predictions=%0d mirrored=%0h", counter.count, regs.ctrl.get_mirrored_value());
    phase.drop_objection(this);
  endtask
endclass

module tb;
  bit clk;
  always #5 clk = ~clk;
  apb_if   bus (clk);
  incr_csr dut (bus);
  initial begin
    uvm_config_db #(virtual apb_if)::set(null, "uvm_test_top", "vif", bus);
    run_test("predict_test");
  end
endmodule
Predict the outputThe bench from the example, with the predictor left unconnected. The boot sequence writes a5 on the bus, then the test turns on check-on-read and calls an ordinary read. What line does the $display print? UVM's own messages are not part of the answer.
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

// The incrementer's register slice on a small APB bus.
interface apb_if (input logic clk);
  logic        psel = 0, penable = 0, pwrite = 0;
  logic [31:0] paddr = 0, pwdata = 0;
  logic [31:0] prdata;
  logic        pready;
endinterface

// CTRL at address 0 (8 bits, read-write); every access completes at once.
module incr_csr (apb_if bus);
  logic [7:0] ctrl = 8'h01;
  assign bus.pready = 1;
  assign bus.prdata = (bus.paddr == 0) ? {24'b0, ctrl} : 32'b0;
  always @(posedge bus.clk)
    if (bus.psel && bus.penable && bus.pwrite && bus.paddr == 0)
      ctrl <= bus.pwdata[7:0];
endmodule

class apb_item extends uvm_sequence_item;
  `uvm_object_utils(apb_item)
  bit          write;
  bit   [31:0] addr, data;
  function new(string name = "apb_item"); super.new(name); endfunction
endclass

class ctrl_reg extends uvm_reg;
  `uvm_object_utils(ctrl_reg)
  rand uvm_reg_field mode;
  function new(string name = "ctrl_reg"); super.new(name, 32, UVM_NO_COVERAGE); endfunction
  function void build();
    mode = uvm_reg_field::type_id::create("mode");
    mode.configure(this, 8, 0, "RW", 0, 8'h01, 1, 1, 0);
  endfunction
endclass

class incr_regs extends uvm_reg_block;
  `uvm_object_utils(incr_regs)
  rand ctrl_reg ctrl;
  function new(string name = "incr_regs"); super.new(name, UVM_NO_COVERAGE); endfunction
  function void build();
    ctrl = ctrl_reg::type_id::create("ctrl");
    ctrl.configure(this);
    ctrl.build();
    default_map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
    default_map.add_reg(ctrl, 'h0, "RW");
    lock_model();
  endfunction
endclass

// Translates between a register operation and an APB item.
class apb_adapter extends uvm_reg_adapter;
  `uvm_object_utils(apb_adapter)
  function new(string name = "apb_adapter");
    super.new(name);
    supports_byte_enable = 0;
    provides_responses   = 0;   // the driver writes read data into the request itself
  endfunction
  virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    apb_item t = apb_item::type_id::create("t");
    t.write = (rw.kind inside {UVM_WRITE, UVM_BURST_WRITE});
    t.addr  = rw.addr;
    t.data  = rw.data;
    return t;
  endfunction
  virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    apb_item t;
    if (!$cast(t, bus_item)) `uvm_fatal("NOTAPB", "bus2reg needs an apb_item")
    rw.kind   = t.write ? UVM_WRITE : UVM_READ;
    rw.addr   = t.addr;
    rw.data   = t.data;
    rw.status = UVM_IS_OK;
  endfunction
endclass

class apb_driver extends uvm_driver #(apb_item);
  `uvm_component_utils(apb_driver)
  virtual apb_if vif;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(req);
      @(negedge vif.clk);                       // setup phase
      vif.psel <= 1; vif.penable <= 0;
      vif.pwrite <= req.write; vif.paddr <= req.addr; vif.pwdata <= req.data;
      @(negedge vif.clk);                       // access phase
      vif.penable <= 1;
      do @(posedge vif.clk); while (!vif.pready);
      if (!req.write) req.data = vif.prdata;    // read data goes back in the request
      @(negedge vif.clk);
      vif.psel <= 0; vif.penable <= 0;
      seq_item_port.item_done();
    end
  endtask
endclass

// Watches the pins and publishes every completed transfer.
class apb_monitor extends uvm_monitor;
  `uvm_component_utils(apb_monitor)
  virtual apb_if vif;
  uvm_analysis_port #(apb_item) ap;
  function new(string name, uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);
  endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (vif.psel && vif.penable && vif.pready) begin
        apb_item t = apb_item::type_id::create("t");
        t.write = vif.pwrite;
        t.addr  = vif.paddr;
        t.data  = vif.pwrite ? vif.pwdata : vif.prdata;
        ap.write(t);
      end
    end
  endtask
endclass

// Counts how often the model updates the mode field's mirror.
class count_predictions extends uvm_reg_cbs;
  int count;
  function new(string name = "count_predictions"); super.new(name); endfunction
  virtual function void post_predict(input uvm_reg_field fld, input uvm_reg_data_t previous,
                                     inout uvm_reg_data_t value, input uvm_predict_e kind,
                                     input uvm_door_e path, input uvm_reg_map map);
    count++;
  endfunction
endclass

// A boot sequence that writes CTRL on the bus without using the register model.
class boot_write extends uvm_sequence #(apb_item);
  `uvm_object_utils(boot_write)
  function new(string name = "boot_write"); super.new(name); endfunction
  task body();
    apb_item t = apb_item::type_id::create("t");
    start_item(t);
    t.write = 1; t.addr = 0; t.data = 'ha5;
    finish_item(t);
  endtask
endclass

class predict_test extends uvm_test;
  `uvm_component_utils(predict_test)
  incr_regs                 regs;
  apb_adapter               adapter;
  apb_driver                drv;
  uvm_sequencer #(apb_item) sqr;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    regs = incr_regs::type_id::create("regs");
    regs.build();
    adapter = apb_adapter::type_id::create("adapter");
    sqr     = uvm_sequencer #(apb_item)::type_id::create("sqr", this);
    drv     = apb_driver::type_id::create("drv", this);
    if (!uvm_config_db #(virtual apb_if)::get(this, "", "vif", drv.vif))
      `uvm_fatal("NOVIF", "no virtual interface")
  endfunction
  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(sqr.seq_item_export);
    regs.default_map.set_sequencer(sqr, adapter);
    regs.default_map.set_auto_predict(1);          // no predictor: only map accesses are predicted
  endfunction
  task run_phase(uvm_phase phase);
    uvm_status_e      status;
    uvm_reg_data_t    value;
    uvm_report_server svr  = uvm_report_server::get_server();
    boot_write        boot = boot_write::type_id::create("boot");
    phase.raise_objection(this);
    regs.reset();
    boot.start(sqr);                               // writes a5 behind the model's back
    regs.default_map.set_check_on_read(1);         // every read now also checks
    regs.ctrl.read(status, value);
    $display("status=%s value=%0h mirrored=%0h errors=%0d", status.name(), value,
             regs.ctrl.get_mirrored_value(), svr.get_severity_count(UVM_ERROR));
    phase.drop_objection(this);
  endtask
endclass

module tb;
  bit clk;
  always #5 clk = ~clk;
  apb_if   bus (clk);
  incr_csr dut (bus);
  initial begin
    uvm_config_db #(virtual apb_if)::set(null, "uvm_test_top", "vif", bus);
    run_test("predict_test");
  end
endmodule
Spot the bugA boot sequence writes 0xa5 straight on the bus, without the model. Each option is the rest of the test's connect_phase. Which one leaves the mirror stale?
ConceptA bench uses a predictor and also leaves auto prediction on. All register tests pass. What is wrong?
ConceptSTATUS.level rises as data arrives, without any bus transfer. What does an explicit predictor do about it?

Remember

  • Auto prediction updates the mirror only for accesses the map makes, so writes by other code leave it stale.
  • Explicit prediction connects the bus monitor to a uvm_reg_predictor, which needs the map and the adapter too.
  • Use one prediction path: with a predictor, keep auto prediction off, or every map access is predicted twice.
  • A predictor learns only from bus transfers; fields the hardware changes by itself should be volatile.
  • A scoreboard reads configuration from the mirror; mirror(UVM_CHECK) and check-on-read report a mismatch as an error while the status stays UVM_IS_OK.