Tuesday. The verification engineer who took row S3 from the triage has walked it back from the stream pins to the power domain: a signal goes unknown while the domain is off. The power-domain owner sits in another team, eight hours away. The last time a bug crossed that boundary it took three days of messages before anyone looked at the RTL (an illustrative story, but a common one).
What must travel with this bug so that the power-domain owner starts at the first wrong cycle tomorrow morning, not at the regression page?
The usual hand-off is a reassignment: the ticket gets a new owner and a comment, "looks like a power-domain issue, see the log". The receiver opens the log, finds one FAIL line, rebuilds the repro, wonders which simulator was used, and starts the walk-back from the symptom again. If they reach a different conclusion, the ticket goes back. Two people now half-own it, and neither is working on it.
Two habits cause most of the waste. Ownership by block: the bug "belongs" to whoever owns the module where the symptom shows, so it moves when someone has a guess, not when the evidence says so. Handing over the failure instead of the hunt: the receiver gets the symptom, but not what the giver learned, which is the expensive part.
One person at a time, by name. A team cannot own a bug; a team's queue is where bugs wait.
The owner is whoever acts next. During Reproduce and Localise that is often verification; once the first wrong cycle is inside a block, the block's designer; for a timing question, physical design; for a part on a bench, the lab.
Ownership moves by acceptance. The giver offers, the receiver checks the package, then takes it. Until then the giver still owns it and still answers questions.
The tracker records each move with the step of the debug loop the bug is on, so anyone can see where it stands.
Field | What goes in it | Without it the receiver |
|---|---|---|
State of the hunt | Which loop step, in one line | does not know whether to reproduce, localise or fix |
Replay | Full commit hash, command, seed, simulator and version, environment, local edits | rebuilds the repro by guessing |
Signature | The exact FAIL line in every simulator it was run in | misses that one bug looks like two |
Frequency and history | n of m seeds; last good and first bad commit, with the bisect log | repeats the sweep and the bisect |
First wrong cycle | Cycle, time, signal and value, with the probe or command that shows it | walks back from the symptom again |
Waveform window | How to make the dump, the time window and the six or so signals to look at | opens a full dump and browses |
Ruled out | What was excluded and by which result | re-tests dead hypotheses |
Open question | What the receiver is asked to decide or do | does not know why they have it |
Most of these fields already exist if the giver kept a debug log and a replay record; the package is their summary, written for one reader.
The receiver builds a fresh tree from the package's commit and runs its command: the same FAIL line.
The receiver runs the probe or opens the window: the same first wrong cycle.
Only then is ownership moved. Anything that differs goes back to the giver as a question, the same day.
$ tb/run.sh periph_power 1
dbglab: test=periph_power seed=1
FAIL test=periph_power seed=1 cycle=431: STREAM: pstream_valid is unknown
$ tb/run.sh periph_power 1 verilator
dbglab: test=periph_power seed=1
FAIL test=periph_power seed=1 cycle=2421: WATCHDOG: no bus or stream progress for 2000 cycles; cpu_valid=0 cpu_ready=0 cpu_addr=00003008The same bug prints two different signatures: an unknown value in Icarus, a hang in Verilator, whose two-state supply model drives noise instead of X. If the package names only one, the receiver who runs the other simulator sees "a different bug". Both lines go in.
$ git bisect start HEAD v0.1
$ git bisect run tb/run.sh periph_power 1
...
$ git bisect log
...
# bad: [122e88a7c8e74f9e3b29db8fad03dbe3fd3b7e9b] periph_pd: isolate only the outputs that leave the peripheral
git bisect bad 122e88a7c8e74f9e3b29db8fad03dbe3fd3b7e9b
# good: [b357bd84b7252b54097cac920e6a14cadda7503c] csr: no reset on the DMA address and data registers
git bisect good b357bd84b7252b54097cac920e6a14cadda7503c
# first bad commit: [122e88a7c8e74f9e3b29db8fad03dbe3fd3b7e9b] periph_pd: isolate only the outputs that leave the peripheral
$ git show 122e88a
...
rinc only feeds our own async FIFO, so it does not need a clamp.
...
- assign rinc = iso_en ? 1'b0 : rinc_p;
+ assign rinc = rinc_p; // internal: only feeds our own async FIFOThe giver's probe prints the power controls, the sink's read-increment and the FIFO's read side on every edge of the peripheral clock. It is compiled next to the design and changes nothing (the exact command is in the Evidence box). It goes in the package as a file, because a cycle number the receiver cannot reproduce is only a claim.
// not-run: needs dbglab's sources and bench; compile it next to them (command in the Evidence box)
// pd_probe: one line per pclk edge from +FROM to +TO (bus cycles): the power controls,
// the sink's read-increment and the FIFO's read side. Reads the design, changes nothing.
`timescale 1ns/1ps
module pd_probe;
int unsigned from_c, to_c;
initial begin
if (!$value$plusargs("FROM=%d", from_c)) from_c = 0;
if (!$value$plusargs("TO=%d", to_c)) to_c = 0;
$display(" ns cycle pwr_on iso_en rinc rempty rbin");
end
always @(posedge dbglab_tb.pclk) begin
#0.5;
if (dbglab_tb.cycle >= from_c && dbglab_tb.cycle <= to_c)
$display("%4.0f %5d %b %b %b %b %h", $realtime, dbglab_tb.cycle,
dbglab_tb.dut.u_per.u_pd.pwr_on, dbglab_tb.dut.u_per.u_pd.iso_en, dbglab_tb.dut.u_per.rinc,
dbglab_tb.dut.u_per.rempty, dbglab_tb.dut.u_per.u_fifo.rbin);
end
endmodule$ ../probes/run-probe.sh ../probes/pd_probe.sv periph_power 1 +FROM=70 +TO=95
dbglab: test=periph_power seed=1
ns cycle pwr_on iso_en rinc rempty rbin
722 72 1 1 0 1 04
759 76 0 1 x 1 04
796 80 0 1 x 1 04
833 83 0 1 x 0 04
870 87 0 1 x x xx
907 91 0 1 x x xx
944 94 0 1 x x xx
FAIL test=periph_power seed=1 cycle=431: STREAM: pstream_valid is unknownLine by line: at 722 ns the domain is on and isolated, and rinc is 0. At 759 ns (cycle 76) power goes off and rinc becomes X while `iso_en` is 1: that is the first wrong value. At 833 ns words arrive and the FIFO is no longer empty, so the X increment reaches the read pointer, which is X from 870 ns. The STREAM failure at cycle 431 is 355 cycles of consequence later.
$ DUMP=1 tb/run.sh periph_power 1
dbglab: test=periph_power seed=1
FAIL test=periph_power seed=1 cycle=431: STREAM: pstream_valid is unknown
$ ls -l dbglab.vcd | awk '{print $5}'
55057Here the whole dump is 55 KB, so it could be attached; on a real project the package gives the command, the window (700 to 950 ns) and the signals (pwr_on, iso_en, rinc, rempty, rbin, pstream_valid), and the receiver makes their own. Six lanes and 250 ns is a picture anyone can read; "see the dump" is not.
Field | Content for S3 |
|---|---|
State | Localised: first wrong value found inside |
Replay | dbglab |
Signature | Icarus: |
Frequency and history | Seeds 1 to 20, both simulators. First bad 122e88a, last good b357bd8 (bisect log attached) |
First wrong cycle | Cycle 76, 759 ns: |
Waveform window |
|
Ruled out | Bench and stimulus (122e88a touches only |
Question | 122e88a says |
Notice what the package does not say: "the bug is the missing clamp". It states the first wrong value, the commit that changed it and the question only the owner can answer. The receiver may know a reason the clamp was removed that the giver does not.
Next morning, the power-domain owner builds the package's commit in a new folder and runs the three commands it names:
$ R=$(mktemp -d)/recv && build-history.sh $R > /dev/null && cd $R && git checkout -q 49c5f04
$ tb/run.sh periph_power 1; echo "exit $?"
dbglab: test=periph_power seed=1
FAIL test=periph_power seed=1 cycle=431: STREAM: pstream_valid is unknown
exit 1
$ tb/run.sh periph_power 1 verilator
dbglab: test=periph_power seed=1
FAIL test=periph_power seed=1 cycle=2421: WATCHDOG: no bus or stream progress for 2000 cycles; cpu_valid=0 cpu_ready=0 cpu_addr=00003008
$ run-probe.sh pd_probe.sv periph_power 1 +FROM=72 +TO=87
dbglab: test=periph_power seed=1
ns cycle pwr_on iso_en rinc rempty rbin
722 72 1 1 0 1 04
759 76 0 1 x 1 04
796 80 0 1 x 1 04
833 83 0 1 x 0 04
870 87 0 1 x x xx
FAIL test=periph_power seed=1 cycle=431: STREAM: pstream_valid is unknownSame lines, same cycle, same first wrong value, on a tree built from nothing. Now the tracker's owner changes, with a note: accepted, first wrong cycle reproduced, step: Fix. The receiver starts from cycle 76 and one line of RTL.
The package above is an RTL-to-RTL hand-off inside simulation. Across disciplines the fields stay the same, but what "replay" and "first wrong cycle" mean changes:
From, to | Replay means | Extra fields |
|---|---|---|
Verification to design | commit, test, seed, simulator | the checker that fired and why it is trusted (when the bug is in the test) |
Design to physical design or timing | netlist and constraints version, corner, the path or endpoint | the functional symptom that made you suspect timing (timing-only bugs) |
Lab to design | part identifier, board, voltage, frequency, temperature, the failing pattern | register reads and scan dump at the failure (first failing test on silicon) |
Design to lab | the experiment to run on the bench and the result that would decide it | how to read the debug registers that answer it (bring-up failure triage) |
The receiver spent three commands and a minute to get exactly where the giver stopped, and took the bug with nothing to ask. The package cost the giver perhaps twenty minutes, most of it already in their log. Compare that with the three-day thread: the difference is not effort, it is whether the evidence travels.
A different commit or a dirty tree on either side, a different simulator or version, or an input the package forgot (an environment variable such as XRAND, a plusarg, a local edit). The giver still owns it: the hand-off failed its first check. The fix is a better replay record, then a new offer.
Because the giver has localised the first wrong value, not proven the cause or the right fix. The owner knows the power intent, whether other outputs were also changed, and whether the commit's reasoning was right for some other configuration. A package that states facts and a question lets them decide; a proposed fix invites a quick yes that skips their knowledge.
dbglab at HEAD 49c5f04 (dbglab/build-history.sh), 2026-10-18. tb/run.sh periph_power 1 (Icarus Verilog 13.0) and the same with verilator (Verilator 5.048) give the two FAIL lines shown; seeds 1 to 20 fail in both. pd_probe.sv as shown, compiled with iverilog -g2012 -Wno-timescale -I tb -o build/probe.vvp <rtl/files.f> tb/bus_rules.sv tb/dbglab_tb.sv pd_probe.sv and run with vvp -n build/probe.vvp +TEST=periph_power +SEED=1 +FROM=70 +TO=95.
git bisect start HEAD v0.1; git bisect run tb/run.sh periph_power 1; git bisect log (git 2.54.0) as shown; git show 122e88a as quoted. DUMP=1 tb/run.sh periph_power 1 wrote a 55,057-byte dbglab.vcd. Acceptance: a fresh build-history.sh into a new temporary folder, git checkout -q 49c5f04, the repro, the Verilator repro and the probe with +FROM=72 +TO=87, output as shown (script paths shortened in the listing).
Limits: the people, teams and the earlier three-day hand-off are illustrative. Hand-offs to physical design and to the lab are described, not run: there is no netlist flow or silicon here. The package records one bug; it does not prove the receiver's hypothesis.