sync: SHANGHAI-U9 2026-08-19 sim-rtl-flow 增约束原语阶段与版本说明树文件重命名,新增 ledger 迁移脚本

This commit is contained in:
bowei
2026-08-19 12:50:44 +08:00
parent d77d025c59
commit 6535dd83d4
3 changed files with 205 additions and 21 deletions
+97 -17
View File
@@ -2,14 +2,15 @@
name: sim-rtl-flow
description: >
Run the sim-rtl-flow LangGraph workflow at E:\LangGraph_Design\sim-rtl-flow — the
Simulink-to-RTL-to-hardware design pipeline: read the previous run's design history
doc sim-rtl-flow-<date>.md to decide what may be reused → Simulink -x principle sim
Simulink-to-RTL-to-hardware design pipeline: read the previous run's design version-tree
doc xxx_tree.md to decide what may be reused → Simulink -x principle sim
(sim-golden) → HUMAN sign-off on simulation performance → optional -q fixed-point →
parallel functional partition + RTL/SoC interface spec → HUMAN sign-off on the
partition (skippable when history says the partition is unchanged) → per-module
-E.slx nativization and Xilinx-IP replacement (parallel Send fanout, modules the
history proved good are skipped) → Vivado build + timing → hardware-in-the-loop via
fpga-imx6-flow or zynq-imx6-flow → write a fresh sim-rtl-flow-<date>.md summarising
history proved good are skipped) → constraint-primitive injection into the delivered
RTL tree → Vivado build + timing → hardware-in-the-loop via
fpga-imx6-flow or zynq-imx6-flow → write a fresh xxx<date>_tree.md summarising
every -x / -q / -E version. It drives the compiled graph in-process (no langgraph dev
server) and hands every subagent task back to THIS Claude session — and every human
checkpoint to the USER — through a record/replay ledger stored in the design project
@@ -26,8 +27,9 @@ description: >
## What this skill does
Drives the **sim-rtl-flow** graph (25 top-level nodes + a 5-node per-module subgraph,
1:1 with `Notes/PR01-1.md`, `Notes/PR01-2.md` and `Notes/PR01_sim_rtl_flow.drawio`).
Drives the **sim-rtl-flow** graph (27 top-level nodes + a 5-node per-module subgraph,
1:1 with `Notes/PR01-1.md`, `Notes/PR01-2.md` and `Notes/PR01_sim_rtl_flow.drawio`,
plus the constraint-primitive stage from `Notes/PR23_flow_constraint_stage.md`).
The graph contains **no** agent logic — it talks to the outside world through two
protocols: `SubagentBackend` (design work) and `ReviewGate` (the two mandatory human
@@ -64,7 +66,7 @@ export PYTHONIOENCODING=utf-8
The driver resolves the workflow checkout from `SIM_RTL_FLOW_ROOT` in
`~\skills_env.md`, so no path is baked into this skill.
## 📄 The design history doc `sim-rtl-flow-<date>.md`
## 📄 The design version-tree doc `xxx<date>_tree.md`
Every run **reads** the newest history doc and **writes** a new one. That doc is the
only mechanism stopping the flow from re-doing — and thereby re-breaking — design work
@@ -73,7 +75,7 @@ that a previous run already validated.
| | |
|---|---|
| Location | `<work_dir>/<history_dir>/`, where `history_dir` defaults to the folder holding `requirements_path` (usually `Notes`) |
| Name | `sim-rtl-flow-<YYYYMMDD-HH>.md` — date **and hour**, zero-padded, so name order == time order |
| Name | `<project_name><YYYYMMDD-HH>_tree.md` — project name + date **and hour**, zero-padded, so name order == time order. A hand-written `xxx_tree.md` with no stamp counts as the oldest (it is the human-supplied starting point). Pre-rename `sim-rtl-flow-<date>.md` files are still read when no `*_tree.md` exists |
| Read | `start` freezes the newest matching file into `input.json` as `history_path`; the `history_review#1` task interprets it |
| Written | the `design_summary#1` task, at the end of **every** run — completed *and* failed |
@@ -86,6 +88,7 @@ graph skip work:
| `redo_partition` + `partition_review_required` | both `false` → the **`functional_partition` human checkpoint is skipped** |
| `reuse_modules` | listed module → its `native:M` + `ip_replace:M` are skipped; result recorded as `stage: history_reuse` |
| `redo_modules` | listed module always re-runs, even if also in `reuse_modules` |
| `redo_constraints` + `constraint_spec_hash` | `redo_constraints: false` → the `constraint_primitives` stage is skipped, **but only if no module regenerated RTL this round** (graph-enforced). Answer `false` only after actually hashing the current spec file and matching it against the hash in the history doc; no hash recorded = `true` |
**Every default is "redo".** Omit a field and the stage runs. Fail the
`history_review` gate (`gate_passed: false`) and *nothing* is skipped. Recording a skip
@@ -151,6 +154,8 @@ From `Notes/PR01.md` and `Notes/PR01-2.md`; the graph routes on these.
4. **Never hand-edit MATLAB-generated RTL.** If IP replacement, Vivado timing, or HIL
needs a change there, return `change_scope: "simulink_module"` + `affected_modules`.
The graph then forces `modify_e:* → sync_sim:*` before any RTL is regenerated.
**The one sanctioned way to add something HDL Coder cannot express is
`constraint_primitives` — and it is not an exception to this rule.** See below.
5. **Editing a `-E` model obliges you to sync the `-x`/`-q` model.** `modify_e:*` edits
only the `-E`; the follow-up `sync_sim:*` uses **`simulink-rtl-redev`** to carry the
same change into the simulation model and re-verify against sim-golden. The
@@ -167,8 +172,53 @@ From `Notes/PR01.md` and `Notes/PR01-2.md`; the graph routes on these.
sim-rtl-flow main graph, then re-enters Vivado and HIL.
8. **Skipping a step needs written evidence, and every run leaves some.** The only
grounds for not executing a stage are a history doc that records it as verified and
unaffected. Every run ends by writing the next `sim-rtl-flow-<date>.md`; anything you
unaffected. Every run ends by writing the next `xxx<date>_tree.md`; anything you
could not verify goes in as *not verified*, so the next run redoes it.
9. **Synthesis attributes are injected, never typed in.** See the next section.
## 🔧 步骤 4-3 · 约束原语 `constraint_primitives`
Between `ip_replace:*` and `vivado`, one **global** node injects the synthesis
attributes HDL Coder cannot express — `DONT_TOUCH`, `shreg_extract="no"`, `KEEP`
(ILA instantiation later) — into the **delivered RTL tree**.
**Why it exists.** Synthesis merges bit-identical registers. A 32-bit pipeline
(`gk_tp_ra`) was folded away with `[Synth 8-4471] merging register … into
gk_RUN_out1_reg[31:0]`: the net name survives, the flip-flops are gone, and **the
timing report shows nothing at all** — it was caught only by the area accounting.
An earlier instance of the same merge cost 178 failing endpoints. HDL Coder has no
way to express an anti-merge attribute, so without this stage the design silently
ships without them, every single regeneration.
**Why it is not "hand-editing generated RTL".** The rule exists to stop the RTL and
the model from **silently diverging**. This stage is driven by a *declarative spec*,
is *idempotent*, and is *re-runnable*: regenerate the RTL, run it again, and the
result is byte-for-byte the same. A replayable injector produces no divergence, so
it is not a hand edit — and it is the **only** sanctioned route for these attributes.
Hand-typing one into a generated file is still forbidden.
**Where it sits, and why globally rather than per module.** The spec is one file for
the whole design and its attributes may target cross-module or top-level signals; the
injector operates on the whole `ip_replace`-output tree. A global node also covers
modules reused from history — those skip `native`/`ip_replace` but their RTL is still
in the delivered tree and still needs the attributes.
**Invariant: every path that changed the RTL tree re-injects before Vivado.**
`main_timing_rework` and `main_hil_rework` therefore return to
`constraint_primitives`, not straight to `vivado`. A plain `vivado` retry does not
re-inject (it changed nothing).
| failure shape | route |
|---|---|
| `gate_passed: false` | **retry `constraint_primitives`** — an injection gate failure is normally a spec/script problem, not a model problem. Never goes to `modify_e` on its own |
| `metadata.idempotent_verified: false` | fails the gate **on its own**, even with `gate_passed: true` — then retries |
| `needs_code_change: true` + `change_scope: "simulink_module"` + `affected_modules` | `modify_e:constraints` → `sync_sim:constraints` → only those modules re-run `native → ip_replace` → re-inject. Use this **only** when the spec proves the signal's shape in the `-E` model is wrong (target absent, structurally merged, width/hierarchy mismatch) |
**Reuse.** Skipped only when all three hold: a history doc exists, `history_review`
returned `redo_constraints: false` (it must have hashed the current spec and compared
it to the hash in the history doc), **and no module regenerated RTL this round**. The
last condition is enforced by the graph — a subagent cannot override it, because a
regenerated tree has lost last round's attributes. Default, as everywhere here, is redo.
## Run (agent path)
@@ -200,7 +250,8 @@ EOF
| `fixed_point_required` | `false` skips the `-q` stage |
| `rtl_modules` | submodule names; each gets its own parallel `native → ip_replace` chain. Non-empty. |
| `soc_modules` | MCU/SoC-side modules (interface-spec input) |
| `history_dir` | optional — folder holding `sim-rtl-flow-<date>.md`; defaults to the folder of `requirements_path` |
| `constraint_spec_path` | optional — the constraint-primitive spec file, relative to `work_dir`. A hint only; the subagent's reported `metadata.spec_path` is authoritative |
| `history_dir` | optional — folder holding `xxx<date>_tree.md`; defaults to the folder of `requirements_path` |
| `history_path` | optional — pin one history doc. Omit and `start` fills in the newest it finds; set `""` to declare "no history, do everything". |
### 2. Start — exits 10 with a handoff
@@ -244,7 +295,7 @@ keys with a default success — rehearsal only, and it refuses review keys.
| key | what you actually do | which skill to use |
|---|---|---|
| `history_review#n` | read `sim-rtl-flow-<date>.md` + the requirement doc, decide what may be reused; verdict in `metadata` (**first task of every run that has a history doc**) | inline — read the files, don't guess |
| `history_review#n` | read `xxx<date>_tree.md` + the requirement doc, decide what may be reused; verdict in `metadata` (**first task of every run that has a history doc**) | inline — read the files, don't guess |
| `simulink_x#n` | build/revise the `-x` sim, document the performance envelope | MATLAB MCP + `model-based-design-core:building-simulink-models`, `simulating-simulink-models` |
| `review:sim_performance#n` | **ask the user** to sign off on sim-golden | — (relay to user) |
| `fixed_point#n` | `-q` fixed-point, compare to `-x`, **and hold the word-length rule** (mult/add ≤16bit with truncation after the product; loop accumulators ≥32bit then truncated). Report `metadata.{wordlength_compliant, wordlength_violations, wordlengths}` — a violation fails the gate even when the performance matches sim-golden | same MATLAB skills |
@@ -254,13 +305,14 @@ keys with a default success — rehearsal only, and it refuses review keys.
| `module_plan#n` | split the RTL side into A/B/C…, give order + dependencies (**first pass only**) | inline |
| `native:<M>#n` | nativize M into HDL-capable blocks, save `<project>_M-E.slx`, generate RTL | `simulink_rtl_dev` |
| `ip_replace:<M>#n` | swap in Xilinx IP, cycle-true compare vs MATLAB RTL | `rtl-ip-replace` |
| `modify_e:module#n` / `modify_e:vivado#n` / `modify_e:hil#n` | edit **only** the named `-E.slx` (cycle-true / timing / HIL driven) | `simulink_rtl_dev` |
| `constraint_primitives#n` | inject the spec's synthesis attributes into the delivered RTL tree; gate = every spec entry hit its target + normalised line diff clean + **re-run byte-identical**. Report `metadata.{injected, unmatched, idempotent_verified, spec_path, spec_hash}` | inline (the injector script + spec; see `Notes/PR23_constraint_primitives.md`) |
| `modify_e:module#n` / `modify_e:vivado#n` / `modify_e:hil#n` / `modify_e:constraints#n` | edit **only** the named `-E.slx` (cycle-true / timing / HIL / wrong-signal-shape driven) | `simulink_rtl_dev` |
| `sync_sim:<source>#n` | carry that `-E` change into the matching `-x`/`-q` model and re-verify alignment | **`simulink-rtl-redev`** |
| `vivado#n` | build the Vivado project, synth+impl+bitstream, check all timing | `vivado-prj-create`, `vivado-mcp` |
| `hil:fpga-imx6-flow#n` / `hil:zynq-imx6-flow#n` | HIL debug, **read-only** | `fpga-imx6-flow` / `zynq-imx6-flow` |
| `timing_rework#n` | fix non-Simulink RTL / SoC / project timing | inline |
| `hil_rework#n` | apply the code change HIL asked for | inline |
| `design_summary#n` | write `<history_dir>/sim-rtl-flow-<YYYYMMDD-HH>.md` (path handed to you in `context.target_path`) covering every `-x`/`-q`/`-E` version, **the `-q` word lengths (multiplier/adder widths, truncation points, loop-accumulator widths) and whether they satisfy the word-length rule** — unrecorded widths force the next run to redo `-q` — the partition, per-module status, Vivado/HIL verdicts, what was reused and what is still open; return the real path in `artifacts` (**last task of every run, success or failure**) | inline |
| `design_summary#n` | write `<history_dir>/<project_name><YYYYMMDD-HH>_tree.md` (path handed to you in `context.target_path`) covering every `-x`/`-q`/`-E` version, **the `-q` word lengths (multiplier/adder widths, truncation points, loop-accumulator widths) and whether they satisfy the word-length rule** — unrecorded widths force the next run to redo `-q` — **the constraint-primitive spec path and its hash**, which attributes were injected and how many signals each hit (an unrecorded hash forces the next run to re-inject), the partition, per-module status, Vivado/HIL verdicts, what was reused and what is still open; return the real path in `artifacts` (**last task of every run, success or failure**) | inline |
`#n` is the n-th time that key has been requested this run — retries and rework produce
`#2`, `#3`, …
@@ -281,6 +333,7 @@ review: {"approved": true, "notes": "", "metadata": {}}
| `success` + `gate_passed` | every design node — `false` → retry, then `failed` after 10 attempts |
| `artifacts` | `simulink_x`'s first artifact **becomes sim-golden**; `vivado`'s go to HIL as the bitstream |
| `metadata.wordlength_compliant` + `wordlength_violations` + `wordlengths` | `fixed_point` — a violation (or a bare `wordlength_compliant: false`) fails the gate on its own; `wordlengths` is what the history doc records |
| `metadata.injected` + `unmatched` + `idempotent_verified` + `spec_path` + `spec_hash` | `constraint_primitives` — `idempotent_verified: false` fails the gate on its own; **`spec_hash` is what the history doc records**, and without it the next run must re-inject |
| `timing_met` | `vivado` only |
| `hil_passed` | `hil:*` only |
| `needs_code_change` + `change_scope` + `affected_modules` | `vivado` and `hil:*` — the rework router |
@@ -305,6 +358,7 @@ A failing `ip_replace:<M>` (cycle-true mismatch) enters the same chain on its ow
| after `-x` / `-q` | `interfaces#1` **and** `partition#1` |
| module fanout | one `native:<M>#1` per module (3 for `["A","B","C"]`) — **minus any module reused from history** |
| next module stage | one `ip_replace:<M>#1` per module |
| after the module gate | `constraint_primitives#1` — a single global task, never per module |
`history_review#1` and `design_summary#1` are always single-task batches, at the very
start and the very end of the run.
@@ -334,12 +388,16 @@ decisions) to the input JSON; `start`/`resume` strip both.
## Other commands
```bash
"$PY" "$RUN" $W status # ledger + pending + final status
"$PY" "$RUN" $W status # ledger + pending + final status (read-only)
"$PY" "$RUN" schema # both answer templates
"$PY" "$RUN" mermaid # graph as Mermaid (see gotcha)
"$PY" -m unittest discover -s tests # project's own 16 tests — all pass
"$PY" -m unittest discover -s tests # project's own 59 tests — all pass
```
`scripts/migrate_constraint_ledger.py <run_dir> <rounds> [--apply]` back-fills
`constraint_primitives#N` placeholders into a ledger written **before** that node
existed — see the gotcha below.
Global flags go **before** the subcommand: `--project`, `--work-dir`, `--thread-id`,
`--recursion-limit` (200), `--max-attempts` (10).
@@ -368,9 +426,30 @@ Global flags go **before** the subcommand: `--project`, `--work-dir`, `--thread-
and the graph substitutes `<project_name>-x.slx`. Always record the real filename.
- **`module_plan` runs only on the first pass.** Rework rounds reuse the plan, so you
will not be asked to re-partition the module list mid-run.
- **`constraint_primitives` failing is not a reason to touch the `-E` model.** Record
`gate_passed: false` and fix the spec or the injector; the graph retries. Only escalate
to `change_scope: "simulink_module"` when the target signal genuinely isn't the shape
the spec needs. Getting this backwards turns a spec typo into a model rework round.
- **Recording `idempotent_verified: true` without re-running the injector defeats the
whole justification.** Idempotence is what makes this stage *not* a hand edit. If you
did not actually re-run and compare, record `false`.
- **`spec_hash` is the next run's only basis for skipping this stage.** Omit it and the
next run re-injects — which is the safe default, not a bug.
- **Re-recording a key requires editing `ledger.json` by hand.** `record` only accepts
currently-pending keys; anything already in the ledger is replayed forever. To redo a
step, delete its entry *and everything after it*, then `resume`.
- **Adding a node rewinds every in-flight run.** Replay is keyed by
`<task>#<n>`, so a ledger written before `constraint_primitives` existed halts at
`constraint_primitives#1` — right after the *first* module round, potentially many
rounds back from where the run actually was. The run is not damaged (nothing is
deleted), it just asks one extra question per module round already completed. Fix:
`scripts/migrate_constraint_ledger.py <run_dir> <number-of-module-rounds> --apply`,
which backs the ledger up to `ledger.json.pre_constraint_migration` and seeds one
honest placeholder per past round (`migration_placeholder: true`, empty `injected`,
empty `spec_hash` — those rounds really did go to Vivado uninjected). The next round
gets a real injection. **Rehearse on a copy first**: copy the run dir elsewhere and
`resume --work-dir <copy>`; `status` alone cannot prove replay because it never
invokes the graph.
- **Runs created before the PR01-2 change live in the old location**
(`<SIM_RTL_FLOW_ROOT>\runs\<id>\`) and their ledgers have no `review:*` or
`module_plan` keys, so they cannot be resumed under the current graph. Start a fresh
@@ -380,7 +459,7 @@ Global flags go **before** the subcommand: `--project`, `--work-dir`, `--thread-
launched — always use an absolute path.
- **`start` wipes the ledger** unless `--keep-ledger`. Use a fresh `--thread-id` per run.
- **`start` freezes `history_path` into `input.json`.** It must not be re-scanned on
every `resume`: the run itself writes a *newer* `sim-rtl-flow-<date>.md` at the end,
every `resume`: the run itself writes a *newer* `xxx<date>_tree.md` at the end,
and a re-scan would make a later replay reference a different history doc than the
one already recorded in the ledger. Pinning it also makes `resume` after
`WORKFLOW FINISHED` a no-op instead of a spurious new handoff.
@@ -390,7 +469,7 @@ Global flags go **before** the subcommand: `--project`, `--work-dir`, `--thread-
- **`design_summary` is the deliverable, not a formality.** Record it only after the
file really exists on disk with real content; `artifacts[0]` must be its path.
- **The summary can fail an otherwise-good run.** `status: failed` with
`运行已结束但历史设计说明文件 … 未能生成` means the design work passed but the history
`运行已结束但版本说明树文件 … 未能生成` means the design work passed but the history
doc did not get written — fix that and re-run the summary, don't ignore it.
## Troubleshooting
@@ -408,5 +487,6 @@ Global flags go **before** the subcommand: `--project`, `--work-dir`, `--thread-
| `needs_code_change=true requires an explicit change_scope` | add `change_scope` |
| `MissingReviewGateError` | only if the graph is driven without a review gate — the driver always supplies one |
| `device_family must be 'fpga-imx6' or 'zynq-imx6'` | raised in `initialize`; exit 2 |
| an old run suddenly asks for `constraint_primitives#1` | its ledger predates the node — run `scripts/migrate_constraint_ledger.py` (see gotcha) |
| exit 2 + `recursion_limit` | a rework loop isn't converging — raise `--recursion-limit`, or stop recording `needs_code_change` for the same module |
| mojibake in the Chinese prompts | `export PYTHONIOENCODING=utf-8` |