The structure of an SDF file, how delays are annotated into the gate-level netlist, the two annotation methods, MTM corner control, annotation statistics, and how to confirm delays are correctly applied before trusting simulation results.
After synthesis and place-and-route, the backend implementation tool knows the exact propagation delay of every signal path in the design β derived from standard cell characterisation data and extracted parasitic capacitance and resistance of the physical wires. This timing data must somehow be transferred to the simulator so that simulation reflects real-world behaviour.
Back-annotation is the process of taking timing information from the physical implementation domain and applying it to the gate-level simulation model. The word “back” reflects the direction: information flows backward through the design flow β from the physical implementation stage back to the verification stage.
The design flow runs left to right: RTL β synthesis β P&R β STA β silicon. Back-annotation (red dashed path) moves timing data extracted from the P&R stage backward to the simulation stage. The gate-level netlist and the SDF file β both products of the backend flow β are combined in GLS to produce simulation that closely predicts silicon behaviour.
The Standard Delay Format (SDF) is the industry-standard file format for carrying this back-annotated timing information. It is a text file, human-readable, and understood by all major HDL simulators. Every delay value in an SDF file corresponds to a specific timing arc or timing check in a specific instance of the netlist.
An SDF file has a consistent two-part structure: a header section containing metadata about the design and timing environment, followed by one or more cell entries that contain the actual delay values for each instance.
SDF file structure. The header (left, blue) records metadata: format version, design name, generating tool, process corner, voltage range, temperature range, and the timescale that all delay values are expressed in. Each cell entry (right, green) targets one specific instance in the netlist hierarchy, provides IOPATH delays for each inputβoutput arc, and TIMINGCHECK values. The three-value syntax (180:210:260) represents MIN:TYP:MAX delay.
Every SDF header field serves a specific purpose. Understanding them is essential for diagnosing mismatches between the SDF and the simulation environment:
| Header keyword | What it specifies | Simulation relevance |
|---|---|---|
SDFVERSION |
Version of the SDF format used (e.g. “3.0”) | Simulator must support this version. If mismatched, annotation may fail silently. |
DESIGN |
Name of the top-level design module | Informational β helps verify the SDF matches the correct design. |
TIMESCALE |
Unit that all delay values in the file are expressed in (e.g. 1ps, 100fs) |
Critical. If the simulation timescale precision is coarser than the SDF timescale, delay values will be rounded down β potentially to zero. Mismatch here is a common cause of “no delays in waveform” bugs. |
VOLTAGE |
Supply voltage range at which delays were characterised (MIN:TYP:MAX) | Documents the PVT corner. Does not affect annotation directly but confirms the SDF matches the intended operating point. |
PROCESS |
Process corner label (e.g. “slow”, “fast”, “tt_1v0_25c”) | Confirms which process corner the SDF was generated for. Must match the intended simulation corner. |
TEMPERATURE |
Temperature range at which delays were extracted (MIN:TYP:MAX in degrees Celsius) | Together with VOLTAGE and PROCESS, defines the complete PVT operating point. Informational β verify it matches intent. |
DIVIDER |
Hierarchy separator character used in instance paths within the SDF (typically / or .) |
Must match the hierarchy separator used by the simulator for the target design. Mismatch causes all instance path lookups to fail β 0% annotation. |
TIMESCALE 1ps and the simulator is running at 1ns/1ns precision, a delay value of 250ps in the SDF becomes 0.25ns at the simulator’s precision β which rounds to zero. The annotator reports the annotation as successful (the value was written), but the delay in simulation is zero. The symptom is: SDF annotation statistics show 100% annotated, but no delays appear in waveforms. Always verify that simulation timescale precision is at least as fine as the SDF timescale.
Below the header, the SDF contains one cell entry for each instance that has timing information. A cell entry has three parts: the cell type, the instance path, and one or more timing constructs:
; Example cell entry (CELL (CELLTYPE "NAND2X1") (INSTANCE top.u_alu.u_adder.g42) ; Path delays: absolute values override specify block defaults (DELAY (ABSOLUTE (IOPATH A Y (95:112:148) (88:105:139)) (IOPATH B Y (98:115:152) (91:108:143)) ) ) )
CELLTYPE names the cell type β this is for reference only and does not affect which instance the delay is applied to. The annotation is driven entirely by INSTANCE, which specifies the full hierarchical path of the target instance in the netlist. The path uses the DIVIDER character to separate hierarchy levels.
The DELAY block contains path delay entries. Two modes exist:
ABSOLUTE: The values in the SDF replace the delay values coded in the specify block of the cell model. This is the standard mode for back-annotation from P&R.INCREMENT: The values are added to the existing specify block values. Used when the SDF represents only the wire (interconnect) portion of the delay, with the cell intrinsic delay already in the specify block.Each IOPATH entry specifies one timing arc: from a named input pin to a named output pin. The two value triples that follow are the rise delay and fall delay respectively. Both are expressed as (MIN:TYP:MAX) triplets β the simulator selects which value to use based on the MTM control setting (see Section S5).
; Timing check entries for a flip-flop (TIMINGCHECK ; Setup + hold combined check (SETUPHOLD D (posedge CK) (45:52:68) ; setup time MIN:TYP:MAX (ps) (10:12:15) ; hold time MIN:TYP:MAX (ps) ) ; Recovery + removal check for async reset (RECREM (negedge RN) (posedge CK) (30:38:50) ; recovery time (8:10:13) ; removal time ) ; Minimum clock pulse width (WIDTH (posedge CK) (200:220:260)) )
| Timing check | What it enforces | Failure means |
|---|---|---|
SETUP / SETUPHOLD |
Data must be stable for at least this long before the active clock edge | Data not yet settled when the clock samples β captured value unreliable |
HOLD / SETUPHOLD |
Data must remain stable for at least this long after the active clock edge | Data changes too soon after clock edge β previously captured value corrupted |
RECOVERY / RECREM |
Asynchronous deassert (e.g. reset release) must happen this long before the next clock edge | Reset deasserted too close to the clock β flip-flop may not exit reset reliably |
REMOVAL / RECREM |
Asynchronous assert must remain stable for this long after the clock edge | Reset withdrawn too soon after the clock β state uncertain during the transition |
WIDTH |
Minimum pulse width on a clock or asynchronous control pin | Clock pulse too narrow β flip-flop may not latch correctly |
Every delay value in an SDF file is expressed as a triplet: (minimum : typical : maximum). These three values correspond to different PVT corners β fast process/high voltage/low temperature (min), nominal (typ), and slow process/low voltage/high temperature (max).
When the simulator annotates an SDF file, an MTM (Min-Typ-Max) control setting tells it which value from each triplet to use:
| MTM setting | Value selected from (a:b:c) | When to use |
|---|---|---|
| MAXIMUM | c (third value) β worst-case slow | Setup timing signoff at slow corner |
| MINIMUM | a (first value) β best-case fast | Hold timing signoff at fast corner |
| TYPICAL | b (middle value) β nominal | Development debug runs (typical silicon) |
If a triplet is given as a single value β for example, (250) β that single value is used regardless of the MTM setting. Some back-end flows generate per-corner SDF files (one file for slow, one for fast) rather than embedding all three values in a single file, in which case the MTM setting is irrelevant.
MTM control selects one value from each MIN:TYP:MAX triplet. MINIMUM selects the fastest (best-case) delays β used to stress hold timing. MAXIMUM selects the slowest (worst-case) delays β used for setup timing signoff. TYPICAL selects the middle value for representative development simulation. The same SDF file can be used for all three corners by changing only the MTM control setting.
There are two ways to tell the simulator to read and apply an SDF file. Both achieve the same result β delays annotated into the netlist before simulation begins β but they differ in where the annotation instruction lives and which takes precedence when both are present.
Method 1 (left): the $sdf_annotate system task is placed inside the testbench initial block. It is executed at time 0 during elaboration, reading the named SDF file and annotating the specified scope. Method 2 (right): a separate SDF command file is passed as a simulator argument. It supports all the same options plus explicit MTM control and does not require any testbench modification. When both are present, Method 2 takes precedence.
The $sdf_annotate system task is called inside the testbench at time 0. The simulator recognises this task during elaboration and automatically applies the SDF before simulation begins. Arguments include:
| Argument | Required? | Purpose |
|---|---|---|
sdf_file | Yes | Path to the SDF file to annotate |
scope | No | The module instance to annotate. Defaults to the scope where the task is called. Must match the hierarchy in the SDF INSTANCE paths. |
log_file | No | Name of the annotation log file β records every arc annotated. Essential for debugging. |
mtm_spec | No | MTM control: "MINIMUM", "TYPICAL", or "MAXIMUM". Defaults to simulator’s built-in default. |
scale_factors | No | Scale factor string applied to all delays, e.g. "1.0:1.0:1.0" |
A plain-text command file passed to the simulator. This approach keeps annotation configuration outside the testbench β useful when the same testbench is reused across multiple GLS runs at different corners or with different SDF files. Common keywords in an SDF command file:
| Keyword | Purpose |
|---|---|
SDF_FILE | Path to the SDF file (uncompiled β simulator compiles it automatically) |
COMPILED_SDF_FILE | Path to a pre-compiled SDF binary β faster startup for large files |
SCOPE | Instance path in the netlist where annotation begins |
MTM_CONTROL | MINIMUM, TYPICAL, or MAXIMUM β which delay value from each triplet to use |
SCALE_FACTORS | Derating multipliers for min, typ, and max delays |
LOG_FILE | Destination for the detailed annotation log |
$sdf_annotate call in the testbench and an SDF command file are present, the command file takes priority and the system task is ignored. The simulator issues a warning noting that the system task was suppressed. This precedence rule allows the same testbench to be used for both RTL simulation (no command file) and GLS (command file present) without modifying the testbench source.
The annotation scope is the hierarchical instance path in the simulation that the SDF applies to. Understanding scope is critical because the instance paths in the SDF file are relative to the annotation scope β they identify cells within the scope’s subtree.
Consider a typical testbench structure:
// Testbench hierarchy: module tb_top; // testbench top level my_chip dut(β¦); // device under test // dut contains: u_cpu, u_mem, u_periphβ¦ endmodule // SDF INSTANCE paths inside the SDF file: (INSTANCE u_cpu.u_alu.g42) // relative to annotation scope (INSTANCE u_mem.u_sram.q0) // relative to annotation scope
If the annotation scope is set to tb_top.dut, then the SDF instance path u_cpu.u_alu.g42 resolves to tb_top.dut.u_cpu.u_alu.g42 in the simulation hierarchy β which is the correct gate instance. If the scope is wrong, every instance path in the SDF will fail to resolve and annotation will be 0%.
tb_top) instead of the DUT instance (tb_top.dut) β SDF instance paths skip the DUT level and resolve to nothing. (2) Scope matches the DUT but the SDF was generated with a different instance hierarchy (synthesis preserved a different set of hierarchy levels) β SDFNEP warnings fire for every mismatched path. (3) The DIVIDER character in the SDF header doesn’t match the simulator’s hierarchy separator β all paths fail silently.
After annotation completes, the simulator prints a statistics summary showing how many timing arcs and checks were successfully annotated. This is the first thing to examine after any SDF GLS run β before looking at simulation results.
// Example annotation statistics output: Annotating SDF timing data: Compiled SDF file: delays.sdf.X Log file: sdf.log Backannotation scope: tb_top.dut MTM control: MAXIMUM Scale factors: 1.0:1.0:1.0 Annotation completed successfully⦠SDF statistics: Path Delays: Specified = 2840 Annotated = 2840 (100.00%) $setuphold: Specified = 960 Annotated = 960 (100.00%) $recrem: Specified = 240 Annotated = 240 (100.00%) $hold: Specified = 120 Annotated = 0 ( 0.00%) $width: Specified = 480 Annotated = 0 ( 0.00%)
Reading this output:
$hold checks annotated β this is expected if the library uses $setuphold (combined) rather than separate $setup and $hold statements. The SDF contains HOLD entries that don’t match the cell model’s SETUPHOLD specify constructs β this requires investigation.Annotation statistics triage. Path delays below 100% are always a problem requiring investigation β common causes are scope mismatch, hierarchy separator mismatch, or a stale SDF. Timing check percentages below 100% may or may not be problems: combined $setuphold vs separate $setup/$hold mismatches are common and expected; $width checks at 0% may be normal for the flow; 0% across all check types alongside 100% path delays suggests a library model vs SDF mismatch on the timing check structure.
100% annotation statistics are necessary but not sufficient to confirm delays are working correctly. As noted in the TIMESCALE section, values can be annotated but rounded to zero due to precision mismatch. Three verification steps confirm delays are genuinely active:
With verbose annotation logging enabled, the log file records every arc that was annotated and the value written. Examine a representative flip-flop instance and verify the logged delay values match expectations from the SDF file.
// Example annotation log entries: Time units: 1ps Annotating to instance tb_top.dut.u_cpu.u_reg of module DFFX1 ABSOLUTE (IOPATH (posedge CK) Q) = (180:210:260) rise ABSOLUTE (IOPATH (posedge CK) Q) = (175:205:255) fall SETUPHOLD D (posedge CK) = (45:52:68) setup (10:12:15) hold
Run the simulation with waveform dumping enabled and probe the output pin of a cell that has a known, non-trivial SDF delay. Measure the propagation time from the input transition to the output transition. It should match the SDF value (at the selected MTM corner) multiplied by the scale factor.
SDF entry: IOPATH A Y (95:112:148)(88:105:139) for instance top.dut.u1.g5. MTM = MAXIMUM. Timescale = 1ns/1ps.
Expected delay: Rise = 148ps (MAX). Fall = 139ps (MAX).
In waveform viewer: Drive input A high at time 10.000ns. Measure output Y rising edge. It should occur at approximately 10.148ns.
If Y rises at 10.000ns (zero delay): The delay was not annotated. Check: (1) Is timescale precision fine enough? 1ns/1ps is fine for 148ps. (2) Is verbose log showing the annotation? (3) Is the instance path in the SDF exactly matching the simulation hierarchy? Enable the annotation dump timing file option to list all annotated values per instance.
If Y rises at 10.112ns: Simulation is using the TYP value (112ps), not MAX. The MTM control setting is TYPICAL, not MAXIMUM. Verify the MTM setting in the SDF command file or the $sdf_annotate call.
Most simulators offer an option to dump the complete timing information of the design to a text file after elaboration. This file lists every instance in the simulation hierarchy alongside all annotated timing arcs and check values. Searching this file for a known instance path confirms exactly what the simulator is using β directly from the elaborated simulation model, bypassing any uncertainty about whether the log file reflects the actual simulation state.
In a typical SoC physical implementation flow, SDF files are generated at two points. The first is immediately after synthesis, before routing, using estimated wire delays β this is sometimes called a “pre-layout” or “estimated parasitics” SDF. Its path delays include accurate cell intrinsic delays from characterisation but use statistical wire delay estimates because routing has not yet occurred. This SDF is useful for early zero-delay or estimated-delay GLS but should never be used for final signoff. The second and definitive SDF is generated after static timing analysis on the post-route parasitic extraction β this SDF contains actual measured interconnect delays and is the correct file for signoff GLS. The difference in wire delays between pre-layout and post-layout SDF can be large β particularly for long nets crossing multiple metal layers. Always confirm with the backend team which SDF was delivered before interpreting GLS timing results.
In large SoC designs, the same macro or IP block is often instantiated multiple times β for example, four identical CPU cores. Each instance has a unique hierarchical path (e.g. top.core0, top.core1β¦) and the SDF contains separate cell entries for each instance, since the physical layout of each core is different (different placement, different routing, different parasitic values). If the annotation scope is set incorrectly β for example, to top instead of the individual core β the annotator may map one core’s SDF entries to another core’s instances, producing incorrect timing values without generating any errors. The annotation percentage stays at 100% because the instance paths resolve (they just resolve to the wrong instances). The symptom is that all four cores pass GLS but silicon shows timing failures on specific cores. This is one reason why multi-core designs often annotate each core separately with explicitly scoped annotation calls.
For large designs, parsing and compiling a multi-megabyte SDF text file at the start of every simulation run adds significant overhead β sometimes minutes per run. Most simulators support pre-compiling the SDF into a binary format once, then loading the binary in subsequent runs. The pre-compiled file is typically several times smaller than the text SDF and loads orders of magnitude faster. In a GLS regression with hundreds of test cases, switching to pre-compiled SDF can reduce total simulation time by 20β40% simply by eliminating repeated SDF parsing. The compiled file must be regenerated whenever the SDF changes (i.e., when a new netlist or new timing extraction is delivered by the backend team). A simple Makefile dependency on the SDF file timestamp ensures the compiled version stays current automatically.