Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Part 0 · What Debugging Is6
  • Part 1 · Reproduce8
  • Part 2 · Observe9
  • Part 3 · Localise10
  • Part 4 · Hypothesise and Test6
  • Part 5 · Bug Classes16
  • Part 6 · Debug by Stage9
  • Part 7 · Hangs and Deadlocks7
  • Part 8 · Intermittent Failures7
  • Part 9 · Fix, Prove, Prevent7
  • Part 10 · Case Studies12
    • The Off-by-One That Fails at Wrap
    • The Reset Value That Changed
    • The X That Hid for Thousands of Cycles
    • The Race Two Simulators Disagree On
    • The Bus Value That Was Never Sent
    • The Response to the Wrong Master
    • The Deadlock Under Back-Pressure
    • The Credit Leak That Hangs After an Hour
    • The 2x Throughput Regression
    • The Missing Isolation
    • The Test-Mode Leak
    • The Testbench That Was Wrong
  • Part 11 · Debug as a Team5
  • Part 12 · Interview Prep5
Loading lesson

‹Part 10 · Case Studies
  1. Topics
  2. The Art of Hardware Debug
  3. Part 10 · Case Studies
  4. The X That Hid for Thousands of Cycles

Part 10 · Case Studies · Working (W)

The X That Hid for Thousands of Cycles

Nine seeds fail in Icarus with xxxxxxxx and pass in the Verilator nightly. A real dbglab hunt from that report: why the fast simulator hides it, the word written at cycle 514 and read at 7947, a register nobody wrote, a check that fires at the start of the run, and seeds where the X is never seen at all.

On this page

    Why this matters

    An unreset register can poison data that sits unread for thousands of cycles, and a two-state simulator turns the X into a plausible zero, so the bug looks like a simulator quirk. Calling it that ships a part whose behaviour depends on power-up state.

    After this lesson you can: treat a failure seen in one simulator as real until proven otherwise, walk an X from the read back to the register that was never written, and place a check where the X enters, not where it is read.

    Note

    Level: Working (W) · Debug-loop step: The whole loop (won at Observe: the bench's own message named the run)

    Before this: The Debug Loop, X in a Waveform

    The report

    The project runs its nightly random sweep in Verilator. A verification engineer reruns the same sixty seeds in Icarus before a release and files this:

    Terminal
    $ grep xxxxxxxx ../runs/icarus60.txt
    FAIL test=random seed=16 cycle=686: SCOREBOARD: RAM word 759 (address 00001bdc) read xxxxxxxx, expected 00000000 (last written by DMA run #3)
    FAIL test=random seed=17 cycle=7949: SCOREBOARD: RAM word 816 (address 00001cc0) read xxxxxxxx, expected 00000000 (last written by DMA run #4)
    FAIL test=random seed=19 cycle=415: SCOREBOARD: RAM word 984 (address 00001f60) read xxxxxxxx, expected 00000000 (last written by DMA run #3)
    FAIL test=random seed=23 cycle=2385: STREAM: word #41 = xxxxxxxx, expected 00000000
    FAIL test=random seed=29 cycle=1820: SCOREBOARD: RAM word 542 (address 00001878) read xxxxxxxx, expected 00000000 (last written by DMA run #8)
    FAIL test=random seed=31 cycle=5693: SCOREBOARD: RAM word 652 (address 00001a30) read xxxxxxxx, expected 00000000 (last written by DMA run #3)
    FAIL test=random seed=41 cycle=7656: SCOREBOARD: RAM word 846 (address 00001d38) read xxxxxxxx, expected 00000000 (last written by DMA run #41)
    FAIL test=random seed=43 cycle=2793: SCOREBOARD: RAM word 444 (address 000016f0) read xxxxxxxx, expected 00000000 (last written by DMA run #7)
    FAIL test=random seed=51 cycle=1155: SCOREBOARD: RAM word 929 (address 00001e84) read xxxxxxxx, expected 00000000 (last written by DMA run #7)

    Nine seeds read an unknown value where the model expects zero. None of the nine shows this failure in the Verilator nightly. (Seeds 16 and 51 fail there too, but with a different message: that is another bug, the off-by-one that fails at wrap.) The first comment on the ticket: "Icarus X pessimism, Verilator is fine, low priority".

    Stop here if you want to try it yourself. Is the comment right? If not, which seed would you take, and what in the message tells you where to start?

    Check yourself Want a hint before you read the hunt?

    Every line expects 00000000, and every line names the DMA run that last wrote the word. Find out what kind of DMA run writes zeros, and where it gets the value it writes.

    The hunt

    This hunt turns at Reproduce (a pass that proves nothing), is won at Observe (the message names the run) and is closed at Localise (the register nobody wrote).

    Reproduce: a pass that proves nothing

    Seed 17 is the one from the report with the longest wait. Run it in both simulators:

    Terminal
    $ tb/run.sh random 17
    dbglab: test=random seed=17
    FAIL test=random seed=17 cycle=7949: SCOREBOARD: RAM word 816 (address 00001cc0) read xxxxxxxx, expected 00000000 (last written by DMA run #4)
    $ tb/run.sh random 17 verilator
    dbglab: test=random seed=17
    PASS test=random seed=17 cycles=18442 time=184416 ns

    Here is the trap the ticket fell into. Verilator is a two-state simulator: a flip-flop with no reset starts at 0, never X. If the missing value happens to be 0, and this one should be 0, the run passes. That pass does not say "no bug". It says "no bug if every unreset flop powers up at zero", which no silicon promises. The test is to give Verilator random start values instead (XRAND=1 builds with --x-initial unique and runs with +verilator+rand+reset+2):

    Terminal
    $ XRAND=1 tb/run.sh random 17 verilator
    dbglab: test=random seed=17
    FAIL test=random seed=17 cycle=7949: SCOREBOARD: RAM word 816 (address 00001cc0) read 5d88e2dd, expected 00000000 (last written by DMA run #4)

    Same word, same cycle, and now a random number where Icarus showed X. The bug is real in both simulators; Verilator's default start values hid it. "Blame the tool" is a hypothesis like any other, and it just failed its test (confirmation bias and blaming the tool covers how to check that claim properly). The repro is tb/run.sh random 17, deterministic, under a second.

    Observe: the write, not the read

    The failing read happens at cycle 7949. Opening a waveform there shows the CPU reading an X from RAM, which you already knew. The message says more: the word was last written by DMA run #4. So the question is what run #4 wrote, and when. dma_runs.sv, a probe compiled next to the bench that changes nothing, prints each DMA run and every bus transfer to one address:

    Terminal
    $ ../probes/run-probe.sh ../probes/dma_runs.sv random 17 +ADDR=00001cc0
    dbglab: test=random seed=17
    DMA run #1  cycles 30-209  ctrl=0e len=22 src=00001874 dst=00003000  wrote 22 words
    DMA run #2  cycles 291-310  ctrl=03 len=2 src=000019d8 dst=0000140c  wrote 2 words
    DMA run #3  cycles 388-421  ctrl=03 len=4 src=00001784 dst=00001124  wrote 4 words
    cycle 514  DMA write 00001cc0 data=xxxxxxxx
    DMA run #4  cycles 483-526  ctrl=12 len=9 src=00001344 dst=00001ca8  wrote 9 words
    ...
    DMA run #51  cycles 7752-7931  ctrl=02 len=22 src=00001a50 dst=000010dc  wrote 22 words
    cycle 7947  CPU read  00001cc0
    FAIL test=random seed=17 cycle=7949: SCOREBOARD: RAM word 816 (address 00001cc0) read xxxxxxxx, expected 00000000 (last written by DMA run #4)
    • Run #4 has `ctrl=12`: bit 4 is fill and bit 1 is err_ie. A fill run reads nothing; it writes the FILL register's value to every destination word. Zeros expected means a zero fill.

    • Cycle 514: the DMA writes xxxxxxxx to 0x1cc0, the seventh word of the nine-word fill starting at 0x1ca8.

    • Cycles 527 to 7946: 47 more DMA runs and thousands of CPU operations, and not one touches 0x1cc0. The X just sits in RAM.

    • Cycle 7947: the CPU reads it, and the scoreboard fails two cycles later, at 7949. The read comes 7,433 cycles after the bad write (514 to 7947).

    Why did nothing complain at cycle 514? The bench has a valid/ready rule checker on both bus masters, and it does look for X:

    Terminal
    $ grep -n "isunknown" tb/bus_rules.sv
    27:      if ($isunknown(valid) || (valid && ($isunknown(addr) || $isunknown(write))))
    47:  a_r1: assert property (@(posedge clk) disable iff (!rst_n) valid |-> !$isunknown({addr, write}));

    It checks valid, addr and write, not the write data. An X can cross the bus as data and nobody looks until a reader compares it with the model. That gap is why the scoreboard is the first thing to notice (scoreboards report late measures this gap).

    Localise: a register nobody wrote

    Walk the data back one hop at a time (the first wrong cycle has the method). In fill mode the DMA's write data comes from wdata_q, loaded from fill_val_q, which samples the CSR block's FILL register at START. So: was FILL ever written before run #4 started at cycle 483? A second probe prints the first X write by either master and every CPU write to FILL (0x2024):

    Terminal
    $ ../probes/run-probe.sh ../probes/x_writes.sv random 17 | head -4
    dbglab: test=random seed=17
    cycle 488  first X write: DMA, address 00001ca8, during DMA run #4
    cycle 807  CPU writes FILL = 3578a189
    cycle 1089  CPU writes FILL = 1449e86a

    The first X on the bus is run #4's first word, at cycle 488. The first write to FILL comes at cycle 807, after the run. So at cycle 483 FILL held whatever it powered up with. Look at how the bench zeroes a buffer, then at the register:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: tb/tests.svh lines 194-198 (the zero-fill branch of r_start_dma) and rtl/csr_block.sv lines 55-65
      else if (k < 25) begin                                             // zero a buffer
        r_fill = 1'b1;
        if (m_fill != 32'h0) csr_wr(CSR_FILL, 32'h0);
        mode = 8'h1 << CTRL_FILL;
      end
    
      // Datapath registers: always programmed before START, so they need no reset.
      always_ff @(posedge clk)
        if (wr) begin
          case (off)
            CSR_SRC:  src_q  <= wdata;
            CSR_DST:  dst_q  <= wdata;
            CSR_LEN:  len_q  <= wdata[9:0];
            CSR_FILL: fill_q <= wdata;
            default: ;
          endcase
        end

    Two reasonable decisions collide. The bench writes FILL only when its model says FILL is not already 0, trusting the register map's "FILL resets to 0". The RTL comment says the datapath registers are "always programmed before START". That is true for SRC, DST and LEN, which dma_program writes every time, and false for FILL. FILL has no reset, so in Icarus it is X until first written; in silicon it is whatever it powers up as.

    Hypothesise and test: predict every seed

    Hypothesis: FILL has no reset, and a zero fill that starts before any write to FILL writes FILL's power-up value. Predictions: (1) a check at START that a fill run's FILL is known fires on run #4 of seed 17, at cycle 483; (2) over many seeds, the seeds where that check fires are exactly the seeds that write an X to the bus; (3) since nothing makes the X get read soon, the wait between write and failure should vary wildly, and some X words should never be read at all.

    fill_start_check.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // not-run: needs dbglab's sources and bench; compile it next to them (command in the Evidence box)
    // fill_start_check: a fill run must not start with an unknown FILL value.
    `timescale 1ns/1ps
    module fill_start_check;
      always @(posedge dbglab_tb.clk)
        if (dbglab_tb.dut.dma_start && dbglab_tb.dut.dma_ctrl[4] && $isunknown(dbglab_tb.dut.dma_fill)) begin
          $display("FAIL cycle=%0d: FILL_CHECK: DMA run #%0d starts a fill with FILL=%h",
                   dbglab_tb.cycle, dbglab_tb.n_dma + 1, dbglab_tb.dut.dma_fill);
          $fatal(1, "fill check");
        end
    endmodule
    Terminal
    $ ../probes/run-probe.sh ../probes/fill_start_check.sv random 17
    dbglab: test=random seed=17
    FAIL cycle=483: FILL_CHECK: DMA run #4 starts a fill with FILL=xxxxxxxx

    Prediction 1 holds: cycle 483, the cycle run #4 starts, five cycles before the first bad write (488) and 7,466 cycles before the scoreboard reports at 7949. For predictions 2 and 3, sweep 300 seeds. Use a copy of HEAD with the off-by-one bug fixed (CW = 10), so its failures cannot mix in. Record the first X write and the failure line for each seed, then run the check on the same seeds:

    Terminal
    $ for s in $(seq 1 300); do ../probes/run-probe.sh ../probes/x_writes.sv random $s | grep -E "first X|^FAIL" | tr '\n' ' ' | sed "s/^/$s /"; echo; done | grep "first X" > ../runs/xgap300.txt
    $ for s in $(seq 1 300); do ../probes/run-probe.sh ../probes/fill_start_check.sv random $s | grep FILL_CHECK | sed "s/^/$s /"; done > ../runs/fillchk300.txt
    $ wc -l < ../runs/fillchk300.txt; diff <(cut -d' ' -f1 ../runs/fillchk300.txt) <(cut -d' ' -f1 ../runs/xgap300.txt) && echo "same 41 seeds"
          41
    same 41 seeds

    Prediction 2 holds: 41 seeds write an X, and they are exactly the 41 where a fill starts with FILL unknown. There is no second source of X. A short script measures how long each X waited, from the seed's first X write to the failing check's report (for seed 17, 488 to 7949: 7,461 cycles; the failing word itself is the later write at 514):

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # not-run: reads the sweep file written by the x_writes probe (commands above)
    # gaps.py: from x_writes probe lines (one per seed), how long each X waited before a check saw it.
    import re, sys
    gaps, silent = [], []
    for line in open(sys.argv[1]):
        seed, w = re.match(r"(\d+) cycle (\d+)  first X write", line).groups()
        f = re.search(r"FAIL test=random seed=\d+ cycle=(\d+)", line)
        if f: gaps.append((int(f.group(1)) - int(w), int(seed)))
        else: silent.append(int(seed))
    gaps.sort()
    print(f"seeds that write an X: {len(gaps) + len(silent)}; fail: {len(gaps)}; pass anyway: {len(silent)} {silent}")
    for lo, hi in [(0, 100), (100, 1000), (1000, 5000), (5000, 10**6)]:
        print(f"  X seen {lo:>5}-{hi - 1 if hi < 10**6 else '':<5} cycles after it was written: {sum(lo <= g < hi for g, _ in gaps)}")
    print(f"  shortest {gaps[0][0]} (seed {gaps[0][1]}), median {gaps[len(gaps) // 2][0]}, longest {gaps[-1][0]} (seed {gaps[-1][1]})")
    Terminal
    $ python3 -I gaps.py runs/xgap300.txt
    seeds that write an X: 41; fail: 37; pass anyway: 4 [86, 105, 128, 154]
      X seen     0-99    cycles after it was written: 0
      X seen   100-999   cycles after it was written: 14
      X seen  1000-4999  cycles after it was written: 15
      X seen  5000-      cycles after it was written: 8
      shortest 127 (seed 19), median 1730, longest 19708 (seed 293)
    Three seeds, three hiding times: seed 17's failing word is written at cycle 514 and read at 7947, 7,433 cycles later; seed 293's waits 19,708 cycles for the final RAM sweep; seed 86's is overwritten and the run passes.
    • No X is seen within 100 cycles. The shortest wait is 127 cycles. Every report lands far from its cause.

    • Seed 293: the X is written at cycle 882 and found only by the RAM sweep at the end of the test, 19,708 cycles later. Without that sweep, this seed passes.

    • Seeds 86, 105, 128 and 154 pass. They write an X into RAM, and later traffic overwrites it before anything reads it. A green run here is not evidence that the design never wrote an X.

    Prediction 3 holds too. Now history: when did FILL lose its reset? Bisect would work (bisection in history bisects this exact bug), but here the suspect is one line, so ask git which commits added or removed it:

    Terminal
    $ git log -S'fill_q <= FILL_RST' --oneline
    b357bd8 csr: no reset on the DMA address and data registers
    bc2bb34 dma: fill mode
    $ git show b357bd8 -- rtl/csr_block.sv
    ...
        SRC, DST, LEN and FILL are always programmed before START, so their
        reset only costs area. Control and status keep their reset.
    ...
    -      src_q <= SRC_RST; dst_q <= DST_RST; len_q <= LEN_RST;
    -      fill_q <= FILL_RST;
    ...

    Commit 12 added FILL with its reset; commit 14 removed it, with a reason that is true for three of the four registers. git log -S (the "pickaxe") lists the commits that change how many times a string appears. When you already know the line, it is faster than bisect and needs no test at all.

    The fix and the lesson

    Fix: give FILL its reset back

    design.sv
    // snippet: the fix in rtl/csr_block.sv: FILL joins the reset block (two lines), and leaves the unreset one
          err_q <= 1'b0; err_addr_q <= '0; fill_q <= FILL_RST;
              CSR_FILL:    fill_q    <= wdata;

    The alternative, making the bench always write FILL before a zero fill, would make the tests pass and leave the register map's promise broken for every driver that trusts it. The area saving was for registers that really are always written. FILL is not one of them, because the documented reset value is part of how software uses it.

    Prove: three simulators' worth of evidence

    Terminal
    $ tb/run.sh random 17 && tb/run.sh random 17 verilator && XRAND=1 tb/run.sh random 17 verilator
    dbglab: test=random seed=17
    PASS test=random seed=17 cycles=18442 time=184416 ns
    dbglab: test=random seed=17
    PASS test=random seed=17 cycles=18442 time=184416 ns
    dbglab: test=random seed=17
    PASS test=random seed=17 cycles=18442 time=184416 ns
    $ (for s in $(seq 1 60); do tb/run.sh random $s | grep FAIL; done) > ../runs/b4fix-icarus60.txt
    $ wc -l < ../runs/b4fix-icarus60.txt; diff ../runs/b4fix-icarus60.txt ../runs/vl60.txt && echo "same as the Verilator sweep at HEAD"
          12
    same as the Verilator sweep at HEAD

    Seed 17 passes in four-state Icarus, in two-state Verilator and with random start values. After the fix, Icarus fails exactly the twelve seeds the Verilator nightly failed at HEAD, line for line: the off-by-one bug, and nothing else. With both fixes applied, a copy fails none of 300 seeds in Icarus and none of 60 in Verilator with random start values, and the fill check stays silent on all 300.

    Prevent: check where the X enters

    • A check at the point of entry. fill_start_check fires at the START of the bad run and names the register. As an assertion in the DMA or CSR checker it turns a 7,000-cycle hunt into a one-line message. A general rule that would also work: no X on bus write data ($isunknown(wdata) when valid && write), the check bus_rules.sv leaves out. dbglab's bench is left unchanged here; the assertion that would have caught it shows how to write and prove such a check.

    • Run the nightly where X can appear. Keep a four-state simulator in the loop, or run the two-state one with random start values on part of the seeds. A two-state-only regression cannot see this class of bug.

    • Review every removed reset. The question: is this register written on every path before it is used, including paths that rely on its documented reset value? A lint rule that lists unreset flops makes the list to review; the localisation checklist produces one with Yosys.

    Which step won this hunt? Observe, by reading the message to its end: "last written by DMA run #4" moved the search from cycle 7949 to cycle 483 in one probe run. The step that nearly lost it was Reproduce, where a passing run in the other simulator was taken as evidence.

    Common pitfall

    Making Verilator the only simulator. It is faster and passes more seeds, so it is tempting to drop Icarus. In this case that hides a bug that silicon would show as random data in a buffer that software expects to be zero. Two-state speed is fine for most of the sweep; keep some runs where an unknown stays unknown.

    Why this matters

    Senior lens. Severity is not frequency. This bug fails nine seeds in sixty in one simulator and none in the other, and it would ship as a part that zeroes buffers correctly only on boards whose flops happen to power up at zero. Rate it by what silicon would do, not by how often the regression notices (bug tracking and severity uses this very bug).

    Removing resets to save area is a real and good optimisation, but it moves a guarantee from the hardware into a rule about software. Write that rule down where software engineers read it, and keep a check that enforces it in simulation.

    Interview

    Asked: "A test fails in one simulator with an X and passes in the other. Your colleague says it is X pessimism and wants to waive it. What do you do?"

    Weak answer: X pessimism is common in four-state simulators, so I would check the waveform, and if the X looks like it comes from simulation semantics, waive it and rely on the simulator that passes.

    Strong answer: I treat the pass as unproven, because a two-state simulator turns X into 0 and the expected value may be 0. I rerun the passing simulator with random initial values; if it now fails at the same place with a random value, the bug is real. Then I go to the write, not the read: the scoreboard said which DMA run wrote the word, so I look at that run's source of data and find a register with no reset that the run used before anything wrote it. I predict which seeds fail from that and check it over a sweep, fix the reset, and add an X check where the value enters the datapath so the next one fails at the start of the run, not thousands of cycles later.

    Follow-ups: (1) When is X pessimism real, and how would you show it? (2) How would you choose which registers can safely lose their reset?

    Check yourself After the fix, someone proposes deleting the final RAM sweep at the end of the test because it 'never finds anything the reads do not'. What does this case study say?

    Seed 293 says otherwise: its X was found only by the final sweep, 19,708 cycles after it was written; without the sweep that seed passes. A late check is still a check. The better answer is an early check where X enters, plus the sweep as a backstop, not one instead of the other.

    Check yourself In seed 86 the design writes X into RAM and the test passes. Is that seed useful for proving the fix?

    Not as a pass/fail test: it passes before and after the fix, so it cannot tell them apart. It is useful with a check at the point of entry. fill_start_check fires on seed 86 at HEAD with B3 fixed (it is one of the 41) and stays silent after the fix. Prove a fix with checks that fail on the bug, not with runs that happen to pass.

    Remember

    • A pass in a two-state simulator says nothing about X. Rerun with random initial values before you call a four-state failure a quirk.

    • Read the scoreboard's message for who last wrote the word. The write, not the read, is where the hunt starts.

    • An X can sit in memory for any length of time, or be overwritten unseen. A clean run is not proof that no X was written.

    Rule of thumb: check for X where a value enters the datapath, not where it is finally read.

    How this was checked

    dbglab at HEAD 49c5f04 (built with dbglab/build-history.sh), 2026-10-18, Icarus Verilog 13.0, Verilator 5.048, git 2.54.0. random seeds 1 to 60 in Icarus: 19 failures, 9 with xxxxxxxx; in Verilator: 12 failures, none with X. tb/run.sh random 17: FAIL at cycle 7949, xxxxxxxx; tb/run.sh random 17 verilator: PASS; XRAND=1 tb/run.sh random 17 verilator: FAIL at cycle 7949, 5d88e2dd.

    Probes compiled next to dbglab in Icarus with run-probe.sh (iverilog -g2012 -Wno-timescale -I tb -o build/probe.vvp <rtl/files.f> tb/bus_rules.sv tb/dbglab_tb.sv <probe>; vvp -n build/probe.vvp +TEST=random +SEED=<s>): dma_runs.sv +ADDR=00001cc0 (DMA writes X to 0x1cc0 at cycle 514 in run #4; next access is the CPU read at 7947); x_writes.sv (first X write at 488, first FILL write at 807); fill_start_check.sv fires at cycle 483. git log -S'fill_q <= FILL_RST' --oneline: bc2bb34, b357bd8.

    On a copy of HEAD with B3 fixed (CW = 10), seeds 1 to 300: 41 seeds write an X, 37 fail (all xxxxxxxx; gaps 127 to 19,708 cycles, median 1730), 4 pass; fill_start_check.sv fires in the same 41 seeds; Verilator fails none of the 300.

    Fix on a copy of HEAD (FILL moved into the reset block): seed 17 passes in Icarus, Verilator and Verilator with XRAND=1; Icarus seeds 1 to 60 fail exactly the 12 lines the Verilator sweep failed at HEAD. With both fixes: Icarus seeds 1 to 300 and XRAND Verilator seeds 1 to 60, 0 failures; the fill check is silent on 300 seeds. Limits: simulation only; the bench's bus checker does not check write data for X (a known gap, not changed here).

    Keep going

    • Practice: X and Z in simulation and the mismatches they cause

    • Practice: Inline checkers: X detection and handshake liveness

    • Practice: Reset strategies in RTL

    • Code lab: X-propagation configuration

    • Code lab: DFF with asynchronous reset

    • Interview: run a mock interview on this topic

    Related topics

    • Part 10 · Case Studies

    • X and reset bugs

    • X in a waveform

    • Symptom, fault and cause (this bug's chain)

    • Case study: the off-by-one that fails at wrap

    • Case study: the reset value that changed

    Previous

    88 / 107

    Next: The Race Two Simulators Disagree On

    Up next

    Up nextThe Race Two Simulators Disagree On

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The Off-by-One That Fails at Wrap
    • The Bus Value That Was Never Sent
    • The Response to the Wrong Master