The register round is where interviewers find out whether you have run a register model or only generated one. The vocabulary (mirror, desired, predict) is easy; the timing is not. Who updates the mirror, at which moment, from which observation? Most register bugs in real benches are a wrong answer to one of those three.
The checking half asks how you know a test checked anything. Senior answers lean on evidence: a mismatch that is expected by design, a comparator that passed eight wrong predictions, a verdict that said PASS with zero compares. All of them happened on the DMA bench.
Register layer Auto, explicit or passive prediction: which do you use, and what is the catch with explicit? Weak answer: Auto is enough; explicit is only for multiple bus masters.
Strong answer: Auto prediction updates the mirror at the end of the model's own access, so it misses traffic it did not start. Explicit prediction connects a uvm_reg_predictor to the bus monitor, so every observed transfer updates the mirror. The catch is timing: the mirror updates when the monitor publishes, a moment after write()/ read() return. On the DMA bench, dma_ral_job_test logs STATUS=2: done=0 because the mirror had not caught up; uvm_reg_bit_bash_seq, which takes its expected value from get() right after write(), failed every bit, and mirror(UVM_CHECK) missed a real mismatch until auto_predict was turned on for those tests.
Follow-up: “Both on at once?” Every access is predicted twice, and with set_check_on_read(1) one mismatch is reported twice.
Home lesson: Prediction modes
Does peek() change the mirror? Weak answer: No; peek is a backdoor read, so it does not touch the model.
Strong answer: Yes. Like every read, a peek() predicts the value it read. On the DMA bench, a peek placed before mirror(UVM_CHECK) set the mirror to the DUT's value and hid the busy-LEN mismatch: the check then compared the DUT with itself. The bench runs the check first and peeks afterwards, with a comment saying why. A peek never checks; read(..., UVM_BACKDOOR) does.
Follow-up: “Does a peek of a read-clear field clear the mirror?” No. Read side effects are not applied on the backdoor path, because a backdoor read does not clear the hardware either.
Home lesson: Volatile fields and mirror checks
What does marking a field volatile do? Weak answer: The model still checks it; volatile just means the value can change.
Strong answer: Volatile fields are never compared: mirror(UVM_CHECK) and read checks skip them, and reading such a field's mirror warns ( UVM/FLD/GET_MIRRORED_VAL/VOL). It also makes needs_update() always true for a writable volatile field. Model hardware-updated fields as volatile; on the DMA bench that is STATUS.BUSY, DONE, ERR and COUNT. Check them with explicit reads against an expectation, for example DONE=1 and COUNT=LEN after the interrupt.
Follow-up: “What does the reset test do with them?” uvm_reg_hw_reset_seq skips registers whose fields are all volatile, which the bench's log says in words.
Home lesson: Volatile fields and mirror checks
How do you model a write-1-to-clear status bit, and what is the trap with update()? Weak answer: Model it RW and write 1 to clear it.
Strong answer: W1C (and volatile, since hardware sets it). W1C makes the model predict mirror & ~written natively, so a clear needs no custom code; as RW, a clear write of 1 would predict the bit as 1, the opposite of the hardware. The trap: a block-level update() writes 1 to every W1C bit whose desired value is 0, because writing 1 is how a W1C bit becomes 0, and a writable volatile field always needs an update. A configuration sequence can clear pending interrupt status before the handler sees it. Call update() on the register you changed, not on a block with W1C status.
Follow-up: “A W1C bit that clears only when idle?” define_access("W1CBUSY") registers the name but gives it no behaviour; the behaviour comes from a callback.
Home lesson: Field access policies and callbacks
A second job does go.set(1); ctrl.update() and nothing reaches the bus. Why? Weak answer: update() is broken for single bits.
Strong answer: update() writes only if needs_update() finds a field whose desired value differs from the mirror. CTRL.GO is a WO trigger that reads 0, but after the first ctrl.write(3) its mirror holds 1, and a read never changes a WO field's mirror. So go.set(1) makes desired equal mirrored and update() returns without an access. The DMA bench writes CTRL directly for that reason. update() is a diff against the model's belief, not against the hardware.
Follow-up: “Can GO=1 ride along on another write?” Yes. On APB a field write becomes a whole-register write, and WO neighbours are filled from their mirror, so a later write to IRQ_EN alone carries GO=1.
Home lesson: The access API
The bus returns a slave error on a register write. Does the mirror update? Weak answer: No, the mirror never updates on an error.
Strong answer: It depends on the library. The stock uvm_reg_predictor passes every observed transfer to do_predict and skips only its read check on UVM_NOT_OK. In IEEE 1800.2, uvm_reg::do_predict then skips the mirror update and warns PREDICT_NOK. In UVM 1.2, do_predict has no status check, so the mirror is updated even after a slave error; filter in pre_predict or a custom predictor to keep it. Either way the adapter must turn PSLVERR into UVM_NOT_OK, as the bench's adapter does.
Follow-up: “What about the desired value?” write() sets it before the access, so after a failed write needs_update() is true and a later update() retries the write.
Home lesson: The adapter contract
What does provides_responses mean in an adapter, and what if it is wrong? Weak answer: Set it to 1 so the model gets read data.
Strong answer: It tells the map whether the driver returns a separate response. Set it to 1 only when the driver calls item_done(rsp) or put_response; then the map waits for that response. If the driver writes the result into the request, as the DMA bench's APB driver does with rdata and slverr, set it to 0 and bus2reg reads the request. Set to 1 with no response, register accesses hang; responses nobody collects fill the response queue instead.
Follow-up: “What does bus2reg do with a failed cast?” On the bench it is a fatal: a wrong item type means the adapter is attached to the wrong sequencer.
Home lesson: The adapter contract
Your mirror check fails right after a legal write. Model bug or DUT bug? Weak answer: A DUT bug; the model is generated from the spec.
Strong answer: Either, and the bench has the model case. dma_ral_busy_len_test writes LEN=9 while BUSY; the DMA ignores it, as specified, but the model has no "ignored while busy" rule, so the mirror says 9 and mirror(UVM_CHECK) reports read 4 against mirrored 9. That error is expected by design: it shows a model gap. Fix the model (a callback or custom predictor for the rule), not the test. Before blaming either, rule out prediction lag and a peek before the check.
Follow-up: “Bits the model does not define?” Unmodelled bits are left out of the compare and never cause mismatches.
Home lesson: Why a register model
How do you run the built-in register sequences on a block with a GO bit and W1C status? Weak answer: Run bit-bash on everything; the model knows the policies.
Strong answer: Exclude what the sequence cannot handle. The DMA bench's bit-bash test sets NO_REG_TESTS on CTRL and STATUS through uvm_resource_db, because writing GO starts a job and STATUS is W1C, and turns auto_predict on so the expected values are not stale. uvm_reg_hw_reset_seq runs in its own test and skips all-volatile registers. Bit-bash then covers LEN, KEY and COUNT.
Follow-up: “Why not just trust the generated model?” The built-in sequences check that model and RTL agree; a model with wrong access policies fails them, which is the point.
Home lesson: Built-in register sequences
Checking A built-in comparator reports all matches, but the predictor is wrong. How? Weak answer: Built-in comparators are reliable; the predictor must be right.
Strong answer: uvm_in_order_class_comparator compares with compare(), and axis_item has no do_compare, so every comparison returns a match. dma_cmp_trap_test feeds it a predictor with the wrong key: 8 matches, 0 mismatches for 8 wrong predictions. Every item that is compared needs a real do_compare (or field macros), and a comparator needs one deliberate mismatch test before you trust it.
Follow-up: “How does the bench's working comparator avoid it?” It is a uvm_in_order_comparator with an explicit axis_data_comp policy that compares the data, so the check does not depend on the item's compare().
Home lesson: Built-in comparators
AXI responses come back out of order. How do you key the scoreboard? Weak answer: One expected slot per ID.
Strong answer: A queue per ID. Several outstanding transactions may share one ID, and responses for the same ID return in order, while different IDs may interleave. One slot per ID overwrites the earlier expectation and either false-fails or, worse, matches the wrong one. Pop the head of that ID's queue on each response, and fail at end of test if any queue is not empty.
Follow-up: “And transactions without IDs?” Match by content or address with an explicit window, and report what stayed unmatched.
Home lesson: Out-of-order matching by ID
How do you know a passing test actually checked something? Weak answer: No UVM_ERRORs in the log.
Strong answer: A scoreboard that fails when it compared nothing, and a verdict that cannot be faked. dma_false_pass_test demotes SB_EMPTY and SB_LEFTOVER unconditionally and the verdict says PASS with 0 words compared. Waivers must be targeted (one ID, one pattern, one ticket, counted), like dma_waiver_catcher. And print the verdict from the report server: a UVM_FATAL ends the run before report_phase, so a PASS/FAIL banner there never appears after a fatal; dma_report_server's report_summarize still prints.
Follow-up: “What else proves the checkers work?” Fault injection. The bench's campaign forces eight stuck-at faults and detects five; the three misses are real checking gaps, such as TLAST never being checked.
Home lesson: The false-pass playbook
Rapid fire Check yourself After rm.status.read(st, v) returns, a test prints rm.status.done.get_mirrored_value() and sees 0 although the DUT has DONE=1. Name two reasons. With explicit prediction the mirror updates when the monitor publishes, a moment after read() returns. And DONE is volatile, so reading its mirror warns and is not a meaningful check. Use the value read() returned.
Check yourself A configuration sequence calls rm.update(st) on the whole block and the first interrupt is lost. What happened? STATUS's W1C fields are volatile, so the block's update writes STATUS, and for a W1C field it writes 1 where the desired value is 0: DONE was cleared before the handler read it. Update only the registers you changed.
Check yourself A scoreboard keys AXI expectations as exp[id] = item. Two writes with ID 3 are outstanding. What goes wrong? The second expectation overwrites the first, so the first response is compared with the second's data. Keep a queue per ID and pop in order.
Next: the architecture and closure round : a red regression two weeks before tape-out, and what you do first.