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.
| Time | Bus activity | Design CTRL | Mirror (auto prediction) |
|---|---|---|---|
| 0 ns | Model reset | 01 | 01 |
| 25 ns | Boot sequence writes a5 | a5 | 01: the map did not make this access |
| Later | mirror(UVM_CHECK) reads a5 | a5 | Error: 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.
| Mode | How it is set up | Updates the mirror from | Sees other masters |
|---|---|---|---|
| Auto prediction | map.set_auto_predict(1) | Each access the map itself makes | No |
| Explicit prediction | Monitor to uvm_reg_predictor, auto prediction off | Every transfer the monitor observes | Yes |
| Passive | Predictor only; no sequencer behind the map | Every observed transfer; the model never drives | Yes |
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.
- 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
- data boot apb_item: write 0 <- a5: boot_write sequence → APB sequencer + driver
- Waiting: boot_write sequence — in finish_item until item_done
- 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
- data pwdata = a5 at address 0: APB sequencer + driver → incr_csr.ctrl
- 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
- data pwdata = a5 at address 0: incr_csr.ctrl → apb_monitor
- data monitor item t: write 0, a5: apb_monitor → uvm_reg_predictor
- 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
- data predict a5: uvm_reg_predictor → regs.ctrl
- The boot write returns30 ns (illustrative)
The driver calls
item_doneand the boot sequence'sfinish_itemreturns. 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
- return item_done: APB sequencer + driver → boot_write sequence
- 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.
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
endfunctionThe 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 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.
// 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 modeOrdering 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 edge | Mode given to x = 16 | Expected y |
|---|---|---|
| Whichever subscriber happens to run first | 1 or 2 | 17 or 18 |
| Requests first, then configuration (an old-mode design) | 1 | 17 |
| Configuration first, then requests (a new-mode design) | 2 | 18 |
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.
| Check | Reported when the mirror is out of date | Status returned | Mirror afterwards |
|---|---|---|---|
mirror(status, UVM_CHECK) | UVM_ERROR: value read does not match mirrored value | UVM_IS_OK | a5, the value read |
read after set_check_on_read(1) | The same UVM_ERROR | UVM_IS_OK | a5 |
read with checking off | Nothing reported | UVM_IS_OK | a5 |
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.
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 : 0Check yourself
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 staysUVM_IS_OK.