RF/发射类验收不能只靠数字侧证据。analyze 节点新增一节,规定用已注册的 频谱仪 MCP(N9320B / DSA815)取证:需求点名哪台就用哪台,未点名默认 DSA815;只读抓图优先,改了设置要还原;artifact 路径、峰值频率/功率与 扫描设置一并写进 analysis_log。频谱仪连不上时报缺证,不得仅凭数字证据 判定 RF 需求通过。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tMFjKGgxdmxJ28dyXdx1u
225 lines
13 KiB
Markdown
225 lines
13 KiB
Markdown
---
|
||
name: zynq-imx6-flow
|
||
description: >
|
||
Run the zynq-imx6-flow LangGraph workflow at E:\LangGraph_Design\zynq-imx6-flow —
|
||
the closed-loop xmod (Zynq PS+PL + i.MX6) hardware-in-the-loop design/debug pipeline:
|
||
cold power-cycle, connect JTAG, program bitstream/ELF or burn flash, restart the
|
||
ps/imx6 app, analyze (ILA + PS/imx6 serial consoles), judge against design
|
||
requirements, and iterate by modifying RTL / imx6 app / PS baremetal code. It drives
|
||
the graph in-process (no langgraph dev) and hands the 6 interrupts (analyze /
|
||
meets_requirement / propose / modify_fpga / modify_imx6 / modify_ps) back to this
|
||
Claude session to execute. Also supports zynq-only boards (no i.MX6) and PS
|
||
baremetal/petalinux modes.
|
||
|
||
ALWAYS use this skill whenever the user wants to run, start, drive, resume, or
|
||
continue the zynq-imx6-flow workflow / xmod Zynq+imx6 design loop, or says things
|
||
like "启动 zynq-imx6-flow", "跑 zynq 设计流程", "run the zynq FPGA flow", "执行 xmod
|
||
设计任务", "继续 zynq 工作流", "resume the zynq design loop", even if they don't name
|
||
the project path. Before driving, this skill confirms the langgraph conda env from
|
||
/langgraph-vivado-install is in place.
|
||
---
|
||
|
||
## What this skill does
|
||
|
||
Drives the **zynq-imx6-flow** graph (28 nodes / 39 flow-edges, 1:1 with
|
||
`Notes/zynq-imx6-flow.excalidraw`, plan `Notes/Task00.md`). The deterministic
|
||
hardware/check nodes (power, JTAG, program, restart) run in-process; the **6 interrupt
|
||
nodes** pause and hand control to **this Claude session**, which does the real work
|
||
(analysis, judgment, RTL/app/PS edits) and resumes the same thread. Persistence is a
|
||
SqliteSaver file, so `start`/`resume` survive across separate process calls.
|
||
|
||
| Item | Value |
|
||
|---|---|
|
||
| Project root | 见 `~\skills_env.md` 的 `ZYNQ_IMX6_FLOW_ROOT`(driver 自动读取,也可 `--project-dir` 覆盖) |
|
||
| Conda env | `langgraph` (path 见 `~\skills_env.md` 的 `CONDA_LANGGRAPH_PYTHON`;装环境用 `/langgraph-vivado-install`) |
|
||
| Graph | `app.main_graph:build_graph` (in-process; no langgraph dev) |
|
||
| Driver | `%USERPROFILE%\.claude\skills\zynq-imx6-flow\scripts\run_zynq_flow.py` |
|
||
| Checkpoint | `<project>\runs\skill_checkpoint.sqlite` (keyed by `--thread-id`) |
|
||
|
||
**Drive the driver with the Bash tool (Git Bash), not PowerShell.** Windows PowerShell
|
||
5.1 strips the double-quotes out of `--resume-json` payloads (see Gotchas), corrupting
|
||
the JSON. Git Bash single-quoting passes it intact. All commands below are Bash and were
|
||
run this way to verify them.
|
||
|
||
```bash
|
||
PY="/c/Users/Administrator/miniconda3/envs/langgraph/python.exe"
|
||
RUN="/c/Users/Administrator/.claude/skills/zynq-imx6-flow/scripts/run_zynq_flow.py"
|
||
export PYTHONIOENCODING=utf-8
|
||
```
|
||
|
||
## ⚠️ Hardware timing rule (power / USB-Blast)
|
||
|
||
**Before every power change to the board, USB-Blast must already be disconnected; only
|
||
reconnect JTAG after power is up.** Never power the board up/down while USB-Blast is
|
||
connected (protects the JTAG adapter and board). The graph topology already enforces
|
||
this: `power_off → check_power_off →(是) blast_off_a → check_blast_off →(是) power_on →
|
||
check_power →(是) blast_on → check_hw`. After `flash_burn` the graph returns to
|
||
`power_off` for a full cold-restart before re-analyzing. Serial port numbers are **never
|
||
hardcoded** — they come from `~/zynq_imx6_flow.conf` (see "Mock vs real").
|
||
|
||
## Step 0 — Confirm the environment (REQUIRED before driving)
|
||
|
||
The workflow runs in the `langgraph` conda env created by **`/langgraph-vivado-install`**.
|
||
Verify it first so a missing env is a clear message, not a cryptic mid-run import error:
|
||
|
||
```bash
|
||
"$PY" -c "from langgraph.checkpoint.sqlite import SqliteSaver; from langgraph.types import Command; import langgraph; print('[OK] langgraph')"
|
||
```
|
||
|
||
- **`[OK]`** → proceed.
|
||
- **import fails / no python** → tell the user to run **`/langgraph-vivado-install`**
|
||
first (it creates the `langgraph` env with `langgraph` + `langgraph-checkpoint-sqlite`).
|
||
Do not proceed until it succeeds.
|
||
|
||
Optional sanity check — the project's own acceptance suite (all mock, ~2 s):
|
||
|
||
```bash
|
||
cd /e/LangGraph_Design/zynq-imx6-flow && "$PY" smoke_test.py # prints [PASS]
|
||
```
|
||
|
||
## Step 1 — Start the workflow
|
||
|
||
Pick the board + first-pass program mode (defaults shown), then start on a fresh
|
||
`--thread-id`:
|
||
|
||
```bash
|
||
"$PY" "$RUN" start --thread-id zynq-<short-task-name> \
|
||
--board-type xmod --ps-mode baremetal --program-mode bitstream --bit-path mock.bit
|
||
```
|
||
|
||
- `--board-type` `xmod` (Zynq+i.MX6, default) | `zynq_only` (no i.MX6 — imx6 branch is skipped).
|
||
- `--ps-mode` `baremetal` (default) | `petalinux`.
|
||
- `--program-mode` `bitstream` (default, downloads bit → restart → analyze) |
|
||
`debug_flash` (image already fused in flash → straight to analyze, no download).
|
||
- `--bit-path` / `--elf-path` / `--flash-path` optional artifact paths.
|
||
- Add `--real` only with hardware wired + board connected (default is MOCK — see below).
|
||
|
||
The driver prints **one JSON line** and exits:
|
||
|
||
| Exit | `status` | Meaning |
|
||
|---|---|---|
|
||
| 0 | `done` | Workflow finished (符合要求 / iteration cap) → Step 3 |
|
||
| 1 | `error` | Read `error`, diagnose, report |
|
||
| 2 | `interrupt` | An interrupt fired → read `interrupt.action` (and `pending_count`) → Step 2 |
|
||
|
||
## Step 2 — Handle the interrupt(s), then resume
|
||
|
||
On exit 2, `interrupt.action` (or the `pending_interrupts` list) tells you what to do.
|
||
Perform it, then resume the **same `--thread-id`**. Repeat until exit 0.
|
||
|
||
| `action` | excalidraw node | What you (the session) do | Resume JSON |
|
||
|---|---|---|---|
|
||
| `analyze` | 通过ila/串口分析 | Grab ILA + listed consoles; for RF/TX requirements also capture the requested analyzer spectrum; summarize all evidence | `{"analysis_log":"<摘要/路径>"}` |
|
||
| `meets_requirement` | 符合设计要求? | Judge analysis vs the design requirement | `{"meets": true}` or `{"meets": false}` |
|
||
| `propose` | 分析原因,提方案 | Diagnose; **review the plan with `/langgraph-plan-review`**; decide what to change + next program mode | `{"reason":"...","need_fpga":bool,"need_imx6":bool,"need_ps":bool,"program_mode":"bitstream"或"flash"}` |
|
||
| `modify_fpga` | 改 RTL→综合→bit/flash | Edit RTL, run Vivado synth/impl, produce bit + flash | `{"bit_path":"...","flash_path":"..."}` |
|
||
| `modify_imx6` | VM 改 app→交叉编译 | In the Ubuntu VM edit imx6 app + cross-compile | `{"app_path":"..."}` |
|
||
| `modify_ps` | sdk 改 PS 裸机→ELF | In Xilinx SDK edit PS baremetal code + build (petalinux: rebuild app/image) | `{"elf_path":"..."}` |
|
||
|
||
**Single interrupt** — resume with the payload directly:
|
||
|
||
```bash
|
||
"$PY" "$RUN" resume --thread-id zynq-<name> --resume-json '{"meets": false}'
|
||
```
|
||
|
||
**Three-way parallel fan-out** — after a `propose` that sets `need_fpga`/`need_imx6`/
|
||
`need_ps` true, `modify_fpga` + `modify_imx6` + `modify_ps` fire **in one superstep**
|
||
(`pending_count: 3`). Resume them with **one map keyed by `action`** (subset allowed;
|
||
unresumed ones stay pending and the deferred `round_done` still waits for every branch):
|
||
|
||
```bash
|
||
"$PY" "$RUN" resume --thread-id zynq-<name> --resume-json \
|
||
'{"modify_fpga":{"bit_path":"new.bit","flash_path":"new.bin"},"modify_imx6":{"app_path":"new-app"},"modify_ps":{"elf_path":"new.elf"}}'
|
||
```
|
||
|
||
Notes:
|
||
- Always reuse the **same `--thread-id`** from `start` — that's how the sqlite checkpoint
|
||
reattaches (different tasks → different thread ids → isolated state).
|
||
- `propose` fans out to `decide_fpga`/`decide_imx6`/`decide_ps`; all-false goes straight
|
||
to `round_done` → re-program. `board_type=zynq_only` forces `need_imx6=false`.
|
||
- `program_mode` from `propose` chooses the re-program path after `round_done`:
|
||
`bitstream` = download bit; `flash` = burn flash then full cold power/connect
|
||
re-sequence (`flash_burn → power_off`). This is the big closed loop.
|
||
- `"$PY" "$RUN" status --thread-id zynq-<name>` re-prints the current state without advancing.
|
||
|
||
### Spectrum-analyzer evidence during `analyze`
|
||
|
||
For RF/transmitter acceptance, use a registered analyzer MCP in addition to ILA and
|
||
console logs:
|
||
|
||
- N9320B (tool prefix `mcp__mcp_n9320b__`): `get_idn` → `get_settings` →
|
||
`get_spectrum` with `mark_peak=true` and a stable `csv_path`.
|
||
- DSA815 (tool prefix `mcp__mcp_dsa815__`): `dsa_get_identity` → `dsa_get_status` →
|
||
`dsa_capture_plot`.
|
||
|
||
Use the analyzer named by the requirement. If none is named, prefer DSA815; use both only
|
||
for an explicit cross-check or independent confirmation. For DSA815 specifically:
|
||
|
||
1. `dsa_get_identity` → `dsa_get_status` verifies serial `DSA8A194401321` and records
|
||
the current sweep, unit, RBW and VBW.
|
||
2. `dsa_capture_plot` captures the current Trace and returns PNG/CSV evidence; call
|
||
`dsa_capture_spectrum` only when all 601 points are needed for numerical analysis.
|
||
3. Put artifact paths, peak frequency/power and sweep settings in `analysis_log`.
|
||
4. Prefer read-only capture. If a required mask/channel needs different settings,
|
||
record the original status and restore it after capture. Do not enable raw SCPI write.
|
||
|
||
Always record the selected analyzer identity with the RF artifacts. If the analyzer cannot
|
||
be verified or reached, report the missing RF evidence and do not mark an RF requirement
|
||
as passed from digital evidence alone.
|
||
|
||
## Step 3 — Finish
|
||
|
||
On exit 0 (`status: done`), report iterations completed and whether the design met
|
||
requirements (`meets_requirement`) or hit the iteration cap. Offer to inspect
|
||
`<project>\runs\` artifacts or the per-step `history`.
|
||
|
||
To abandon a paused run and start clean, delete `<project>\runs\skill_checkpoint.sqlite`
|
||
or use a fresh `--thread-id`.
|
||
|
||
## Mock vs real
|
||
|
||
- **MOCK (default)** — sets `ZYNQ_IMX6_MOCK=1`; hardware/check nodes return success
|
||
immediately. Dry-runs the *design-loop logic* and all 6 interrupts with no board. Ideal
|
||
for demonstrating/validating the flow. Every command in this doc was verified in mock.
|
||
- **Real (`--real`)** — sets `ZYNQ_IMX6_MOCK=0` and drives actual hardware. The `tools_hw`
|
||
paths are wired (Modbus power relay `0x05`, USB-Blast `0xA0` frames over pyserial). Real
|
||
mode additionally requires:
|
||
1. **pyserial** in the env (`"$PY" -m pip install pyserial`) — not installed by default;
|
||
without it `--real` returns `{"status":"error","error":"Real mode requires pyserial"}`.
|
||
2. **`~/zynq_imx6_flow.conf`** with 4 sections `[power] [usb_blast] [imx6_console]
|
||
[ps_console]` (template: project's `zynq_imx6_flow.example.conf`). Relay ports
|
||
(`power`/`usb_blast`) may fall back to `~/vivado_mcp_usb.conf`. `zynq_only` boards
|
||
don't need `[imx6_console]`. COM numbers differ per machine — find them in Device
|
||
Manager → Ports (COM & LPT).
|
||
3. A connected board + Vivado for the JTAG program/flash nodes.
|
||
|
||
## Gotchas
|
||
|
||
- **PowerShell 5.1 corrupts `--resume-json`.** `& $py $RUN resume --resume-json '{"meets": false}'`
|
||
arrives at Python as `{meets: false}` — PS strips the double-quotes (and `\"`-escaping
|
||
splits it on spaces instead). This is the known PSNativeCommandArgumentPassing bug,
|
||
unfixed in 5.1. **Use the Bash tool.** Verified: the same JSON through Git Bash arrives
|
||
intact (`analysis_log` values with spaces round-trip correctly).
|
||
- **`round_done` is a deferred node** (`defer=True`). With the three-way fan-out it runs
|
||
**exactly once** after all active modify/deploy branches finish (verified: iteration
|
||
advances 0→1 once even with all three branches on). Don't expect it after only the
|
||
first branch.
|
||
- **`propose` must return `program_mode` ∈ {bitstream, flash}.** Any other value raises
|
||
`propose returned invalid program_mode`. (The `start` `--program-mode` has a third
|
||
option `debug_flash` for the first pass only; `propose` does not.)
|
||
- **`langgraph.__version__` doesn't exist** on 1.2.9 — check the version via
|
||
`importlib.metadata.version("langgraph")` if needed (smoke_test asserts `>= 0.2.39`).
|
||
|
||
## Troubleshooting
|
||
|
||
| Symptom | Fix |
|
||
|---|---|
|
||
| `--resume-json` value shows up without quotes / split on spaces | You used PowerShell. Drive via the Bash tool (single-quoted JSON). |
|
||
| `UnicodeEncodeError: 'gbk' ...` | Don't use bare `conda run`; use the env `python.exe` directly (the driver reconfigures stdout to UTF-8). |
|
||
| `{"status":"error","error":"Real mode requires pyserial"}` | `--real` needs pyserial: `"$PY" -m pip install pyserial`. Or drop `--real` for a mock run. |
|
||
| `SerialConfigError: Missing serial sections [...]` | Create `~/zynq_imx6_flow.conf` from `zynq_imx6_flow.example.conf` (relay ports may come from `~/vivado_mcp_usb.conf`). |
|
||
| `import failed ... Run /langgraph-vivado-install` | The `langgraph` env is missing/incomplete → run `/langgraph-vivado-install`. |
|
||
| `propose returned invalid program_mode` | The `propose` resume used something other than `bitstream`/`flash`. |
|
||
| `resume` seems to start over | You passed a different `--thread-id` than `start`. Use the same one. |
|
||
| Want to see the graph visually | Optional: `langgraph dev` via the project's `langgraph.json` opens Studio — not required to run. |
|