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. Gate-Level Simulation with a UVM Bench

Part 12 · Industry Practice · Intermediate

Gate-Level Simulation with a UVM Bench

Why GLS exists next to LEC and STA, the netlist flavours, what changes in the bench (probes, binds, drive skew, SDF and timing checks), reduced test lists, X debugging and sign-off criteria, with a real Yosys netlist of the DMA.

On this page

    Three weeks before tape-out, the gate-level regression goes red. The RTL regression has been green for a month, and equivalence checking says the netlist matches the RTL. The first failure is not even a mismatch: the bench does not compile, because a backdoor probe points at a register that synthesis merged away. You fix that, and the next run has xxxxxxxx on the read-data bus for the whole simulation. A manager asks the obvious question: if equivalence checking passed, what is this simulation for, and how long will it take to make it green?

    Why does GLS still exist when equivalence checking passes?

    Gate-level simulation (GLS) runs your testbench against the netlist: the design after synthesis (and later after DFT insertion, place-and-route and ECOs), built from library cells instead of RTL statements. Logical equivalence checking (LEC) proves mathematically that the netlist computes the same next state as the RTL for every input, and static timing analysis (STA) proves every path meets timing. Between them they cover most of what GLS used to be for, which is why modern projects run far fewer gate-level tests than they did 20 years ago.

    They do not cover everything. Think of LEC as checking a translated book against the original, sentence by sentence, assuming both start on page one. GLS is what opens the book in the middle, with some pages still blank:

    • Initialisation and X. LEC compares two designs from matching states; it does not prove that reset brings every flop out of X. A flop without reset, or a reset tree that misses a branch, is a GLS failure first.

    • Simulation-synthesis mismatch. full_case/parallel_case pragmas, incomplete sensitivity lists, initial blocks and code under translate_off can make RTL simulation behave differently from the logic synthesis built. LEC compares against the synthesis view of the RTL, so the gap can survive it.

    • Timing with real delays. An SDF-annotated simulation at slow and fast corners checks that asynchronous paths, reset release, clock-gating cells and paths that STA treated as exceptions (false paths, multicycle paths) behave as the exceptions claimed. A wrong exception is a silicon bug that STA will never flag.

    • DFT and ECO netlists. Scan insertion adds test-mode muxes and ECOs edit the netlist by hand. A short simulation of functional mode on the final netlist checks that test logic is really inactive in mission mode and that the ECO did what its ticket said.

    The netlist also changes in steps through the project, and each step gets a different flavour of GLS:

    Flowchart: RTL to synthesis netlist to DFT netlist to place-and-route netlist with SDF to ECO netlist, with dotted arrows to zero-delay GLS, scan pattern simulations, SDF GLS at corners and a reduced rerun after ECO.
    Early netlists get zero-delay or unit-delay simulation to flush out X and reset issues cheaply. Timing-annotated simulation waits for the place-and-route netlist and its SDF files, one per corner.

    Build it: the DMA as a netlist

    You can make a real netlist of the course DMA with Yosys, the open-source synthesis tool. A generic synth maps the design to Yosys's own internal cell library instead of a foundry library, which is enough to see what changes for the bench:

    terminal
    yosys -q -p "read_verilog -sv dma_dut.sv; synth -top dma_dut -flatten; opt_clean -purge; \
                   stat; write_verilog -noattr -noexpr dma_dut_gates.v"
    Diagram
    === dma_dut ===   (stat excerpt)
          384 cells
           23   $_ANDNOT_
           99   $_AND_
           99   $_DFFE_PN0P_
            4   $_DFF_PN0_
           77   $_NAND_
           48   $_XOR_
           ...

    Every flop became a cell instance ($_DFFE_PN0P_ is a flop with enable and an active-low asynchronous reset to 0). Look at two of them in the netlist, because they show the first thing that breaks a bench:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    // snippet: from dma_dut_gates.v (Yosys output, ports joined on one line)
      \$_DFF_PN0_  pready_reg /* _514_ */ (
        .C(clk), .D(_004_), .Q(pready), .R(rst_n)
      );
      \$_DFFE_PN0P_  \len_reg[0]  /* _516_ */ (
        .C(clk), .D(pwdata[0]), .E(_009_), .Q(len[0]), .R(rst_n)
      );

    The RTL had a flop called waited (the APB wait-state flag) and drove pready from it through a continuous assignment. Synthesis merged them: there is now a pready_reg and no waited at all. Any testbench reference to dut.waited (a probe, a force, an SVA bind port) stops compiling. Here is exactly that, on Icarus:

    Diagram
    probe.sv:6: error: Unable to bind wire/reg/memory `dut.waited' in `probe_tb'
    1 error(s) during elaboration.

    This netlist happens to keep key, len and count as net names, so the course's register backdoor (dma_bd_if, bound into dma_dut with ports on those three nets) and the fault campaign's force dut.key[0] would still find them. A commercial flow is less kind: registers are commonly renamed after their instance, hierarchy may be flattened or uniquified, and optimised-away nets do not exist. What survives depends on the tool's naming rules and your scripts, so check the real netlist.

    What changes in the bench

    Bench part

    RTL run

    Gate-level run

    Internal probes, force, backdoor HDL paths

    RTL signal names

    Netlist names, or disabled. Keep a path map per netlist, or limit GLS tests to front-door access.

    SVA bind into internals

    Binds to RTL modules and signals

    Bind only to ports of preserved hierarchy (or to the top's ports); internal binds go behind ifdef.

    Monitors

    May sample internal state (FSM, counters)

    Must sample ports only. A monitor that peeks at dut.state dies with the probe.

    Driver timing

    Drive at the clock edge with NBA

    Drive with an output skew after the edge. With SDF, flop clocks arrive after the clock-tree delay, so an input that changes exactly at the testbench edge looks like a hold violation.

    Delays

    Zero delay

    Zero or unit delay for early netlists; SDF min/max per corner for the routed netlist.

    Memories and macros

    RTL memory models

    Library or memory-compiler simulation models, with their own timing checks and X behaviour.

    Test list

    Thousands of seeds

    A reduced list: reset and boot, one test per mode, register access, every clock and reset domain crossing, low-power entry and exit.

    Make GLS a configuration, not a fork: one define removes internal binds and probes and adds the drive skew; sequences, scoreboard and coverage stay unchanged.

    axis_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: interface timing for GLS; the RTL build uses a zero skew
    interface axis_if (input logic clk);
      logic [31:0] tdata;
      logic        tvalid, tlast, tready;
    `ifdef GLS
      localparam time DRIVE_SKEW = 1ns;   // after the edge: clear of the flops' hold window
    `else
      localparam time DRIVE_SKEW = 0;
    `endif
      clocking drv_cb @(posedge clk);
        default input #1step output #(DRIVE_SKEW);
        output tdata, tvalid, tlast;
        input  tready;
      endclocking
    endinterface

    Watch it run: the same UVM bench on the netlist

    Before adding timing, run the bench unchanged against the netlist. This scratch run compiled dma_dut_gates.v and Yosys's cell models in place of dma_dut.sv, with the stock dma_tb.sv, on Verilator:

    Diagram
    +UVM_TESTNAME=dma_smoke_test    (scratch run: gate netlist, Verilator)
    UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
    UVM_ERROR :    0
    +UVM_TESTNAME=dma_rand_test
    UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched
    UVM_ERROR :    0
    +UVM_TESTNAME=dma_truncate_test
    UVM_INFO dma_tb.sv(761) @ 385000: uvm_test_top [TRUNC] DMA reported ERR for the short packet, as expected
    UVM_INFO dma_tb.sv(411) @ 485000: uvm_test_top.env.sb [SB] 4 words matched, 0 mismatched
    UVM_ERROR :    0

    Identical results to RTL, and that is the point to notice: Verilator is two-state and zero-delay, so this run proves no more than LEC already did. GLS earns its cost only with four-state simulation (X) and, later, timing. Run the netlist on a four-state simulator (Icarus) with a small directed bench, and one innocent testbench habit changes the picture: the APB driver parks the bus with X on paddr and pwdata between transfers, which is legal (the protocol ignores them while PSEL is low).

    design.sv
    1
    2
    3
    4
    5
    // snippet: the directed bench's APB write task (tb_gls.v); PARK_X parks the bus at X
          psel <= 0; penable <= 0;
    `ifdef PARK_X
          paddr <= 8'hxx; pwrite <= 1'bx; pwdata <= 32'hxxxxxxxx;
    `endif
    Diagram
    == RTL, PARK_X  (Icarus 13.0)
    175000 out a5a51f0f last=0
    195000 out a5a51f0e last=0
    215000 out a5a51f0d last=1
    255000 irq=1
    == gates, PARK_X
     75000 X on an output: prdata=0000000X pready=0 pslverr=0 s_tready=0 m_tvalid=0 irq=0 m_tlast=0
    115000 X on an output: prdata=XXXX0x0x pready=0 pslverr=0 s_tready=0 m_tvalid=0 irq=0 m_tlast=0
    ...
    175000 out a5a51f0f last=0
    175000 X on an output: prdata=XXXX0x0x pready=0 pslverr=0 s_tready=1 m_tvalid=1 irq=0 m_tlast=0

    The data is still right in both (the same three words and the interrupt), but in the netlist prdata turns unknown whenever paddr is unknown. In the RTL, case (paddr) with an X selector matches no item and takes default: prdata = 0, so RTL showed a clean zero. In the netlist the address decoder is AND/OR gates, and X on an input propagates to the output. Neither is a DUT bug: PRDATA only matters on the cycle the access completes. But a bench check that says "no X on any output, ever" passed for months on RTL and fires on every cycle in GLS. The fix belongs in the check, not the waveform: qualify each no-X check with its valid signal (prdata only when PSEL, PENABLE and PREADY are all 1).

    What breaks: the flop that was never reset

    The classic GLS failure is a flop left without reset to save area: "software always loads it before use". Here is a minimal version. mode_q has no reset and selects between two inputs for a flop that does reset:

    mode_mux.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // not-run: needs a four-state simulator (run with Icarus)
    // A config flop with no reset, used as a mux select by a flop that is reset.
    module mode_mux (
      input  logic       clk, rst_n,
      input  logic       load, mode_in,
      input  logic [7:0] a, b,
      output logic [7:0] y
    );
      logic mode_q;                        // no reset: "software always loads it first"
      always_ff @(posedge clk)
        if (load) mode_q <= mode_in;
    
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n)      y <= 8'h00;
        else if (mode_q) y <= a;           // RTL: if (X) takes the else branch
        else             y <= b;
    endmodule

    Synthesised with Yosys (8 flops with reset, 1 enable flop without, 8 muxes) and simulated on Icarus with the same bench, which releases reset and loads the mode three clocks later (a=AA, b=55):

    Diagram
    == RTL                       == gates
     20 ns  y=00                  20 ns  y=00
     30 ns  y=55                  30 ns  y=xx  <-- X on the output
     40 ns  y=55                  40 ns  y=xx  <-- X on the output
     50 ns  y=55                  50 ns  y=xx  <-- X on the output
     60 ns  y=55                  60 ns  y=xx  <-- X on the output
     70 ns  y=55                  70 ns  y=55

    The RTL never shows a problem: if (mode_q) with mode_q = X is treated as false, so y takes b. The netlist mux has an unknown select and different inputs, so y is X until software loads the mode. If anything samples y in those cycles (a status bit, an interrupt, a register read at boot), the chip can behave differently on each power-up. The debug recipe for any GLS X is the same: find the first time an X appears on a port, trace it back in the waveform through the fan-in cone to a flop with X on Q, and ask why that flop was never initialised. Then decide with the designer: add the reset, prove the load always happens first (and constrain the bench to match), or, as a documented last resort, initialise the flop in simulation only and record the waiver.

    Timing checks and the notifier

    In an SDF simulation, each library flop model carries timing checks in its specify block, such as $setuphold(posedge CK, D, tsu, th, notifier) and $width(posedge CK, tmin). SDF annotation replaces the placeholder limits with the values for the chosen corner. When data changes inside the setup/hold window, the simulator prints a violation with the instance and the time, and toggles the notifier register, which the flop's model uses to drive Q to X. One violation therefore becomes a spreading X, which is why the first violation in the log matters far more than the thousandth.

    Icarus parses specify blocks but does not run timing checks, so this course cannot show a live violation. On a project the flow is:

    • The implementation team delivers the netlist, cell and memory models, and one SDF file per corner. The testbench annotates with the standard task $sdf_annotate("dma_max.sdf", top.dut, , "sdf.log", "MAXIMUM"), run once at time 0. The annotation log must show zero unannotated or mismatched cells.

    • You run the reduced test list at least twice: maximum delays at the slow corner (setup-type failures) and minimum delays at the fast corner (hold-type failures). Projects add corners for specific concerns.

    • First stage flops of synchronisers will violate timing in simulation, because their inputs are asynchronous by design. Every commercial simulator can disable timing checks, or the notifier's X, for a listed set of instances. That list is reviewed: each entry must be a real synchroniser flop that CDC sign-off also recognises.

    • A violation on a path STA declared false or multicycle, in a mode the exception says cannot happen, means the exception is wrong.

    What a GLS sign-off looks like

    • Every test on the agreed GLS list passes on the final netlist, zero-delay and SDF at each required corner, with the same scoreboard and checks as RTL.

    • No X on any qualified output after reset completes; every initialisation workaround is listed and approved.

    • No timing violations except instances on the reviewed exclusion list; SDF annotation complete.

    • Each ECO netlist reruns the subset the ECO could affect, and the results are attached to the ECO ticket.

    Interview

    "We have LEC and STA. Why should we spend weeks on gate-level simulation?"

    A weak answer: "to check the netlist works", or "because we always have". It names no failure that GLS finds and the other tools miss, so the interviewer hears habit.

    A strong answer separates the tools by what they assume: "LEC proves the netlist matches the RTL from matching states, so it does not prove the design comes out of reset cleanly, and it compares against the synthesis interpretation of the RTL, so a simulation-synthesis mismatch can survive it. STA proves path delays against constraints, so it cannot catch a wrong false-path or multicycle exception. GLS covers those three: X and initialisation, sim-synth mismatch, and dynamic behaviour of exceptions and asynchronous paths. That is why the list is small and targeted (reset, boot, modes, crossings, low-power transitions) rather than the full regression."

    Follow-ups: Your first SDF run has 4,000 timing violations. Where do you start? (the first one in time; check SDF annotation coverage and the testbench drive skew before blaming the design) and How do you keep backdoor register tests working on a netlist? (a per-netlist HDL path map, or run them front-door only).

    Check yourself A GLS run shows prdata full of X between APB transfers, but every register read in the scoreboard matches. Is this a bug, and what do you change?

    Most likely not a DUT bug: the address decoder propagates the X the bench parks on paddr, and PRDATA is only meaningful on the completing cycle. RTL hid it because case with an X selector takes the default. Change the no-X check to sample prdata only when PSEL, PENABLE and PREADY are all 1. Parking the bus at a known value would also silence it, but then the bench stops exercising the "ignored while idle" rule.

    Check yourself Your SDF simulation shows hold violations on almost every input flop of the stream interface, at the first clock edge of each beat. The fast-corner STA report is clean. What do you suspect first?

    The testbench, not the design. The driver changes inputs exactly at the testbench clock edge, while the flops' clocks arrive later by the clock-tree insertion delay, so the input change lands inside their hold window. STA assumes inputs arrive per the input-delay constraints, not at time zero. Drive with an output skew after the edge (a clocking block output # value that matches the input-delay constraint) and rerun.

    Check yourself A designer wants to add an initial mode_q = 0; in the RTL so GLS stops showing X. What is wrong with that?

    An initial block is ignored by synthesis (or is only honoured on FPGAs), so the netlist flop still powers up unknown and silicon still has the problem. It only hides the X in simulation. Either add a real reset, or prove the flop is always loaded before anything uses it and document the waiver.

    Next: most GLS X failures start long before GLS. Reset and X-Propagation Verification shows how to find them in RTL simulation, weeks earlier and at a fraction of the cost.

    Previous

    238 / 280

    Next: Reset and X-Propagation Verification

    Up next

    Up nextReset and X-Propagation Verification

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Power-Aware Simulation with UPF