Four of the most frequent GLS failures — zero-delay gate oscillation loops, CUVUNF hierarchy errors from synthesis flattening, FLFFNF compiled SDF file errors, and SDFNEP/SDFNET annotation path mismatch warnings. Root causes, diagnostics, and resolutions.
Gate-level simulation failures fall into two broad categories: problems that prevent the simulation from running at all (compile and elaboration errors), and problems that let it run but produce incorrect or misleading results (annotation warnings and functional failures). This article covers four of the most common issues encountered when first bringing up a GLS environment — each with its own distinct error message, root cause, and resolution strategy.
Four common GLS issues mapped to the simulation timeline. CUVUNF (hierarchy name error) appears at elaboration when the testbench references a path flattened by synthesis. FLFFNF (compiled SDF not found) appears during annotation when the pre-compiled SDF binary is missing. TRZDGOC (zero-delay oscillation) appears during simulation when a feedback loop spins infinitely. SDFNEP/SDFNET warnings appear during annotation when SDF paths or checks do not match the HDL model.
The simulation starts but does not progress — it runs indefinitely at the same simulation time point, consuming CPU at 100% and producing no further output. The simulator may issue a warning such as: “Possible zero-delay gate oscillation detected.” The simulation time counter stops advancing.
In zero-delay simulation, all gate delays are zero. If a combinational feedback loop exists in the netlist — where the output of a gate (directly or through several gates) feeds back to its own input — the loop has no delay to break the cycle. Each evaluation triggers another evaluation at the same simulation time, creating an infinite event loop that the simulator cannot escape.
This is not always a design error. Many feedback loops are legitimate (flip-flop models contain internal feedback, for example) but are made safe by having a non-zero delay somewhere in the loop. Zero-delay mode removes all those delays, potentially activating loops that would be harmless in real silicon.
Zero-delay oscillation. With buf b1 and nor b2 cross-coupled, when i1 transitions from 1 to 0, the NOR output changes, the buffer propagates it back to the NOR input, which changes again — all at the same simulation time t=10 with zero delay. In real silicon, the propagation delays through the buf and NOR gates naturally space out these events in time, preventing the infinite loop. Zero-delay mode removes this temporal separation.
Add the -gateloopwarn switch to elaboration. With this flag, the simulator detects potential zero-delay loops during simulation and interrupts execution with a warning rather than hanging indefinitely. Also add -access +c (or the equivalent driver access flag) to enable interactive interrogation of driver signals.
// Elaboration with loop detection enabled: elaborate tb_top \ -nospecify \ -gateloopwarn // interrupt on zero-delay oscillation -access +rwc // enable driver/load interrogation
When the simulator interrupts with the oscillation warning, it drops into interactive mode. Run the TCL command to list all currently active drivers on the oscillating net:
// Interactive TCL command when simulation halts: drivers -active // Example output: // [./top.v, 7, top] buf b1 (o2, o1) // This identifies buf b1 as the currently active driver in the loop
Once the looping circuit is identified, choose the appropriate resolution:
#0.1) on the looping gate in the netlist or testbench wrapper. This is the quickest fix for simulation purposes. Alternatively, use -seq_udp_delay or -sequdp_nba_delay switches to add a uniform delay to all sequential UDP-modelled primitives (commonly used for flip-flop models).// Option A: add sequential UDP delay globally elaborate tb_top -nospecify -seq_udp_delay 1 // Option B: switch to unit delay mode elaborate tb_top +delay_mode_unit
The elaboration phase reports an error that a hierarchical name in the testbench cannot be resolved. The error message reads approximately: “The indicated hierarchical name could not be resolved. The name was resolvable up to, but not including, the last component of the name prefix printed in the error message.”
Error: CUVUNF — Hierarchical name 'tb_top.dut.u_cpu.u_alu.carry_internal' could not be resolved. Resolved up to: 'tb_top.dut.u_cpu' // u_alu no longer exists as a separate hierarchy level in the netlist
During synthesis, the tool flattens the RTL hierarchy. Sub-modules that existed as separately named hierarchies in the RTL are merged into their parent — all the logic is still present but the hierarchical boundaries are gone. If the testbench references signals using their RTL hierarchical path (e.g. tb_top.dut.u_cpu.u_alu.carry_internal), that path no longer exists in the netlist because u_alu was dissolved into u_cpu during synthesis flattening.
RTL vs post-synthesis hierarchy. In RTL (left), u_alu is a distinct submodule with its own scope — carry_internal is a named signal within it. After synthesis flattening (right), u_alu no longer exists as a separate scope. Its logic is merged directly into u_cpu, and internal nets are renamed by the synthesis tool. A testbench reference to u_cpu.u_alu.carry_internal fails with CUVUNF because the path component u_alu does not exist in the netlist.
Ask the backend team for the post-synthesis net name that corresponds to the RTL signal. The synthesis tool maintains a mapping between RTL signal names and the gates/nets that implement them. Replace every RTL hierarchical reference in the testbench with the corresponding post-synthesis net path.
// RTL testbench reference (breaks in GLS): force tb_top.dut.u_cpu.u_alu.carry_internal = 1'b0; // Updated for post-synthesis netlist (ask backend for net name): force tb_top.dut.u_cpu.net_carry_347 = 1'b0;
Ask the synthesis team to preserve the hierarchical boundary during flattening. This is done by applying a preserve attribute to the module before synthesis runs, instructing the tool not to flatten that specific hierarchy level. The syntax varies by synthesis tool but the concept is a hierarchy preservation constraint:
// RTL attribute to preserve hierarchy (tool-agnostic concept): set_attribute preserve true [get_cells u_alu] // After synthesis with preserve, the hierarchy is maintained: // u_cpu.u_alu still exists as a scope in the netlist // and carry_internal (or its synthesis equivalent) is accessible
During elaboration, the simulator attempts to load the SDF file specified in the SDF command file but cannot find the compiled version. The error reads approximately: “The compiled SDF file specified in the SDF annotation command file does not exist. The annotation command will be ignored.”
Error: FLFFNF — Compiled SDF file 'timing.sdf.X' does not exist. The annotation command will be ignored. // Result: simulation runs with zero delays — timing results meaningless
When an SDF command file specifies a COMPILED_SDF_FILE path (pointing to a pre-compiled binary with extension .sdf.X), the simulator expects that binary to exist at the specified location. If the binary was never generated, was generated in a different directory, or the path is wrong, this error occurs. The annotation is silently skipped — the simulation continues but without any delays annotated, which means timing results are completely invalid.
A secondary cause: using COMPILED_SDF_FILE for a file that has been compiled into a different working directory (perhaps by a previous simulation run in another location), and then running the current simulation from a different directory without updating the path.
Replace COMPILED_SDF_FILE with SDF_FILE in the SDF command file. The simulator will automatically compile the text SDF to a binary before annotation — no pre-compiled file is needed. The compiled binary is written to the current working directory.
// Instead of this (may fail if .X file doesn't exist): COMPILED_SDF_FILE "timing.sdf.X" // Use this (auto-compiles if needed): SDF_FILE "timing.sdf" SCOPE tb_top.dut MTM_CONTROL MAXIMUM
For large SDF files (common in post-P&R full-chip runs), pre-compiling once and reusing the binary saves significant time across repeated simulation runs. The compilation command converts the text SDF to a binary format:
// Compile the SDF text file to binary (run once): sdf_compile timing.sdf // Generates: timing.sdf.X in the current directory // Then in the SDF command file, use absolute path: COMPILED_SDF_FILE "/project/gls/sdf/timing.sdf.X"
If the binary exists but is in a different location, update the COMPILED_SDF_FILE path to the correct absolute path. Using absolute paths rather than relative paths in SDF command files prevents this class of error entirely — the file is found regardless of which directory the simulation is launched from.
SDF_FILE (text, auto-compiled) for development runs where the SDF changes frequently with each new netlist delivery. Switch to COMPILED_SDF_FILE with an absolute path for regression runs where the same SDF is used across hundreds of test cases — the one-time compilation cost is amortised across all runs.
During SDF annotation at elaboration, the simulator issues warnings for arcs or timing checks in the SDF that could not be matched to the cell model’s specify block:
Warning: SDFNEP — This path, including the condition if any, does not exist in the instance being annotated. The requested annotation will not occur. Instance: tb_top.dut.u_logic.g7, Cell: NAND2BX1 Path: (IOPATH (posedge A) Y) Warning: SDFNET — This timing check, including conditions if any, does not exist in the instance being annotated. Instance: tb_top.dut.u_ff.reg0, Cell: DFFX1 Check: (SETUPHOLD (posedge D) (posedge CK))
SDFNEP fires when the SDF specifies an IOPATH arc that does not exist in the cell’s specify block. Common causes:
posedge A) but the cell model’s specify block has a non-conditional path (without an edge specifier)SDFNET fires when the SDF specifies a timing check construct that cannot be matched to a corresponding check in the cell model’s specify block. The most common cause is a format mismatch between how setup/hold is expressed:
SDF annotation matching rules. Exact matches (same edge specifiers, same check type) annotate successfully. A conditional IOPATH (with posedge/negedge edge specifier) in the SDF requires an exact match in the model — if the model has a non-conditional path, SDFNEP fires. A non-conditional IOPATH in the SDF will annotate to any matching path in the model, including edge-specified ones, overwriting them. SETUPHOLD in the SDF annotates to $setuphold in the model; to annotate to separate $setup/$hold statements, the +splitvlog_suh option is needed.
The SDF contains a conditional path (with posedge or negedge qualifier) but the cell model specify block has only unconditional paths. The fix is to update the cell model’s specify block to add the conditional path, or to request that the SDF be regenerated without the edge qualifiers for that cell type. The preferred fix depends on which is more accurate: if the cell genuinely has edge-dependent timing, the model should be updated; if the SDF edge qualifier is overly specific, the SDF should be simplified.
Use the +sdf_splitvlog_suh annotation option, which instructs the annotator to split each SETUPHOLD entry in the SDF into separate $setup and $hold components for annotation. This works provided neither the setup nor hold value in the SDF is negative — if either value is negative (which occurs in advanced-node cells with NTC), splitting is not valid and the model must be updated instead.
// Add this option to the SDF command file for SETUPHOLD→$setup/$hold split: SDF_FILE "timing.sdf" SCOPE tb_top.dut MTM_CONTROL MAXIMUM +sdf_splitvlog_suh // split SETUPHOLD into $setup + $hold
Not every SDFNEP or SDFNET warning is a problem that needs immediate action. The triage framework:
| Warning pattern | Severity | Action |
|---|---|---|
| SDFNEP on a handful of exotic cell types | Medium | Investigate whether those cells are on timing-critical paths. If not critical, waive with documentation. |
| SDFNEP on all instances of a common cell (e.g. all DFFX1) | High | Systematic library/SDF mismatch. Fix the cell model or request updated SDF. Do not waive. |
| SDFNET on $hold where library uses $setuphold | Medium — expected | Confirm $setuphold annotates at 100%. The individual $hold warnings are a format mismatch, not a coverage gap. Document and waive. |
| SDFNET on all $setuphold — 0% timing check coverage | Critical | No timing checks are active. Root cause is a fundamental library model mismatch. Do not simulate for signoff until resolved. |
The most common source of zero-delay loops in production GLS is the UDP (User Defined Primitive) models used for sequential elements — flip-flops modelled with Verilog UDPs have internal feedback by definition. In RTL simulation, this is harmless because the flip-flop model is behavioural. In gate-level simulation, the structural UDP model with zero delay can create a loop if the output feeds back to the input at the same simulation time. The standard solution is the -seq_udp_delay switch, which adds a uniform small delay to all sequential UDP outputs. The value should be smaller than any real clock period in the design so it does not perturb timing, but large enough to break the simultaneous event cycle. For a design with a 1 ns minimum clock period, a seq_udp_delay of 0.01 ns (10 ps) is typically used. This is not a timing-accurate fix — it is a simulation workaround specific to zero-delay mode. In SDF mode, the real cell delays replace the UDP delay and the loop ceases to be a problem.
The CUVUNF error is one of the most common first-time GLS failures, and its prevention requires coordination between verification and RTL/synthesis teams before synthesis runs — not after. The most effective approach is a “GLS readiness review” before the first synthesis run: the verification team lists every hierarchical signal path accessed in the testbench (for forcing, probing, or coverage collection), and the synthesis team maps each one to either a preserved hierarchy path or a post-synthesis net name alternative. Paths that would require preservation of large sub-blocks (and incur significant timing penalties) are often better replaced with testbench probe nets at the DUT boundary rather than deep internal nodes. This negotiation prevents the situation where a late-project GLS bring-up is blocked for days by CUVUNF errors on fifty different testbench references.
A high rate of SDFNEP warnings is sometimes the first indicator that the standard cell library simulation model is out of sync with the liberty (.lib) file used for STA. In a well-maintained PDK, the liberty file and the Verilog simulation model are generated from the same characterisation database and contain identical timing arcs. In practice, IP vendors and foundry PDKs occasionally ship with minor discrepancies — a new cell variant characterised for the liberty file but not yet added to the simulation model, or an edge-conditional arc added to the liberty but expressed differently in the Verilog model. When SDFNEP warnings fire systematically on specific cell types, the correct first step is to compare the IOPATH arcs in the SDF (derived from liberty) against the conditional path statements in the Verilog model’s specify block. Discrepancies should be reported to the library vendor for the next PDK update and documented as waivers for the current tape-out.