diff --git a/flow/scripts/synth_canonicalize.tcl b/flow/scripts/synth_canonicalize.tcl index 4ba62a4523..58a2044e6a 100644 --- a/flow/scripts/synth_canonicalize.tcl +++ b/flow/scripts/synth_canonicalize.tcl @@ -1,5 +1,11 @@ source $::env(SCRIPTS_DIR)/synth_preamble.tcl read_design_sources +# Fingerprint the design state right after the HDL frontend +# (slang / builtin Verilog) returns, before any other pass runs. +# An unstable hash here means the frontend itself is non-idempotent +# -- distinct from drift introduced later by hierarchy / opt_clean +# / etc. Surfaced as a literal warning-level rule via genRuleFile.py. +write_state_hash synth__post_read_sources__hash hierarchy -check -top $::env(DESIGN_NAME) diff --git a/flow/scripts/synth_preamble.tcl b/flow/scripts/synth_preamble.tcl index af43512c0f..5e9a052f1a 100644 --- a/flow/scripts/synth_preamble.tcl +++ b/flow/scripts/synth_preamble.tcl @@ -3,6 +3,39 @@ yosys -import source $::env(SCRIPTS_DIR)/util.tcl erase_non_stage_variables synth +# Fingerprint the current yosys design state to a `: ` +# line in the surrounding yosys log so `genMetrics.py` can pick it up. +# +# `setattr -unset src` strips file:line attribute lines from the +# RTLIL before hashing so the hash is path-independent (bazel sandbox +# paths differ from the classic-make build's relative paths; without +# stripping, hashes always differ trivially). +# +# The strip is wrapped in `design -push` / `design -pop` so it does +# not leak into the rest of the synth run -- src attributes are +# preserved for back-annotation / debugging downstream. This matters +# specifically for builds without `SYNTH_REPEATABLE_BUILD=1`, where +# synth_canonicalize.tcl deliberately leaves src attrs alone after +# the canonical-RTLIL write. +proc write_state_hash { metric } { + design -push + # `*/*` strips src attrs across objects in *all* modules. Bare `*` + # only targets the current module's objects, which is fine after + # `hierarchy -check -top` collapses things to one design, but + # `write_state_hash` is called before `hierarchy` here, so the + # post-frontend state may have many separate modules whose src + # attrs would otherwise survive into the hashed RTLIL and break + # path-independence. + setattr -unset src */* + setattr -mod -unset src * + set tmp $::env(OBJECTS_DIR)/.${metric}.tmp.rtlil + write_rtlil $tmp + design -pop + set sha [lindex [split [exec sha1sum $tmp]] 0] + file delete $tmp + puts "${metric}: $sha" +} + # If using a cached, gate level netlist, then copy over to the results dir with # preserve timestamps flag set. If you don't, subsequent runs will cause the # floorplan step to be re-executed. diff --git a/flow/util/genMetrics.py b/flow/util/genMetrics.py index 6424712e27..7ef1f899aa 100755 --- a/flow/util/genMetrics.py +++ b/flow/util/genMetrics.py @@ -262,10 +262,29 @@ def extract_metrics( rptPath + "/synth_stat.txt", ) - # Netlist hashes: fingerprints of the canonical RTLIL (pre-ABC) and - # the final post-synthesis Verilog so the rules-base.json check - # (level=warning) flags when bazel-built vs make-built yosys - # disagree for the same RTL. + # Netlist hashes: fingerprints at three points in the yosys + # pipeline so the rules-base.json check (level=warning) can + # isolate frontend drift (yosys-slang isn't idempotent) from + # mid-synth drift from ABC drift. + # + # post_read_sources = state right after `read_design_sources` + # (HDL frontend output only) + # canonical_netlist = state after `opt_clean -purge` + # (= `1_1_yosys_canonicalize.rtlil`) + # netlist = `1_2_yosys.v`, post-ABC + # + # `post_read_sources` is emitted by synth_canonicalize.tcl via + # `write_state_hash` (synth_preamble.tcl) as a `: ` + # line in 1_1_yosys_canonicalize.log; the other two come straight + # from `file_sha1` of the already-emitted RTLIL / Verilog. + extractTagFromFile( + "synth__post_read_sources__hash", + metrics_dict, + r"^\s*synth__post_read_sources__hash:\s+([0-9a-f]{40})\s*$", + logPath + "/1_1_yosys_canonicalize.log", + t=str, + required=False, + ) metrics_dict["synth__canonical_netlist__hash"] = file_sha1( resultPath + "/1_1_yosys_canonicalize.rtlil" ) diff --git a/flow/util/genRuleFile.py b/flow/util/genRuleFile.py index 358d59231e..58d240e187 100755 --- a/flow/util/genRuleFile.py +++ b/flow/util/genRuleFile.py @@ -79,6 +79,11 @@ def gen_rule_file( # surfaces as a [WARN] diagnostic in checkMetadata.py without # failing the build, matching how rules-base.json already # treats warning counts. + "synth__post_read_sources__hash": { + "mode": "literal", + "compare": "==", + "level": "warning", + }, "synth__canonical_netlist__hash": { "mode": "literal", "compare": "==",