sync: SHANGHAI-U9 2026-08-19 sim-rtl-flow 增约束原语阶段与版本说明树文件重命名,新增 ledger 迁移脚本
This commit is contained in:
+97
-17
@@ -2,14 +2,15 @@
|
|||||||
name: sim-rtl-flow
|
name: sim-rtl-flow
|
||||||
description: >
|
description: >
|
||||||
Run the sim-rtl-flow LangGraph workflow at E:\LangGraph_Design\sim-rtl-flow — the
|
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
|
Simulink-to-RTL-to-hardware design pipeline: read the previous run's design version-tree
|
||||||
doc sim-rtl-flow-<date>.md to decide what may be reused → Simulink -x principle sim
|
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 →
|
(sim-golden) → HUMAN sign-off on simulation performance → optional -q fixed-point →
|
||||||
parallel functional partition + RTL/SoC interface spec → HUMAN sign-off on the
|
parallel functional partition + RTL/SoC interface spec → HUMAN sign-off on the
|
||||||
partition (skippable when history says the partition is unchanged) → per-module
|
partition (skippable when history says the partition is unchanged) → per-module
|
||||||
-E.slx nativization and Xilinx-IP replacement (parallel Send fanout, modules the
|
-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
|
history proved good are skipped) → constraint-primitive injection into the delivered
|
||||||
fpga-imx6-flow or zynq-imx6-flow → write a fresh sim-rtl-flow-<date>.md summarising
|
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
|
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
|
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
|
checkpoint to the USER — through a record/replay ledger stored in the design project
|
||||||
@@ -26,8 +27,9 @@ description: >
|
|||||||
|
|
||||||
## What this skill does
|
## What this skill does
|
||||||
|
|
||||||
Drives the **sim-rtl-flow** graph (25 top-level nodes + a 5-node per-module subgraph,
|
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`).
|
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
|
The graph contains **no** agent logic — it talks to the outside world through two
|
||||||
protocols: `SubagentBackend` (design work) and `ReviewGate` (the two mandatory human
|
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
|
The driver resolves the workflow checkout from `SIM_RTL_FLOW_ROOT` in
|
||||||
`~\skills_env.md`, so no path is baked into this skill.
|
`~\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
|
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
|
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`) |
|
| 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 |
|
| 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 |
|
| 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** |
|
| `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` |
|
| `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_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
|
**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
|
`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
|
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`.
|
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 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
|
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
|
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
|
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.
|
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
|
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
|
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.
|
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)
|
## Run (agent path)
|
||||||
|
|
||||||
@@ -200,7 +250,8 @@ EOF
|
|||||||
| `fixed_point_required` | `false` skips the `-q` stage |
|
| `fixed_point_required` | `false` skips the `-q` stage |
|
||||||
| `rtl_modules` | submodule names; each gets its own parallel `native → ip_replace` chain. Non-empty. |
|
| `rtl_modules` | submodule names; each gets its own parallel `native → ip_replace` chain. Non-empty. |
|
||||||
| `soc_modules` | MCU/SoC-side modules (interface-spec input) |
|
| `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". |
|
| `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
|
### 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 |
|
| 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` |
|
| `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) |
|
| `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 |
|
| `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 |
|
| `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` |
|
| `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` |
|
| `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`** |
|
| `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` |
|
| `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` |
|
| `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 |
|
| `timing_rework#n` | fix non-Simulink RTL / SoC / project timing | inline |
|
||||||
| `hil_rework#n` | apply the code change HIL asked for | 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
|
`#n` is the n-th time that key has been requested this run — retries and rework produce
|
||||||
`#2`, `#3`, …
|
`#2`, `#3`, …
|
||||||
@@ -281,6 +333,7 @@ review: {"approved": true, "notes": "", "metadata": {}}
|
|||||||
| `success` + `gate_passed` | every design node — `false` → retry, then `failed` after 10 attempts |
|
| `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 |
|
| `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.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 |
|
| `timing_met` | `vivado` only |
|
||||||
| `hil_passed` | `hil:*` only |
|
| `hil_passed` | `hil:*` only |
|
||||||
| `needs_code_change` + `change_scope` + `affected_modules` | `vivado` and `hil:*` — the rework router |
|
| `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` |
|
| 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** |
|
| 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 |
|
| 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
|
`history_review#1` and `design_summary#1` are always single-task batches, at the very
|
||||||
start and the very end of the run.
|
start and the very end of the run.
|
||||||
@@ -334,12 +388,16 @@ decisions) to the input JSON; `start`/`resume` strip both.
|
|||||||
## Other commands
|
## Other commands
|
||||||
|
|
||||||
```bash
|
```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" schema # both answer templates
|
||||||
"$PY" "$RUN" mermaid # graph as Mermaid (see gotcha)
|
"$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`,
|
Global flags go **before** the subcommand: `--project`, `--work-dir`, `--thread-id`,
|
||||||
`--recursion-limit` (200), `--max-attempts` (10).
|
`--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.
|
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
|
- **`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.
|
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
|
- **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
|
currently-pending keys; anything already in the ledger is replayed forever. To redo a
|
||||||
step, delete its entry *and everything after it*, then `resume`.
|
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**
|
- **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
|
(`<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
|
`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.
|
launched — always use an absolute path.
|
||||||
- **`start` wipes the ledger** unless `--keep-ledger`. Use a fresh `--thread-id` per run.
|
- **`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
|
- **`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
|
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
|
one already recorded in the ledger. Pinning it also makes `resume` after
|
||||||
`WORKFLOW FINISHED` a no-op instead of a spurious new handoff.
|
`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
|
- **`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.
|
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
|
- **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.
|
doc did not get written — fix that and re-run the summary, don't ignore it.
|
||||||
|
|
||||||
## Troubleshooting
|
## 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` |
|
| `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 |
|
| `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 |
|
| `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 |
|
| 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` |
|
| mojibake in the Chinese prompts | `export PYTHONIOENCODING=utf-8` |
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
"""Seed constraint_primitives#N placeholders into an existing sim-rtl-flow ledger.
|
||||||
|
|
||||||
|
The `constraint_primitives` node was added to the graph after this run started.
|
||||||
|
Replay is keyed by `<task>#<n>`, so a run whose ledger predates the node rewinds
|
||||||
|
to the first unrecorded `constraint_primitives#1` instead of resuming where it
|
||||||
|
left off. Seeding one placeholder per module round that already happened lets
|
||||||
|
the replay walk straight through to the real pending key.
|
||||||
|
|
||||||
|
The placeholders record the truth: those rounds went to Vivado WITHOUT any
|
||||||
|
constraint injection, because the step did not exist yet. The first round that
|
||||||
|
runs after the migration gets a real injection.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python migrate_constraint_ledger.py <run_dir> <count> [--apply]
|
||||||
|
Without --apply it only prints what it would do.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PLACEHOLDER = {
|
||||||
|
"success": True,
|
||||||
|
"gate_passed": True,
|
||||||
|
"artifacts": [],
|
||||||
|
"notes": (
|
||||||
|
"迁移占位:这一轮模块处理发生在『约束原语』步骤并入 sim-rtl-flow 之前,"
|
||||||
|
"当时并未做任何综合属性注入,该轮 Vivado 跑的是未注入的 RTL。"
|
||||||
|
"本条只为让 replay 走过历史轮次;之后的每一轮都会真正执行注入。"
|
||||||
|
),
|
||||||
|
"timing_met": True,
|
||||||
|
"hil_passed": True,
|
||||||
|
"needs_code_change": False,
|
||||||
|
"change_scope": "none",
|
||||||
|
"affected_modules": [],
|
||||||
|
"metadata": {
|
||||||
|
"migration_placeholder": True,
|
||||||
|
"injected": {},
|
||||||
|
"unmatched": [],
|
||||||
|
"spec_path": "",
|
||||||
|
"spec_hash": "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str]) -> int:
|
||||||
|
if len(argv) < 3:
|
||||||
|
print(__doc__)
|
||||||
|
return 2
|
||||||
|
run_dir = Path(argv[1])
|
||||||
|
count = int(argv[2])
|
||||||
|
apply = "--apply" in argv
|
||||||
|
|
||||||
|
ledger_path = run_dir / "ledger.json"
|
||||||
|
ledger = json.loads(ledger_path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
added = []
|
||||||
|
for n in range(1, count + 1):
|
||||||
|
key = f"constraint_primitives#{n}"
|
||||||
|
if key in ledger:
|
||||||
|
print(f" skip {key} (already recorded)")
|
||||||
|
continue
|
||||||
|
ledger[key] = dict(PLACEHOLDER)
|
||||||
|
added.append(key)
|
||||||
|
|
||||||
|
print(f"ledger: {ledger_path}")
|
||||||
|
print(f"would add: {added}")
|
||||||
|
if not apply:
|
||||||
|
print("(dry run — pass --apply to write)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
backup = ledger_path.with_suffix(".json.pre_constraint_migration")
|
||||||
|
if not backup.exists():
|
||||||
|
backup.write_text(
|
||||||
|
json.dumps(
|
||||||
|
json.loads(ledger_path.read_text(encoding="utf-8")),
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
print(f"backup -> {backup}")
|
||||||
|
ledger_path.write_text(
|
||||||
|
json.dumps(ledger, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||||
|
)
|
||||||
|
print(f"applied {len(added)} placeholder(s)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main(sys.argv))
|
||||||
@@ -402,7 +402,7 @@ def _print_final(state: Any, replayed: list[str]) -> None:
|
|||||||
if review:
|
if review:
|
||||||
mark = "批准" if review.get("approved") else "驳回"
|
mark = "批准" if review.get("approved") else "驳回"
|
||||||
if (review.get("metadata") or {}).get("skipped_by_history"):
|
if (review.get("metadata") or {}).get("skipped_by_history"):
|
||||||
mark = "按历史设计说明文件跳过"
|
mark = "按版本说明树文件跳过"
|
||||||
print(f"{label} : {mark} {review.get('notes', '')}".rstrip())
|
print(f"{label} : {mark} {review.get('notes', '')}".rstrip())
|
||||||
plan = state.get("history_plan") or {}
|
plan = state.get("history_plan") or {}
|
||||||
print(f"历史文件 · 参考 : {plan.get('history_path') or '(none)'}")
|
print(f"历史文件 · 参考 : {plan.get('history_path') or '(none)'}")
|
||||||
@@ -421,6 +421,17 @@ def _print_final(state: Any, replayed: list[str]) -> None:
|
|||||||
f" - {item['module']}: stage={item['stage']} "
|
f" - {item['module']}: stage={item['stage']} "
|
||||||
f"success={item['success']} artifacts={item.get('artifacts')}"
|
f"success={item['success']} artifacts={item.get('artifacts')}"
|
||||||
)
|
)
|
||||||
|
constraints = state.get("constraint_result") or {}
|
||||||
|
if constraints:
|
||||||
|
print(
|
||||||
|
"约束原语 : "
|
||||||
|
f"source={constraints.get('source')} "
|
||||||
|
f"passed={constraints.get('passed')} "
|
||||||
|
f"spec={constraints.get('spec_path') or '(未回报)'} "
|
||||||
|
f"hash={constraints.get('spec_hash') or '(未回报 — 下轮必须重注)'} "
|
||||||
|
f"injected={_jsonable(constraints.get('injected'))} "
|
||||||
|
f"idempotent={constraints.get('idempotent_verified')}"
|
||||||
|
)
|
||||||
print(f"vivado : {_jsonable(state.get('vivado_result'))}")
|
print(f"vivado : {_jsonable(state.get('vivado_result'))}")
|
||||||
print(f"hil : {_jsonable(state.get('hil_result'))}")
|
print(f"hil : {_jsonable(state.get('hil_result'))}")
|
||||||
print(f"\nevents ({len(state.get('events', []))}):")
|
print(f"\nevents ({len(state.get('events', []))}):")
|
||||||
@@ -473,8 +484,8 @@ def cmd_start(args, api) -> int:
|
|||||||
payload.pop("outcomes", None)
|
payload.pop("outcomes", None)
|
||||||
payload.pop("reviews", None)
|
payload.pop("reviews", None)
|
||||||
work_dir = _resolve_work_dir(args, payload)
|
work_dir = _resolve_work_dir(args, payload)
|
||||||
# 把"参考哪一份历史设计说明文件"冻结进 input.json:本次运行结束时会写出一份
|
# 把"参考哪一份版本说明树文件"冻结进 input.json:本次运行结束时会写出一份
|
||||||
# 新的 sim-rtl-flow-<date>.md,若每次 resume 都重新扫描目录,replay 就会在
|
# 新的 xxx<date>_tree.md,若每次 resume 都重新扫描目录,replay 就会在
|
||||||
# 半路换掉参考文件,走出一条与已记录 ledger 不同的路。
|
# 半路换掉参考文件,走出一条与已记录 ledger 不同的路。
|
||||||
if "history_path" not in payload:
|
if "history_path" not in payload:
|
||||||
history_dir = api["history_dir_for"](
|
history_dir = api["history_dir_for"](
|
||||||
@@ -482,7 +493,7 @@ def cmd_start(args, api) -> int:
|
|||||||
)
|
)
|
||||||
payload["history_dir"] = history_dir
|
payload["history_dir"] = history_dir
|
||||||
payload["history_path"] = api["discover_history_doc"](
|
payload["history_path"] = api["discover_history_doc"](
|
||||||
str(work_dir), history_dir
|
str(work_dir), history_dir, payload.get("project_name", "")
|
||||||
)
|
)
|
||||||
run = RunDir(work_dir, args.thread_id)
|
run = RunDir(work_dir, args.thread_id)
|
||||||
run.set_input(payload)
|
run.set_input(payload)
|
||||||
|
|||||||
Reference in New Issue
Block a user