493 lines
32 KiB
Markdown
493 lines
32 KiB
Markdown
---
|
||
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 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) → 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
|
||
being worked on.
|
||
|
||
ALWAYS use this skill whenever the user wants to run, start, drive, resume or continue
|
||
the sim-rtl-flow workflow / Simulink→RTL design flow, or says things like "启动
|
||
sim-rtl-flow", "跑 sim-rtl-flow", "运行仿真到RTL的设计流程", "执行 sim-rtl-flow 设计任务",
|
||
"继续 sim-rtl-flow", "run the sim to rtl flow", "resume the sim-rtl-flow run", even if
|
||
they don't name the project path. Also use it when the user asks to record a subagent
|
||
result, to approve or reject one of the two human checkpoints, or to rehearse the graph
|
||
with the scripted backend.
|
||
---
|
||
|
||
## What this skill does
|
||
|
||
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
|
||
sign-offs). This skill plugs a **record/replay** implementation into both: the graph
|
||
runs in-process until it needs an answer that isn't recorded yet, then stops and prints
|
||
the request. **You are the subagent; the USER is the reviewer.** You do the work,
|
||
`record` the answer, and `resume` — the driver replays the ledger, deterministically
|
||
walks back to where it stopped, and asks for the next thing.
|
||
|
||
| Item | Value |
|
||
|---|---|
|
||
| Project root | 见 `~\skills_env.md` 的 `SIM_RTL_FLOW_ROOT`(driver 自动读取,也可 `--project` 覆盖) |
|
||
| Conda env | `langgraph` (path 见 `~\skills_env.md` 的 `CONDA_LANGGRAPH_PYTHON`;装环境用 `/langgraph-vivado-install`) |
|
||
| Graph | `sim_rtl_flow.graph:build_workflow` (in-process, no `langgraph dev`) |
|
||
| Driver | `%USERPROFILE%\.claude\skills\sim-rtl-flow\scripts\run_sim_rtl_flow.py` |
|
||
| **Run state** | **`<work_dir>\.sim_rtl_flow\runs\<thread-id>\`** — inside the *design project*, never in the workflow repo |
|
||
| Max attempts | 10 per gate (default) |
|
||
|
||
There is **no LangGraph checkpointer** in the driver, on purpose: graph nodes have no
|
||
side effects of their own (all side effects live in the subagents, which are replayed),
|
||
so re-invoking from scratch is cheaper and immune to partial-superstep resume semantics.
|
||
`ledger.json` *is* the persistence.
|
||
|
||
**Drive the driver with the Bash tool (Git Bash), not PowerShell** — PowerShell 5.1
|
||
mangles the double quotes inside `--result '{...}'` payloads. Every command below was
|
||
run this way and verified.
|
||
|
||
```bash
|
||
PY="<CONDA_LANGGRAPH_PYTHON — see ~\skills_env.md>"
|
||
RUN="$USERPROFILE/.claude/skills/sim-rtl-flow/scripts/run_sim_rtl_flow.py"
|
||
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 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
|
||
that a previous run already validated.
|
||
|
||
| | |
|
||
|---|---|
|
||
| Location | `<work_dir>/<history_dir>/`, where `history_dir` defaults to the folder holding `requirements_path` (usually `Notes`) |
|
||
| 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 |
|
||
|
||
`history_review` returns its verdict in `metadata`, and that verdict is what lets the
|
||
graph skip work:
|
||
|
||
| metadata field | effect if `false` / listed |
|
||
|---|---|
|
||
| `redo_fixed_point` | `false` → the `-q` stage is skipped entirely. Before answering `false`, re-check the historic `-q` against the **current** word-length rule — widths that violate it, or a history doc that never recorded widths, mean `true` |
|
||
| `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
|
||
you did not verify means a later gate — Vivado, HIL, sim-golden — validates a design
|
||
nobody checked this run.
|
||
|
||
Four things force everything back to redo, regardless of the history doc:
|
||
|
||
1. `simulink_x` running a second time (a rejected `sim_performance`, or a retry) —
|
||
sim-golden may have moved, so the history describes a *different* `-x`;
|
||
2. `partition#n` returning `metadata.partition_changed: true` — the human checkpoint
|
||
comes back even if history said to skip it;
|
||
3. any module named by Vivado or HIL rework — the reuse pool is consumed after the
|
||
first module round and never applies to a rework round;
|
||
4. the user having already answered `functional_partition` once — a rejection can never
|
||
be swallowed by a later "history says skip it".
|
||
|
||
When you write the summary, `artifacts[0]` must be the real path you wrote. If the
|
||
summary cannot be written after `--max-attempts`, the run is reported **failed** even
|
||
if the design itself passed — because the next run would silently lose its basis for
|
||
skipping anything.
|
||
|
||
## ⚠️ The two human checkpoints are the user's decision, not yours
|
||
|
||
The graph stops at two points that **an agent may not decide**:
|
||
|
||
| checkpoint | where | what the user confirms |
|
||
|---|---|---|
|
||
| `sim_performance` | after step 1 | the `-x` sim meets the requirement and may be frozen as **sim-golden** |
|
||
| `functional_partition` | after step 3 | the pure-sim / RTL / SoC split and the RTL↔SoC interface doc — **skipped** only when the history doc says the partition is unchanged (see above) |
|
||
|
||
When one comes up the driver prints a `!!!` banner. **Relay the question and its context
|
||
to the user verbatim, then wait.** Do not approve on your own judgement, do not infer
|
||
approval from "the numbers look fine", and do not use `--all-ok` (the driver refuses it
|
||
for review keys anyway). A rejection **must** carry the user's reason.
|
||
|
||
```bash
|
||
"$PY" "$RUN" --work-dir <proj> --thread-id <id> record --key review:sim_performance#1 --approve
|
||
"$PY" "$RUN" --work-dir <proj> --thread-id <id> record --key review:sim_performance#1 \
|
||
--reject --notes "带外抑制不足 6dB"
|
||
```
|
||
|
||
Rejecting `sim_performance` re-runs step 1; rejecting `functional_partition` re-runs the
|
||
whole partition + interface superstep.
|
||
|
||
## Non-negotiable design rules the graph enforces
|
||
|
||
From `Notes/PR01.md` and `Notes/PR01-2.md`; the graph routes on these.
|
||
|
||
1. **sim-golden is the only acceptance criterion.** `-q`, the partition, and every `-E`
|
||
version must match the `-x` sim. If it doesn't, record `gate_passed: false` — never
|
||
record a pass you didn't verify.
|
||
2. **The fixed-point word-length rule ranks alongside sim-golden.** Only multiplies and
|
||
adds of 16bit or less — after a 16×16 product, truncate back to 16bit before anything
|
||
downstream; widths must not accumulate stage by stage. The one exception is loop
|
||
accumulation: the accumulator is at least 32bit, and its result is truncated before it
|
||
feeds the rest of the design. `fixed_point#n` fails on a violation even when the
|
||
performance matches sim-golden, and `native:*`, `modify_e:*`, `sync_sim:*` may not
|
||
widen anything back. The rule and its machine-readable form arrive in every affected
|
||
task as `context.wordlength_rule` / `context.wordlength_limits`.
|
||
3. **One `-E.slx` per RTL submodule**, saved separately (`<project>_A-E.slx`, …). Do not
|
||
nativize all submodules in one Simulink model.
|
||
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
|
||
derivation script does *not* translate native-core edits into the m implementation,
|
||
so changing one side alone means the RTL moved while the simulation still validates
|
||
the old behaviour. If they don't align, the graph goes back to `modify_e:*` — it will
|
||
**not** let you regenerate RTL.
|
||
6. **Timing fixes change the RTL, not the constraints.** Prefer adding pipeline/delay
|
||
stages. Only touch timing constraints when the constraint itself is plainly wrong,
|
||
and say why in `notes`.
|
||
7. **HIL is strictly read-only.** `hil:*` arrives with `allow_code_changes=false`. Run
|
||
`fpga-imx6-flow` / `zynq-imx6-flow` for *diagnosis only*; the moment a code change is
|
||
needed, stop that sub-flow and report it back here — the fix happens in the
|
||
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 `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)
|
||
|
||
### 1. Write the workflow input
|
||
|
||
`work_dir` is the design project. **All run state goes under
|
||
`<work_dir>/.sim_rtl_flow/`** — that is where `resume`/`record`/`status` will look.
|
||
|
||
```bash
|
||
cat > /c/Users/Administrator/AppData/Local/Temp/claude/input.json <<'EOF'
|
||
{
|
||
"requirements_path": "Notes/requirements.md",
|
||
"work_dir": "E:/Xilinx_design/my_project",
|
||
"project_name": "demo",
|
||
"device_family": "fpga-imx6",
|
||
"fixed_point_required": true,
|
||
"rtl_modules": ["A", "B", "C"],
|
||
"soc_modules": ["control"]
|
||
}
|
||
EOF
|
||
```
|
||
|
||
| field | meaning |
|
||
|---|---|
|
||
| `requirements_path` | user's requirement `.md`, relative to `work_dir` |
|
||
| `work_dir` | the design project — **absolute path**; also where run state is written |
|
||
| `project_name` | artifact prefix — `<name>-x.slx`, `<name>_A-E.slx`, `<name>.bit` |
|
||
| `device_family` | `fpga-imx6` → HIL via `fpga-imx6-flow`; `zynq-imx6` → `zynq-imx6-flow`. Anything else raises at `initialize`. |
|
||
| `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) |
|
||
| `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
|
||
|
||
```bash
|
||
"$PY" "$RUN" --thread-id my-design-001 start --input /c/Users/Administrator/AppData/Local/Temp/claude/input.json
|
||
```
|
||
|
||
`start` takes `work_dir` from the input JSON and prints where state landed. Verified:
|
||
|
||
```
|
||
work dir : E:\Xilinx_design\my_project
|
||
run dir : E:\Xilinx_design\my_project\.sim_rtl_flow\runs\my-design-001
|
||
========================================================================
|
||
HANDOFF — 1 subagent task(s), 0 human review(s)
|
||
========================================================================
|
||
--- key: simulink_x#1
|
||
role : simulink-principle-agent
|
||
permission : WRITE ALLOWED
|
||
prompt : 根据需求文档创建或修订 Simulink -x 原理仿真;...
|
||
```
|
||
|
||
Exit codes: **10** = handoff pending, **0** = completed, **1** = `failed`, **2** = crash.
|
||
|
||
### 3. Answer, then resume
|
||
|
||
Every later command needs to find the run, so pass `--work-dir` (or `cd` into it):
|
||
|
||
```bash
|
||
W="--work-dir E:/Xilinx_design/my_project --thread-id my-design-001"
|
||
"$PY" "$RUN" $W record --key simulink_x#1 \
|
||
--result '{"gate_passed": true, "artifacts": ["demo-x.slx"], "notes": "EVM -38dB"}'
|
||
"$PY" "$RUN" $W resume
|
||
```
|
||
|
||
Loop until `WORKFLOW FINISHED — status: completed`. When several keys are pending,
|
||
record **each**, then `resume` once. `record --all-ok` fast-forwards pending *subagent*
|
||
keys with a default success — rehearsal only, and it refuses review keys.
|
||
|
||
### 4. What you get handed
|
||
|
||
| key | what you actually do | which skill to use |
|
||
|---|---|---|
|
||
| `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 |
|
||
| `partition#n` | pure-sim / RTL / SoC split, must not degrade sim-golden | same |
|
||
| `interfaces#n` | RTL↔SoC interface doc — register-config or data-stream only | write the doc under `work_dir` |
|
||
| `review:functional_partition#n` | **ask the user** to sign off on the split + interfaces | — (relay to user) |
|
||
| `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` |
|
||
| `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>/<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`, …
|
||
|
||
### 5. Answer JSON
|
||
|
||
`"$PY" "$RUN" schema` prints both templates. All fields optional except `approved`.
|
||
|
||
```json
|
||
subagent: {"success": true, "gate_passed": true, "artifacts": [], "notes": "",
|
||
"timing_met": true, "hil_passed": true, "needs_code_change": false,
|
||
"change_scope": "none", "affected_modules": [], "metadata": {}}
|
||
review: {"approved": true, "notes": "", "metadata": {}}
|
||
```
|
||
|
||
| field | who reads it |
|
||
|---|---|
|
||
| `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 |
|
||
|
||
Routing on `change_scope`:
|
||
|
||
- `simulink_module` → **`modify_e:*`** (edit the `-E.slx`) → **`sync_sim:*`** (carry it
|
||
into `-x`/`-q`, re-verify) → only the affected modules re-run `native → ip_replace` →
|
||
Vivado → HIL.
|
||
- `non_simulink_rtl` / `soc` / `hardware` → `timing_rework` (from Vivado) or `hil_rework`
|
||
(from HIL) → Vivado.
|
||
- The driver **rejects** `needs_code_change: true` with `change_scope: "none"`.
|
||
|
||
A failing `ip_replace:<M>` (cycle-true mismatch) enters the same chain on its own:
|
||
`evaluate_modules → modify_e:module → sync_sim:module → native:<M>`. A failing
|
||
`native:<M>` does **not** — the `-E` isn't sound yet, so it just re-runs nativization.
|
||
|
||
### 6. Parallel batches (verified)
|
||
|
||
| stage | keys handed over at once |
|
||
|---|---|
|
||
| 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.
|
||
|
||
Work batched tasks in any order; record all, then `resume`.
|
||
|
||
## Rehearsal without touching hardware
|
||
|
||
```bash
|
||
"$PY" "$RUN" dry-run --input examples/happy_path.json
|
||
```
|
||
|
||
Runs the whole graph against `ScriptedBackend` + auto-approving `ScriptedReviewGate` —
|
||
no handoffs, no side effects. Verified: `status: completed`, 13 subagent calls listed
|
||
with their skill and write permission, plus both review checkpoints. To rehearse a
|
||
branch, add `outcomes` (task → list of results) and/or `reviews` (checkpoint → list of
|
||
decisions) to the input JSON; `start`/`resume` strip both.
|
||
|
||
```json
|
||
"outcomes": {"hil:fpga-imx6-flow": [
|
||
{"success": true, "hil_passed": false, "needs_code_change": true,
|
||
"change_scope": "simulink_module", "affected_modules": ["A"]},
|
||
{"success": true, "hil_passed": true}]},
|
||
"reviews": {"sim_performance": [{"approved": false, "notes": "EVM 未达标"}]}
|
||
```
|
||
|
||
## Other commands
|
||
|
||
```bash
|
||
"$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 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).
|
||
|
||
## Gotchas
|
||
|
||
- **`resume`/`record`/`status` need `--work-dir`** (or a `cd` into the design project).
|
||
Only `start` can infer it, from the input JSON. Without it the driver looks in the cwd
|
||
and reports `no input.json in …`.
|
||
- **The driver refuses a `work_dir` inside the sim-rtl-flow repo.** Run state belongs to
|
||
the design project (PR01-2). `examples/happy_path.json` therefore carries a placeholder
|
||
`E:/Xilinx_design/your_project` — fine for `dry-run`, but replace it before `start`.
|
||
- **`mermaid` output is still partly misleading.** The forward and rework edges now
|
||
render (path maps were added), but `plan_modules`' `Send` fanout is a dynamic edge
|
||
Mermaid can't show. Use `Notes/Result00.md` for the authoritative picture.
|
||
- **`--max-attempts` must precede the subcommand** and stay the *same value* across
|
||
`start` and every `resume` — it is runtime context, not persisted state.
|
||
- **The skill names the graph hands you are real and exact** — `simulink_rtl_dev`
|
||
(**underscores**, not dashes), `rtl-ip-replace`, `simulink-rtl-redev`. All three are
|
||
installed. Invoke the named one rather than improvising with MATLAB MCP directly: they
|
||
carry the project's own gates (`audit_hdl_blocks.m`, cycle-true compare, `check_sync.py`).
|
||
- **`sync_sim:*` is not optional bookkeeping.** It is the step that keeps the `-x`/`-q`
|
||
model telling the truth about the `-E` you just changed. Recording it as passed without
|
||
actually re-simulating means every later gate — Vivado, HIL, sim-golden — is validating
|
||
a model that no longer matches the RTL.
|
||
- **`simulink_x`'s first artifact silently becomes sim-golden.** Record `artifacts: []`
|
||
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
|
||
thread instead.
|
||
- **`work_dir` is passed straight through to every subagent request.** Forward slashes
|
||
are fine on Windows; a relative `"."` resolves against wherever the driver was
|
||
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* `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.
|
||
- **A reused module produces no `native:`/`ip_replace:` keys at all.** If you expected
|
||
three modules and only got one task, check the `modules_reused_from_history` event —
|
||
that is the graph honouring your own `history_review` verdict, not a lost module.
|
||
- **`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
|
||
doc did not get written — fix that and re-run the summary, don't ignore it.
|
||
|
||
## Troubleshooting
|
||
|
||
| symptom | cause / fix |
|
||
|---|---|
|
||
| `sim_rtl_flow package not found under …\src` | `SIM_RTL_FLOW_ROOT` in `~\skills_env.md` is wrong/missing; fix it or pass `--project <检出目录>` |
|
||
| `sim-rtl-flow 工作流仓库路径未配置` | add `SIM_RTL_FLOW_ROOT = <检出目录>` to `~\skills_env.md` |
|
||
| `no input.json in …\.sim_rtl_flow\runs\<id>` | missing/incorrect `--work-dir`, or `start` was never run |
|
||
| `nothing pending — run 'start' or 'resume' first` | the previous invoke completed or crashed; check `status` |
|
||
| `--key X is not pending; pending = [...]` | typo, or already recorded (see the re-record gotcha) |
|
||
| `--all-ok 不能用于人工审核断点` | working as intended — the user must decide; use `--approve` / `--reject` |
|
||
| `驳回必须写明理由:请提供 notes` | add `--notes "<user's reason>"` |
|
||
| `unknown review fields: ['gate_passed']` | you sent a subagent result to a `review:*` key; use `--approve`/`--reject` |
|
||
| `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` |
|