Skip to content
VLSI Mentor

Tcl · Module 29

Stale Inputs: Netlists, Libraries, Constraints

A flawless analysis of last week's netlist asserts cleanly at every step. Catching that needs the identity of the inputs, because timestamps do not have the resolution to answer it.

Advanced22 min readtcledapart-iidebuggingprovenancesignoff

Part II · Module 29 · Chapter 29.2 · Stale Inputs

Chapter 29.1 ended on a failure assertions cannot catch: a correct result computed from the wrong inputs. Every step succeeds, every assertion passes, every number is internally consistent — and the netlist was last week's.

1. Why this one is the worst of them

The faults in Chapter 29.1 produce results that are wrong. This one produces results that are right about the wrong thing, and the difference matters:

  • A wrong result is sometimes implausible, so somebody squints at it.
  • A result from stale inputs is entirely plausible — it is a correct analysis, just not of the design you have.
  • It is self-consistent. The timing report agrees with the netlist it was computed from, the area report agrees with both, and every cross-check in Module 24 passes.
  • And it is reproducible. Running again gives the same answer, which is the usual evidence that a result is trustworthy.

Three real ways it happens, none of them exotic: synthesis failed and the flow continued on the previous netlist (Chapter 29.4); a library path pointed at last quarter's release; an SDC was edited after the run and before the review.

2. The check everyone writes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {[file mtime $netlist] < [file mtime $rtl]} {
    error "netlist is older than the RTL"
}

That is the obvious check and it is this chapter's own brief. It does not work, and the reason is a resolution problem.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== case 1: netlist written LAST, both in the same second ===
    mtime rtl 1791486019  netlist 1791486019  same second: 1
    netlist <  rtl : 0   (correct: not stale)
    netlist <= rtl : 1   <- FALSE POSITIVE: it is NOT stale

=== case 2: rtl edited LAST, both in the same second ===
    mtime rtl 1791486019  netlist 1791486019  same second: 1
    netlist <  rtl : 0   <- FALSE NEGATIVE: it IS stale
    netlist <= rtl : 1   (catches it)

=== so within one second, neither comparison is right ===
    <  misses a genuinely stale netlist
    <= rejects a genuinely fresh one

3. The information exists; Tcl 8.5 cannot reach it

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== what file stat offers ===
    atime    1791486019
    blksize  4096
    blocks   8
    ctime    1791486019
    dev      16777230
    gid      0
    ino      83610641
    mode     33188
    mtime    1791486019
    nlink    1
    size     7
    type     file
    uid      501

=== is there any sub-second field? ===
    sub-second fields in file stat : 0

Thirteen fields and not one sub-second. Meanwhile the filesystem is holding the real value — the same file, seen two ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== the same file, seen by Tcl and by Python ===
    tcl    file mtime : 1791486019
    python st_mtime    : 1791486019.6776323

So this is a Tcl 8.5 limitation, not a filesystem one. The fractional part exists; file mtime truncates it and file stat does not expose it.

Three consequences worth knowing.

You can reach it by other means and probably should not. exec stat -f %Fm on this platform, or a small C extension, gets the fraction — at the cost of a platform-specific dependency in the one place you least want one. stat's flags differ between BSD and GNU, so the wrapper is immediately non-portable.

ctime is not creation time. It is the inode change time, and it updates on a permission change or a rename. Reaching for it as a tiebreaker introduces a field that moves for reasons unrelated to content.

And the fundamental objection stands regardless of resolution, which is §4.

4. Timestamps answer the wrong question

Even with nanosecond resolution, a timestamp comparison is a proxy. The question is "is this output consistent with these inputs", and the timestamp answers "which was touched more recently" — which is a different question that usually has the same answer.

Where they diverge:

  • touch with no edit makes a file newer with identical content. A git checkout that restores a file to exactly what it was updates its mtime, and the whole flow reruns for nothing — the same hazard Chapter 28.2 §5 names for Make.
  • An edit and an undo leave a newer mtime and the original content. The flow reruns and produces the identical result.
  • A copied or restored file can arrive with an older mtime than the output it should invalidate — cp -p, a tar extraction, or a restore from backup. Then the stale output looks current.
  • Two machines with skewed clocks writing to shared storage make the comparison meaningless in either direction.

5. The fix is identity

Record what each input was, beside the output, and compare that.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc ::prov::digest {path} {
    if {![file isfile $path]} {
        error "no such input: $path" {} [list PROV NOINPUT $path]
    }
    switch -- $::prov::HASHER {
        tcllib { return [string range [::md5::md5 -hex -file $path] 0 11] }
        shasum { return [string range [lindex [exec shasum $path] 0] 0 11] }
        none   { error "no hash available" {} {PROV NOHASH} }
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== a stage records what its inputs WERE ===
tcl         	8.5.9
hasher      	tcllib
stage       	synth
design      	core
input       	854A7178B03C  rtl/core.sv
input       	1B1685A8BC30  constraints/c.sdc

Then the check becomes a comparison of identities rather than of times:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== nothing changed ===
    checked 2  changed 0  missing 0

=== now edit the RTL in the SAME SECOND as the manifest ===
    manifest mtime 1791486019   rtl mtime 1791486019   same second: 1
    a timestamp check (<) would say stale: 0
    the identity check says changed: 1
        rtl/core.sv          was 854A7178B03C  now A9D2B2EC8AD3

The timestamp check says "not stale". The identity check names the file and shows both digests. Same second, same files, and one of the two methods can answer the question.

6. What to record the identity of

Not only the netlist. The three the registry names, and two more:

InputWhy it goes staleWhat to record
Netlistsynthesis re-run, or failed and the old one remaineddigest
Constraintsedited after the run, or the wrong variant sourceddigest per file
Librariesa path to a release that moveddigest is impractical; record the resolved path and the release string
Parasiticsan extraction re-run for a different cornerdigest, or the extraction's own run ID
The flow scriptssomebody edited the script after the rundigest — Chapter 28.2 §1 lists it as a prerequisite for the same reason

The library row is the honest exception. Hashing a multi-gigabyte .db on every stage is not viable, so the practical substitute is the resolved absolute path plus whatever version string the library itself carries — and file normalize matters there, because a path through a current symlink records something that will mean a different library next quarter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict set prov library_path    [file normalize $lib]
dict set prov library_release [library_version_string $lib]

That is weaker than a digest and it catches the failure that actually happens: the symlink moved.

7. Where the check goes

At the start of a stage, on everything it is about to read. Not at the end — a stage that discovers its inputs were stale after six hours of work has wasted the six hours, which is Chapter 23.2 §1's cost-curve argument.

And at the start of a review, on everything the result claims to come from. That is the case the chapter title is really about: somebody is looking at a timing report from last month and asking whether it still describes the design. verify_inputs on its manifest answers that in a second, and the answer is frequently no.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# before trusting a result
set s [::prov::verify_inputs $manifest]
if {[llength [dict get $s missing]] || [llength [dict get $s changed]]} {
    error "this result no longer describes the current inputs" {} {PROV STALE}
}

The failure mode without that check is not a wrong number; it is a meeting. A review spends an hour discussing a violation that was fixed three weeks ago, because nothing in the report says which netlist it describes.

8. Exercises

Exercise 1. Why is a stale-input result worse than a wrong one?

Exercise 2. Give three ordinary ways a stale input reaches an analysis.

Exercise 3. What resolution does file mtime have, and what follows?

Exercise 4. Explain both measured cases: what < does and what <= does, and why neither is right.

Exercise 5. Which direction of error is the dangerous one, and why?

Exercise 6. Does the filesystem hold sub-second mtimes? Does file stat expose them?

Exercise 7. Why is ctime a poor tiebreaker?

Exercise 8. Even with nanosecond resolution, why is a timestamp the wrong instrument? Give two divergences.

Exercise 9. Name four properties that make an identity check better than a timestamp.

Exercise 10. Why is hashing a library impractical, and what is recorded instead?

Exercise 11. Why does file normalize matter for a library path?

Exercise 12. Where do the two checks go, and what is the failure mode of omitting the second?

Where this fits

Part of the Tcl curriculum.