GLS · Chapter 4 · SDF Annotation
Debugging Failed & Partial SDF Annotation
SDF annotation can go wrong in several ways, and the worst failure is the silent one. A missing or unreadable file errors loudly and is easy to catch. The quiet failures are the danger: a wrong scope, or a mismatch in the divider, instance path, or cell name, leaves instances unmatched. The most insidious case is partial annotation, where some instances match while the rest are silently left at zero delay. The run then looks timed but is only partly timed, so behaviour in the un-annotated portion is never exercised with real delays. This lesson catalogs the failure modes, shows how the annotation log and coverage report expose them, and argues for making missing or partial annotation fail loud instead of passing quietly.
Foundation12 min readGLSSDFDebuggingAnnotation CoveragePartial Annotation
Chapter 4 · Section 4.5 · SDF Annotation
Project thread — before trusting the counter's timed run (4.6), you must confirm every counter instance annotated. This lesson is how you verify that — and catch the subtree that didn't.
1. Why Should I Learn This?
A partly-annotated run is more dangerous than an un-annotated one — because it looks timed.
- Loud failures (missing file) are easy; silent ones (wrong scope, path/name mismatch, partial match) are the risk.
- Partial annotation leaves part of the design zero-delay with no error.
- The annotation log / coverage report is the only reliable detector.
This is the safety net for the whole chapter (4.1–4.4) and gates the counter capstone (4.6).
2. Real Silicon Story — the 90%-timed regression
A post-layout timing regression passed, and the annotation log showed thousands of matched entries — clearly a timed run. But a control subblock had a handful of unmatched instances (a name change after an ECO) that no one read past the big matched count.
Those unmatched instances ran zero-delay. A race in that subblock — real at post-layout delays — never appeared, because that corner of the design was never timed. The regression was ~90% annotated and 100% trusted.
Lesson: matched counts are not enough — read the unmatched list too. Partial annotation silently leaves part of the design untimed.
3. Concept — the failure modes and how each shows up
Loud failures (easy):
- File not found / unreadable — the annotator errors immediately.
- Malformed SDF — parse error.
Quiet failures (dangerous):
- Wrong scope in
$sdf_annotate(4.3) — SDF paths resolve against the wrong instance → nothing (or the wrong things) match. DIVIDERmismatch (/vs., 4.2) — instance paths don't resolve.- Instance-path / cell-name mismatch — netlist renamed (ECO, different elaboration) vs SDF → unmatched.
- Partial annotation — some instances match, others are silently left at zero delay.
The detector — the annotation log / coverage report:
- Matched counts (
IOPATH,INTERCONNECT,TIMINGCHECK). - Unmatched list — the instances not annotated (the part that matters most).
- Read both. A big matched count can still hide a small, critical unmatched set.
Policy: make missing/partial annotation fail the run, not pass it. Scope reminder: even a fully annotated run is dynamic — STA is signoff (0.3).
4. Mental Model — check the receipt, not just the total
Annotation is like a bulk delivery: the coverage log is the receipt.
- A big matched total is like "1,842 items delivered" — reassuring, but not the whole story.
- The unmatched list is the "items missing" line — small, easy to skip, and the one that bites.
- Read the missing line. A few undelivered (unmatched) instances can be exactly the ones you needed timed.
Trusting the total without the missing line is how a 90%-timed run gets called timed.
5. Working Example — a coverage log with unmatched instances
A representative annotation log showing partial coverage:
# Annotation coverage log — REPRESENTATIVE, tool-neutral
SDF: reading sdf/counter4_tt.sdf (scope: tb.u_dut)
SDF: annotated 40 IOPATH, 14 INTERCONNECT, 8 TIMINGCHECK
SDF: WARNING - 3 unmatched instances:
u_dut/u_ctrl/u_q0 (no matching instance - renamed after ECO?)
u_dut/u_ctrl/u_q1
u_dut/u_ctrl/u_x2
# 3 instances LEFT AT ZERO DELAY. Big matched count, small critical miss. READ the warnings.Practical context (representative, tool-neutral):
# Turn silent partial annotation into a loud failure:
# 1) capture the annotation log every run
# 2) grep for 'unmatched' / 'could not annotate' / 'WARNING'
# 3) fail the run (nonzero exit) if unmatched > 0 <-- policy, not luck
# Common root causes to check: scope arg (4.3), DIVIDER (4.2), post-ECO name changes.Partial annotation — one bit timed, one zero — as a real waveform:
Partial annotation: one counter bit timed, another (unmatched) still zero-delay
8 cycles6. Debugging Session — a passing regression that was only partly timed
A timing regression passes with a large matched count, but a handful of instances were silently left at zero delay after a post-ECO rename — the un-annotated subblock's timing behaviour was never exercised, hidden by trusting the total instead of reading the unmatched list
PARTIAL ANNOTATION HIDES BEHIND A BIG MATCHED COUNTA post-layout timing regression passes; the annotation log shows a large matched count. A subblock's timing-dependent behaviour (a race) never appears in GLS, though it is real at post-layout delays.
Partial annotation. A few instances in that subblock did not match the SDF — commonly a name change after an ECO, a wrong scope (4.3), or a DIVIDER/path mismatch (4.2). Unmatched instances are silently left at zero delay: no error, just untimed. Because the matched count was large, no one read the small unmatched list, so the un-annotated subblock ran zero-delay and its timing behaviour was never exercised. The run was ~90% timed and 100% trusted — a false-confidence failure, not a tool bug.
Read the coverage report — both the matched counts and the unmatched list — and treat any unmatched instance as a failure to investigate. Fix the root cause (correct the scope, the DIVIDER, or the renamed instance paths so the SDF matches the current netlist), then re-run and confirm 0 unmatched. Make this a policy: fail the run (nonzero exit) when unmatched > 0, so partial annotation becomes loud. The lesson: the dangerous SDF failure is silent partial annotation — some instances timed, others left zero-delay — and the coverage report (matched and unmatched) is the only reliable detector; make it fail loud. (Even at 0 unmatched, the run is dynamic — STA is signoff, 0.3.)
7. Common Mistakes
- Reading only the matched count. The unmatched list is where the risk is.
- Trusting a passing run without the log. Partial annotation is silent.
- Ignoring post-ECO renames. Netlist name changes unmatch SDF paths.
- Not enforcing a policy. Unmatched > 0 should fail the run.
- Assuming full annotation = timing closure. STA (all corners) is signoff (0.3).
8. Industry Best Practices
- Always capture and read the annotation coverage log — matched and unmatched.
- Fail the run on any unmatched instance (nonzero exit) — make partial loud.
- Re-verify annotation after ECOs / re-elaboration (names can change).
- Check scope + divider first when instances don't match (4.2/4.3).
- Confirm 0 unmatched before trusting a timed run — then defer closure to STA.
Senior Engineer Thinking
- Beginner: "It annotated thousands of entries — clearly a timed run."
- Senior: "How many unmatched? Even three silently-zero instances can hide a real race. I read the unmatched list, fix the mismatch, and fail the run if anything is unannotated."
The senior treats the unmatched list as the real signal and enforces zero-unmatched as policy.
Silicon Impact
Silent partial annotation is one of the most dangerous GLS traps: a run that is believed fully timed but leaves a subblock zero-delay never exercises that block's timing behaviour with real delays — so a real race, glitch, or check firing there can reach silicon as an intermittent failure (0.3), and the false confidence discourages further checking. Because the failure produces no error, only reading the coverage report and enforcing zero-unmatched defends against it. This discipline — receipt over total — is what makes a "timing GLS" trustworthy end-to-end.
Engineering Checklist
- Captured the annotation coverage log every run.
- Read matched counts and the unmatched list.
- Failed the run on any unmatched instance (policy, nonzero exit).
- Re-verified annotation after ECOs / re-elaboration.
- Confirmed 0 unmatched before trusting timing; deferred closure to STA.
Try Yourself
- Annotate an SDF onto a small netlist, then rename one instance in the netlist (simulating an ECO) and re-run.
- Observe: the annotation log now lists that instance as unmatched, and it runs zero-delay while others are timed.
- Change: fix the instance name (or the SDF path) so it matches again.
- Expect: the log reports 0 unmatched and the instance is timed. Then script a check that fails the run when unmatched > 0.
Any free Verilog simulator prints an SDF annotation/coverage log. No paid tool required.
Interview Perspective
- Weak: "If the SDF is wrong, the simulator errors."
- Good: "A missing file errors, but wrong scope or path mismatches leave instances unmatched — silently zero-delay."
- Senior: "The dangerous failure is partial annotation: some instances timed, others silently zero-delay. A big matched count can hide a few critical misses, so I read the unmatched list, fix the scope/divider/renamed paths, and enforce zero-unmatched as a run-failing policy. Even then it's dynamic — STA signs off."
9. Interview / Review Questions
10. Key Takeaways
- SDF annotation fails loudly (missing/malformed file) or, dangerously, quietly — wrong scope (4.3),
DIVIDER/path/cell-name mismatch (4.2), or partial annotation. - Partial annotation matches some instances and silently leaves others at zero delay, so the run looks timed but is only partly timed.
- The annotation log / coverage report is the detector — read the matched counts and the unmatched list (a big total can hide a small critical miss).
- Make missing/partial annotation fail loud (fail the run on any unmatched instance); re-verify after ECOs / re-elaboration.
- Even a fully annotated run is dynamic — STA (all corners) is the timing signoff (0.3). Next: 4.6 — annotating the counter with SDF, end to end.
Quick Revision
Annotation fails loud (missing file) or SILENT (wrong scope,
DIVIDER/path/name mismatch, PARTIAL match → zero-delay). Partial = looks timed, isn't. Detector = coverage log: matched counts + UNMATCHED list — read both. Fail the run on unmatched > 0. Re-check after ECOs. Full annotation still dynamic; STA signs off. Next: 4.6 — annotating the counter.