Comparing is not equality
Decide what makes an observed transaction correct: which fields count, which bits are unspecified, which mode the design was in, and how the comparer reports a difference.
- Lesson
- U8.3
- Time
- 30 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
The scoreboard gains a time stamp on every transaction to improve its messages, and from that moment every comparison fails. Predictions are stamped when the read is taken, responses when they come back, and compare() dutifully reports that 35 differs from 55. Equality of every field was never the question. The question is whether the design did what the specification says.
Field of mem_rsp | Decides correctness? | Why |
|---|---|---|
tag | Yes | It is the key; a response with the wrong tag answers the wrong read |
data | Yes, through mask | The value the specification promises, where it promises one |
mask | No | It is the rule, read from the prediction only; the observed object's mask is ignored |
at | No | Bookkeeping for messages and for the latency meter in U8.4 |
req | No | A handle to the read, kept for the message |
Two of five fields decide. The other three ride along and must be ignored by the comparison.
Correct is what the specification says
A comparison is a statement of the specification in code. Three kinds of leniency appear again and again. Some fields never count: time stamps, handles, counters, anything added for debugging. Some bits count only sometimes: a register's reserved bits, or memory that was never written. Some values count within a tolerance: a sample from an analog model, or a latency with a bound.
The place for all three is do_compare, the hook that compare() calls after checking the type. Written there, every caller gets the same verdict: the scoreboard, a stock comparator, a quiz program. Field macros with UVM_ALL_ON compare everything, which is exactly the hook's failure. Give bookkeeping fields UVM_NOCOMPARE, or skip the macros and write do_compare by hand as this lesson does.
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
mem_rsp o;
if (!$cast(o, rhs)) return 0;
// Directed: the scoreboard calls expected.compare(actual), so this object's mask
// is the rule. Time stamps, the mask itself and the request handle never take part.
return comparer.compare_field_int("tag", tag, o.tag, 4) &
comparer.compare_field_int("data", data & mask, o.data & mask, 8);
endfunctionBits that are unspecified
This memory's power-up contents are unspecified. A read of a location that was never written returns whatever the silicon held, and the specification promises nothing about it. The model cannot predict it, and must not pretend to.
The prediction says so with a mask: mask = 8'h00 means no data bit is specified. do_compare compares data & mask on both sides, so any value matches, and the tag is still checked. The comparison is directed: the scoreboard always calls e.compare(r) with the prediction on the left, and only the left object's mask applies. A mask on the observed side is ignored, so nothing the monitor produces can weaken the check. In the example the read of address 5 returns 3a. The prediction holds ff, the model's empty location inverted because inv was set, and the pair still matches with the note "unspecified, so any value". The read still counts as checked, because the tag was.
Partial masks are common: the reserved bits of a status register, the low bits of a free-running counter, the lanes of a bus that a narrow transfer does not drive. The mask travels with the prediction because the predictor is the one that knows which bits the specification promises for this transaction.
A prediction that depends on the mode
The memory has a control register with one bit, inv. A read taken while inv is 1 returns the stored byte inverted. The right answer for the same read is 11 in one mode and ee in the other. So the predictor has to know the mode at the edge the read was taken.
It learns it the way the design did: from the bus. The scoreboard keeps a register mirror, a uvm_reg_block with one register and one field, and updates it from observed control writes with regs.ctrl.predict(t.data). When it predicts a read it asks regs.ctrl.inv.get_mirrored_value().
- Remember the mode35 ns (illustrative)
The monitor reports a write of 1 to CTRL. The scoreboard calls
regs.ctrl.predict(1): the mirror now saysinvis 1.More context
The value comes from the observed bus write, the same evidence the design acted on. The scoreboard never reads the design's register.
Follow the calls, values and waits
- data inv = 1: CTRL write, data 1 → Mirror inv = 1
- Predict with the mode at the read edge45 ns (illustrative)
The tag 2 read of address 1 is taken. The model holds 11 there, and the mirrored
invis 1, so the prediction is ~11 = ee.More context
The mode is read when the read is taken, not when its response arrives. A CTRL write between the two must not change this prediction.
Follow the calls, values and waits
- data stored byte 11: READ tag 2, addr 1 → Expected ee
- data inv = 1: Mirror inv = 1 → Expected ee
- Waiting: Expected ee — waiting for the tag 2 response
- Compare65 ns (illustrative)
The tag 2 response carries ee. It is compared with the prediction, ee, under mask ff: match.
More context
The log line is "65 ns rsp tag=2 data=ee matches prediction data=ee mask=ff". Without the mirror, the prediction would be 11 and this pair would mismatch.
Follow the calls, values and waits
- data r: tag 2 = ee: Response tag 2 = ee → compare: match
- data e: expect ee: Expected ee → compare: match
- Released: Expected ee
Read the diagram as text
- CTRL write, data 1: seen by the monitor at 35 ns
- Mirror inv = 1: regs.ctrl.predict(1)
- READ tag 2, addr 1: seen at 45 ns; model holds 11
- Expected ee: get_mirrored_value() is 1, so ~11
- compare: match: tag and masked data
- Response tag 2 = ee: seen at 65 ns
- CTRL write, data 1 → Mirror inv = 1: predict
- Mirror inv = 1 → Expected ee: mode at the read edge
- READ tag 2, addr 1 → Expected ee: stored byte 11
- Expected ee → compare: match: e
- Response tag 2 = ee → compare: match: r
Remembered mode plus stored byte gives the expected value. A design that latched the control write wrongly would return 11 and fail here.
Each prediction uses the mirror as it stood when the read was taken, which is what the design does too.
Why a mirror and not the design's own inv wire, which a hierarchical reference could read? Because the scoreboard checks the design. If the design latched the control write wrongly, a prediction that read its wire would be wrong in the same way, and the error would pass. The mirror is built from the same bus traffic the design received, and agrees with the design only when the design is right.
The quiz program keeps the mirror but never consults it: two reads taken with inv set mismatch. U13 builds the full register model. There, auto-prediction updates the mirror only for accesses made through the model, such as regs.ctrl.write(). This example's control writes come from a plain sequence on the bus, so the model never sees them, and they need explicit prediction. In U13 that is a uvm_reg_predictor connected to the monitor, which turns each observed bus item into a prediction and replaces the predict call here.
Values within a tolerance
Some outputs are correct within a bound rather than exactly: a filter's sample, a measured latency, a converter's code. The library gives no help here. compare_field_real compares two reals with != and nothing else, so a tolerance is yours to write. Compute the difference and compare it with a bound the prediction carries, in the output's units. On failure call comparer.print_msg() with both values and the bound, so that the miscompare is counted and listed like any other. The bound is part of the specification, so it lives in the item or the prediction, never in the comparator's code.
// inside do_compare: a sample is right within tol codes of the prediction
if ((sample > o.sample ? sample - o.sample : o.sample - sample) > tol) begin
comparer.print_msg($sformatf("sample: lhs = %0d : rhs = %0d, tolerance %0d", sample, o.sample, tol));
ok = 0;
endWhat the comparer tells you
compare() runs through a uvm_comparer. With no argument it uses the default one, which it clears before comparing, so each call overwrites the last result and anyone else using the default sees yours. Pass your own, p.compare(a, c), and afterwards c.get_result() is the number of differing fields and c.get_miscompares() the text, one line per field with the lhs and rhs values.
The comparer also prints [MISCMP] messages, at severity UVM_INFO and verbosity UVM_LOW, in two kinds. Each differing field gets a detail line, but only while the count is within show_max, which defaults to 1. Then, once the whole object has been compared, one summary line says how many miscompares there were and how many were shown. So three differing fields print one detail and one summary; get_miscompares() still lists all three, and set_show_max(0) prints every detail. Older code calls set_miscompare_string, which the 2020 library no longer has.
The example starts by comparing two hand-built responses that differ in tag and data. The log shows one detail line for the tag and a summary saying 2 miscompares, 1 shown; the program prints compare=0, result=2 and both fields from get_miscompares(). With equal tags and a mask that hides the low nibble, the same objects compare equal.
A comparator that transforms first
When the prediction is a pure function of the input transaction, the library offers uvm_algorithmic_comparator #(BEFORE, AFTER, TRANSFORMER). The transformer is a plain class with function AFTER transform(BEFORE b), and its transform is the prediction. The comparator calls it on each input and hands the result to an internal uvm_in_order_class_comparator #(AFTER).
class to_rsp; // TRANSFORMER: the prediction as a function
function mem_rsp transform(mem_req b);
mem_rsp e = mem_rsp::type_id::create("e");
e.tag = b.tag; e.data = model_of(b);
return e;
endfunction
endclass
to_rsp tr = new();
alg = new("alg", this, tr); // the constructor takes the transformer, so no factory create
// mon.req_ap -> alg.before_export, mon.rsp_ap -> alg.after_exportIt inherits the stock comparator's limits: in-order only, mismatches as warnings, and the inner comparator is local, so the counts are reachable only through the report ids. For a design with a mode or a memory, the transformer would need state, and the separate predictor of U8.1 is the better shape.
Try it
The first two lines are the comparer on its own: two differing fields give result 2 and two lines of text; a mask that hides the differing bits gives a match. Then the bus runs. Tag 2 was taken with inv set and the prediction is ee; tag 3 reads a never-written location and matches with mask 0; tag 4 is back to 11 after the control write cleared inv.
Delete the get_mirrored_value line in the scoreboard and tag 2 mismatches. Add comparer.compare_field_int("at", at, o.at, 64) to do_compare and every pair mismatches.
comparer: compare=0, result=2, miscompares: predicted.tag: lhs = 'h7 : rhs = 'h8 predicted.data: lhs = 'h11 : rhs = 'h15 comparer with mask f0 and equal tags: compare=1, result=0 45 ns rsp tag=1 data=11 matches prediction data=11 mask=ff 65 ns rsp tag=2 data=ee matches prediction data=ee mask=ff 75 ns rsp tag=3 data=3a matches prediction data=ff mask=00 (unspecified, so any value) 95 ns rsp tag=4 data=11 matches prediction data=11 mask=ff reads 4, checked 4, mismatches 0, orphans 0, waiting 0 UVM_ERROR : 0Check yourself
Remember
- A comparison is the specification in code: decide which fields count, which bits are specified, and within what tolerance.
- Write the rule once in
do_compare, so everycompare()in the bench gives the same verdict; never compare time stamps or handles. - A mask carried by the prediction says which data bits the specification promises; mask 0 means any value matches while the tag is still checked.
- A mode-dependent prediction reads the mode from a register mirror updated by the bus traffic, never from the design's own state.
- Pass your own
uvm_comparerto readget_result()andget_miscompares(); the log prints one detail line and a summary by default.