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

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
    • Simulation for Sign-off
      • Gate-Level Simulation with a UVM Bench
      • Reset and X-Propagation Verification
      • Power-Aware Simulation with UPF
    • Safety, Acceleration and Models
    • Beyond Simulation
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Simulation for Sign-off
  1. Topics
  2. UVM Verification
  3. Verification in Industry Practice
  4. Simulation for Sign-off
  5. Reset and X-Propagation Verification

Part 12 · Industry Practice · Intermediate

Reset and X-Propagation Verification

X-optimism in RTL versus X-pessimism in gates, shown on a four-state simulator; async-assert sync-deassert reset, reset test plans, no-X assertions that fire, and X-propagation modes.

On this page

    A team tapes out a block whose RTL regression ran 40,000 seeds without a failure. On the first silicon, the block works on most boards and hangs on a few, and on the same board it hangs on some power-ups and not others. The root cause is one state flop that comes out of reset as X on the netlist, because its reset came from a different reset branch that released one cycle late. RTL simulation turned that X into a clean 0 on every one of the 40,000 runs. How can a simulator hide an unknown, and how do you make it stop?

    The idea: X means "the simulator does not know", and RTL guesses

    In a four-state simulator a bit is 0, 1, Z (undriven) or X (unknown). X appears wherever real hardware has a value but simulation cannot say which: a flop before reset, an undriven input, two drivers fighting, an out-of-range array read, a timing violation, a powered-down domain. X is the simulator's honest way of saying "this could be 0 or 1 on silicon".

    The trouble is that RTL constructs do not treat X honestly. An if (c) statement with c = X takes the else branch, because the condition is not true. A case statement with an X selector matches no item and runs the default. Either way the simulator picks a branch and the X disappears, producing a clean value that silicon may not produce. That is X-optimism: RTL simulation is more optimistic than hardware. Gates do the opposite. An AND-OR mux with X on its select outputs X even when both data inputs are equal, because each gate only sees its own inputs. That is X-pessimism: gate simulation reports unknowns that silicon would never show.

    A useful analogy is a weather forecaster who does not know whether it will rain. The optimistic forecaster says "sunny" (the else branch). The pessimistic one says "unknown" even when every cloud is above the next town. The honest one says "unknown" only if the two outcomes would actually differ. The RTL ?: operator happens to be the honest one: with an X condition it merges both results bit by bit and gives X only where they differ.

    Build it: one mux, four ways

    This four-state model writes the same 2:1 mux as RTL if, RTL case, the RTL conditional operator and AND-OR gates, and puts three no-X checks around them: on the RTL output, on the gate output, and on the select itself.

    xprop_demo.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // not-run: needs a four-state simulator (run with Icarus)
    // The same 2:1 mux four ways, driven with an unknown select.
    module xprop_demo;
      logic sel;
      logic a, b;
      logic y_if, y_case, y_tern;
      wire  y_gate, sel_n, t0, t1;
    
      always_comb begin                    // RTL: if/else
        if (sel) y_if = a;
        else     y_if = b;
      end
    
      always_comb begin                    // RTL: case
        case (sel)
          1'b1:    y_case = a;
          default: y_case = b;
        endcase
      end
    
      assign y_tern = sel ? a : b;         // RTL: conditional operator
    
      not g0 (sel_n, sel);                 // gate level: AND-OR
      and g1 (t1, sel, a);
      and g2 (t0, sel_n, b);
      or  g3 (y_gate, t1, t0);
    
      task automatic show(input logic s, input logic va, input logic vb);
        sel = s; a = va; b = vb; #1;
        $display("sel=%b a=%b b=%b | if=%b case=%b ?:=%b gates=%b", sel, a, b, y_if, y_case, y_tern, y_gate);
        // no-X checks: on the RTL output, on the gate output, and on the select itself
        assert (!$isunknown(y_if))   else $error("y_if is unknown");
        assert (!$isunknown(y_gate)) else $error("y_gate is unknown");
        assert (!$isunknown(sel))    else $error("sel is unknown");
      endtask
    
      initial begin
        show(1'b0, 1'b1, 1'b0);
        show(1'b1, 1'b1, 1'b0);
        show(1'bx, 1'b1, 1'b0);            // select unknown, inputs differ
        show(1'bx, 1'b1, 1'b1);            // select unknown, inputs equal
      end
    endmodule

    Watch it run

    Diagram
    $ iverilog -g2012 -o x.vvp xprop.sv && vvp -n x.vvp      (Icarus 13.0)
    sel=0 a=1 b=0 | if=0 case=0 ?:=0 gates=0
    sel=1 a=1 b=0 | if=1 case=1 ?:=1 gates=1
    sel=x a=1 b=0 | if=0 case=0 ?:=x gates=x
    ERROR: xprop.sv:32: y_gate is unknown
           Time: 3  Scope: xprop_demo.show
    ERROR: xprop.sv:33: sel is unknown
           Time: 3  Scope: xprop_demo.show
    sel=x a=1 b=1 | if=1 case=1 ?:=1 gates=x
    ERROR: xprop.sv:32: y_gate is unknown
           Time: 4  Scope: xprop_demo.show
    ERROR: xprop.sv:33: sel is unknown
           Time: 4  Scope: xprop_demo.show

    Read the third line carefully. With an unknown select and different inputs, if and case both output a confident 0: optimism. ?: and the gates output X, which is correct, because silicon could produce either value. On the fourth line the inputs are equal, so silicon would produce 1 whatever the select is; ?: says 1, and the gates still say X: pessimism. Now look at the checks. The assertion on y_if never fired, even though the select was unknown twice. A no-X check on an RTL output cannot see an X that an if already swallowed. The check that caught the problem in RTL is the one on the control signal sel. That is the practical rule: put no-X assertions on state, selects, enables and valid signals, not only on data outputs.

    Reset: async assert, sync deassert, and in the right order

    Most X in a design should die at reset, so reset is where X verification starts. The common scheme is asynchronous assertion, synchronous deassertion: reset reaches every flop at once, whether or not the clock runs, but its release is retimed through a two-flop reset synchroniser in each clock domain, so every flop in that domain leaves reset on the same clock edge. Releasing reset asynchronously risks a recovery/removal violation: some flops see the release before an edge and some after, and the state machine starts in a state that is neither.

    Sequence diagram: the reset source asserts reset asynchronously to domain A flops, releases it to the synchroniser, which after two clock edges releases the flops; the bench then enables no-X checks and starts traffic.
    Assertion is immediate and asynchronous; release is synchronised per domain. The bench's no-X checks switch on only after the synchronised release, and traffic starts after the settling time the spec gives.

    What a reset test plan covers, beyond "reset at time 0 works":

    • Reset values of every register, front-door. The course bench does this with uvm_reg_hw_reset_seq in dma_ral_reset_test, which logs reset values checked (registers whose fields are all volatile are skipped) with 0 errors.

    • Reset in the middle of traffic (warm reset): drivers park, monitors drop partial transfers, the scoreboard flushes. The course's dma_reset_test asserts reset in the middle of job 1 and then runs a full job; the pattern is in Reset on the Fly.

    • Reset ordering between domains and blocks, and reset released while a clock is stopped or not yet stable.

    • Reset domain crossings (RDC): a flop reset by one reset feeding a flop on another reset can go metastable when the first reset asserts while the second domain runs. RDC is checked structurally by tools and covered in the Reset Domain Crossing section of the CDC/RDC course, with the simulation side in Reset-aware Simulation.

    No-X checks after reset, as SVA

    Write the checks so they are quiet during reset and qualified by valid signals, then bind them to the DUT's ports (ports survive into the netlist, so the same file runs in GLS):

    dma_xcheck.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // snippet: no-X properties on dma_dut ports, bound in RTL and GLS alike
    module dma_xcheck (input logic clk, rst_n, psel, penable, pready, pslverr,
                       input logic [31:0] prdata, input logic m_tvalid, m_tlast,
                       input logic [31:0] m_tdata, input logic s_tready, irq);
      // control outputs: known on every clock once reset is released
      a_ctrl_known: assert property (@(posedge clk) disable iff (!rst_n)
        !$isunknown(pready) && !$isunknown(s_tready) && !$isunknown(m_tvalid) && !$isunknown(irq));
      // data outputs: known only when their qualifier says they are valid
      a_prdata_known: assert property (@(posedge clk) disable iff (!rst_n)
        (psel && penable && pready) |-> !$isunknown({prdata, pslverr}));
      a_tdata_known: assert property (@(posedge clk) disable iff (!rst_n)
        m_tvalid |-> !$isunknown({m_tdata, m_tlast}));
    endmodule
    bind dma_dut dma_xcheck u_xcheck (.*);

    Two details matter. disable iff (!rst_n) stops the properties during reset; if reset itself is X at time 0, the property is not disabled, so make the bench drive reset to a known value from time 0. And these properties still only see X that reaches a port. To find the X an if swallowed inside the RTL, you need either assertions on internal state (RTL only) or a simulator mode that stops the swallowing.

    X-propagation modes in commercial simulators

    The major commercial simulators each have an X-propagation mode, enabled with a compile option and usually scoped to chosen modules. In that mode, an if or case with an unknown condition no longer picks a branch. The simulator evaluates every branch the X could select and merges the results: bits on which all branches agree keep their value and the rest become X, which is the ?: behaviour from the demo. Most tools also offer a more pessimistic setting that sets every variable assigned in the statement to X, closer to gate behaviour, and rules for edges from or to X on clocks. The option names differ by vendor and release, so look them up in your tool's documentation rather than in a forum post.

    How projects use it: turn X-prop mode on for a subset of the RTL regression (reset, boot, low-power and register tests) once the RTL is stable, because it is slower and every new X needs triage. Each X it reveals is either a real initialisation bug, a testbench that drives X where it should not, or a harmless X that needs a waiver with a reason. Formal tools also offer X-propagation apps that try to prove no X reaches a chosen set of signals after reset, without needing a test to hit the case.

    What breaks: the check that could never fire

    A team added assert property (!$isunknown(state_q)) to every FSM after an X escape. GLS later found another X in the same block. The new assertion had never fired in RTL, and not because the design was clean: the X originated in a configuration flop without reset, passed through an if (cfg_q) into the next-state logic, and the if turned it into the else branch before it ever reached state_q. The assertion watched the result of the optimism instead of its cause. The fix was two-sided: assertions on the configuration flops (the select side, as in the demo), and the reset and boot tests rerun with X-propagation mode on for that block, which made state_q go X in RTL on the first run.

    Interview

    "What is X-optimism, and how do you deal with it on a project?"

    A weak answer: "X-optimism is when X gets lost in simulation; we run GLS to catch it." That pushes every X bug to the most expensive and latest simulation.

    A strong answer gives the mechanism and a layered plan: "In RTL, if and case with an unknown condition pick a branch, so an X disappears, while gates propagate it, sometimes pessimistically. I deal with it in layers: reset every control flop unless there is a reviewed reason not to; no-X assertions on state, selects and valid-qualified outputs after reset; reset and boot tests rerun with the simulator's X-propagation mode on; formal X checks where available; and finally a small GLS list as confirmation, not as the first line of defence."

    Follow-ups: Is `?:` optimistic too? (no: with an X condition it merges both results and gives X where they differ) and Why can't you just reset every flop? (area and power on wide datapaths; data flops are usually safe without reset if a valid bit that is reset qualifies them).

    Check yourself Your RTL bench has assert (!$isunknown(y)) on every output of a block, and it has never fired. A GLS run shows X on y. List two reasons the RTL assertion stayed quiet.

    First, an if or case inside the RTL may have converted an X condition into a clean branch before it reached y (optimism), so y really was known in RTL. Second, the X may exist only in the netlist: a flop the RTL initialised with an initial block or that synthesis restructured, or gate pessimism on a reconvergent path where silicon would be fine. Checking the select or state signals in RTL and running X-prop mode separate the first case from the second.

    Check yourself A data register in a pipeline has no reset, and a reset valid bit travels next to it. Is that safe?

    Usually yes, if every consumer qualifies the data with valid using logic that blocks it when valid is 0 (for example valid & data or a mux selected by a known valid). The X in the data then never reaches state or outputs. It becomes unsafe when something reads the data without valid, such as a parity or CRC computed over every cycle, or a debug register that software can read at any time.

    Check yourself A colleague runs the whole nightly regression with X-propagation mode on and gets 600 new failures. What would you do instead?

    Start with a targeted subset: reset, boot, register and low-power tests, on the blocks you care about, because that is where initialisation X lives. Triage each new X signature once (real bug, testbench drives X, or harmless with a written waiver), fix the testbench sources first, and widen the scope only when the subset is clean. Six hundred failures are probably a handful of root causes repeated.

    Next: reset is not the only time a domain's flops become unknown. Power-Aware Simulation with UPF turns whole domains off on purpose, and makes the bench prove the X stays inside.

    Previous

    239 / 280

    Next: Power-Aware Simulation with UPF

    Up next

    Up nextPower-Aware Simulation with UPF

    Sign in to save progress

    Practice this lessonReport an error