更新了skill
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
---
|
||||
name: fpga-imx6-flow
|
||||
description: >
|
||||
Run the fpga-imx6-flow LangGraph workflow at E:\LangGraph_Design\FPGA-imx6-flow —
|
||||
the closed-loop SMOD (FPGA + i.MX6) hardware-in-the-loop design/debug pipeline:
|
||||
power on the board, connect JTAG, program bitstream/flash, restart the imx6 app,
|
||||
analyze (ILA + COM6 serial), judge against design requirements, and iterate by
|
||||
modifying RTL/imx6 code. It drives the graph in-process (no langgraph dev) and
|
||||
hands the 5 interrupts (analyze / meets-requirement / propose / modify-FPGA /
|
||||
modify-imx6) back to this Claude session to execute.
|
||||
|
||||
ALWAYS use this skill whenever the user wants to run, start, drive, resume, or
|
||||
continue the fpga-imx6-flow workflow / SMOD FPGA+imx6 design loop, or says things
|
||||
like "启动 fpga-imx6-flow", "跑 imx6 设计流程", "run the imx6 FPGA flow", "执行
|
||||
SMOD 设计任务", "继续 imx6 工作流", "resume the imx6 design loop", even if they
|
||||
don't name the project path. Before driving the workflow this skill first confirms
|
||||
the langgraph conda environment from /langgraph-vivado-install is in place.
|
||||
---
|
||||
|
||||
## What this skill does
|
||||
|
||||
Drives the **fpga-imx6-flow** graph (24 nodes / 34 flow-edges, 1:1 with
|
||||
`Notes/fpga-imx6-flow.excalidraw`). The graph's deterministic hardware/check nodes
|
||||
(power, JTAG, program, restart) run in-process; its **5 interrupt nodes** pause and
|
||||
hand control to **this Claude session**, which performs the real work (analysis,
|
||||
judgment, RTL/app edits) and resumes the same thread. Persistence is a SqliteSaver
|
||||
file, so `start`/`resume` survive across separate process calls.
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Project root | `E:\LangGraph_Design\FPGA-imx6-flow` |
|
||||
| Conda env | `langgraph` (`C:\Users\bowei\.conda\envs\langgraph`) |
|
||||
| Graph | `app.main_graph:build_graph` (in-process; no langgraph dev) |
|
||||
| Driver | `C:\Users\bowei\.claude\skills\fpga-imx6-flow\scripts\run_imx6_flow.py` |
|
||||
| Checkpoint | `<project>\runs\skill_checkpoint.sqlite` (keyed by `--thread-id`) |
|
||||
| Design source | `Notes/fpga-imx6-flow.excalidraw`, plan `Notes/Task00.md` |
|
||||
|
||||
## ⚠️ 硬件时序铁律(上电 / USB-Blast / 烧写,务必遵守)
|
||||
|
||||
**每次给 SMOD 上电之前,USB-Blast 必须先断开;上电完成之后再重新连接。** 绝不在
|
||||
USB-Blast 处于连接状态时给板卡上电或断电(保护 JTAG 适配器与板卡)。
|
||||
|
||||
标准时序(图拓扑已按此实现,手动操作或写 `tools_*` / 真机脚本时也必须遵守):
|
||||
|
||||
```
|
||||
断电(COM8 power_off) → 确认已断电 → 断开USB-Blast(COM4 blast_off) → 确认已断开
|
||||
→ 上电(COM8 power_on) → 确认已上电 → 连接USB-Blast(COM4 blast_on) → 连接JTAG/烧写/抓ILA
|
||||
```
|
||||
|
||||
**烧写 flash 的完整闭环**(固化后必须断电重启从 flash 加载再验证):
|
||||
|
||||
```
|
||||
断电 → 断开USB-Blast → 上电 → 连接USB-Blast → 用 /fpga_flash 烧写flash
|
||||
→ 断电 → 断开USB-Blast → 上电 → 连接USB-Blast → 通过ILA验证flash内容正确
|
||||
```
|
||||
|
||||
- 在 LangGraph 图里:`power_off → check_power_off →(是) blast_off_a → check_blast_off
|
||||
→(是) power_on → check_power →(是) blast_on → check_hw`。`flash_burn` 烧写后回到
|
||||
`power_off` 做整机断电重启,再次走完上述上电时序后才 `analyze`(抓ILA验证)。
|
||||
- **强化确认**:`power_on` 节点在上电前调用 `_ensure_blast_off_before_power()` 兜底确认
|
||||
USB-Blast 已断开;若发现仍连接会强制断开并在 `history` 标记 `rule_violation=True`。
|
||||
- vivado-mcp 工具:`power_on`/`power_off`(COM8 继电器)、`USB_Blast_on`/`USB_Blast_off`(COM4)。
|
||||
|
||||
> **Run via the env python directly** (not bare `conda run`): on this Windows GBK
|
||||
> console, `conda run` re-prints the child's UTF-8 stdout through a GBK codec and
|
||||
> **crashes on the Chinese interrupt payloads**. Use the env `python.exe`, or
|
||||
> `conda run --no-capture-output`. The driver also re-configures its stdout to UTF-8.
|
||||
|
||||
```
|
||||
PY = C:\Users\bowei\.conda\envs\langgraph\python.exe
|
||||
RUN = C:\Users\bowei\.claude\skills\fpga-imx6-flow\scripts\run_imx6_flow.py
|
||||
```
|
||||
|
||||
## Step 0 — Confirm the environment (REQUIRED before driving)
|
||||
|
||||
The workflow runs in the `langgraph` conda env created by **`/langgraph-vivado-install`**.
|
||||
Verify it before starting, so a missing env surfaces as a clear instruction rather
|
||||
than a cryptic import error mid-run:
|
||||
|
||||
```powershell
|
||||
$py = "C:\Users\bowei\.conda\envs\langgraph\python.exe"
|
||||
if (-not (Test-Path $py)) { Write-Host "[MISSING] langgraph env" } else {
|
||||
& $py -c "import langgraph; from langgraph.checkpoint.sqlite import SqliteSaver; from langgraph.types import Command; print('[OK] langgraph + checkpoint-sqlite')"
|
||||
}
|
||||
```
|
||||
|
||||
- **If `[OK]`** → proceed to Step 1.
|
||||
- **If missing / import fails** → tell the user the env isn't ready and to run
|
||||
**`/langgraph-vivado-install`** first (it creates the `langgraph` env with
|
||||
`langgraph` + `langgraph-checkpoint-sqlite` and the Vivado link). Do not proceed
|
||||
until it succeeds.
|
||||
|
||||
**For a real-hardware run** (`--real`), additionally confirm: `env_config.json`'s
|
||||
`VIVADO_BIN_PATH` is valid; the vivado-mcp `power_on`/`USB_Blast_on` tools are
|
||||
available; COM4/COM6/COM8 are present (`[System.IO.Ports.SerialPort]::GetPortNames()`).
|
||||
**Default runs are MOCK** (dry-run of the design loop with no hardware), because the
|
||||
workflow's `tools_*` real-hardware paths are still TODO — see "Mock vs real" below.
|
||||
|
||||
## Step 1 — Start the workflow
|
||||
|
||||
Ask the user (or infer) the first-pass program mode (`bitstream` default, or `flash`)
|
||||
and any `--bit-path`/`--flash-path`. Then start:
|
||||
|
||||
```powershell
|
||||
& $py $RUN start --project-dir "E:\LangGraph_Design\FPGA-imx6-flow" `
|
||||
--thread-id "imx6-<short-task-name>" --program-mode bitstream
|
||||
# add --real only when hardware tools are wired and the board is connected
|
||||
```
|
||||
|
||||
The driver prints **one JSON line** and exits:
|
||||
|
||||
| Exit | `status` | Meaning |
|
||||
|---|---|---|
|
||||
| 0 | `done` | Workflow finished (符合设计要求 / 迭代到上限) → Step 3 |
|
||||
| 1 | `error` | Read `error`, diagnose, report to user |
|
||||
| 2 | `interrupt` | An interrupt fired → read `interrupt.action` → Step 2 |
|
||||
|
||||
## Step 2 — Handle the interrupt, then resume
|
||||
|
||||
On exit 2, the JSON's `interrupt.action` tells you what the session must do. Perform
|
||||
it, then resume the **same `--thread-id`** with a JSON payload. Repeat until exit 0.
|
||||
|
||||
| `action` | excalidraw node | What you (the session) do | Resume JSON |
|
||||
|---|---|---|---|
|
||||
| `analyze` | 通过ila/串口分析 | Grab COM6 serial log + pull ILA waveform (vivado-mcp); summarize | `{"analysis_log":"<摘要/路径>"}` |
|
||||
| `meets_requirement` | 符合设计要求? | Judge the analysis against the design requirement | `{"meets": true}` or `{"meets": false}` |
|
||||
| `propose` | 分析原因,提出修改方案 | Diagnose root cause; decide what to change + next program mode | `{"reason":"...","need_fpga": true/false,"need_imx6": true/false,"program_mode":"bitstream"或"flash"}` |
|
||||
| `modify_fpga` | 改 RTL→综合→出 bit/flash | Edit RTL, run Vivado synth/impl, produce bit + flash files | `{"bit_path":"...","flash_path":"...","program_mode":"flash"}` |
|
||||
| `modify_imx6` | VM 改 app→交叉编译 | In the Ubuntu VM (`bowei@192.168.223.128`) edit app + cross-compile | `{"app_path":"..."}` |
|
||||
|
||||
Resume command:
|
||||
|
||||
```powershell
|
||||
& $py $RUN resume --project-dir "E:\LangGraph_Design\FPGA-imx6-flow" `
|
||||
--thread-id "imx6-<short-task-name>" --resume-json '{"meets": false}'
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Always pass the **same `--thread-id`** used at `start` — that's how the sqlite
|
||||
checkpoint reattaches to the run (concurrency-safe: different tasks → different
|
||||
thread ids → isolated state).
|
||||
- `propose` fans out to BOTH `decide_fpga` and `decide_imx6`; with `need_fpga:false`
|
||||
and `need_imx6:false` the loop goes straight to `round_done` → re-program. Setting
|
||||
either true fires the corresponding `modify_*` interrupt next.
|
||||
- After `round_done` the graph re-asks the program-mode decision; `program_mode` you
|
||||
set in `propose`/`modify_fpga` chooses bitstream (download bit) vs flash (re-burn +
|
||||
full power/connect re-sequence). This is the big closed loop.
|
||||
|
||||
## Step 3 — Finish
|
||||
|
||||
On exit 0 (`status: done`), report to the user: iterations completed and that the
|
||||
design met requirements (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)** — `FPGA_IMX6_MOCK=1`; hardware/check nodes return success
|
||||
immediately. Use this to dry-run the *design-loop logic* and the 5 interrupts with
|
||||
no board attached. Great for demonstrating/validating the flow.
|
||||
- **Real (`--real`)** — drives actual hardware. Requires the project's
|
||||
`app/tools_hw.py` / `tools_vivado.py` / `tools_imx6.py` real paths to be implemented
|
||||
(they currently raise `NotImplementedError` as TODO — they will reuse the proven
|
||||
vivado-mcp serial/Vivado logic). Until then, `--real` fails at the first hardware
|
||||
node; tell the user to wire the tools first (see `Notes/Task00.md` §7).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
|---|---|
|
||||
| `UnicodeEncodeError: 'gbk' ...` | Don't use bare `conda run`; use the env `python.exe` or `conda run --no-capture-output`. |
|
||||
| `import failed ... Run /langgraph-vivado-install` | The `langgraph` env is missing/incomplete → run `/langgraph-vivado-install`. |
|
||||
| Exit 1 `NotImplementedError ... tools_*` | A `--real` run hit an unwired hardware tool. Run without `--real` (mock), or implement `tools_*`. |
|
||||
| `resume` seems to start over | You passed a different `--thread-id` than `start`. Use the same one. |
|
||||
| Need to see the graph visually | Optional: `langgraph dev` via the project's `langgraph.json` opens Studio — not required to run. |
|
||||
@@ -0,0 +1,123 @@
|
||||
"""In-process driver for the fpga-imx6-flow LangGraph workflow.
|
||||
|
||||
Builds the compiled graph from the project's app package and drives it with a
|
||||
persistent SqliteSaver — no `langgraph dev` server. Mirrors the fpga-flow helper
|
||||
convention: exit code 0 = done, 1 = error, 2 = interrupt fired.
|
||||
|
||||
Commands:
|
||||
start --project-dir <dir> [--program-mode bitstream|flash] [--bit-path ..]
|
||||
[--flash-path ..] [--thread-id ..] [--real] [--max-iterations N]
|
||||
resume --project-dir <dir> --resume-json '<json>' [--thread-id ..] [--real]
|
||||
status --project-dir <dir> [--thread-id ..]
|
||||
|
||||
Output: one JSON line on stdout describing status + the pending interrupt (if any).
|
||||
Run via the env python directly or `conda run --no-capture-output` to avoid the
|
||||
Windows GBK console re-encode crash on Chinese interrupt payloads.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8") # emit UTF-8 even on a GBK console
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
DEFAULT_PROJECT = r"E:\LangGraph_Design\FPGA-imx6-flow"
|
||||
|
||||
|
||||
def _emit(obj: dict, code: int) -> None:
|
||||
print(json.dumps(obj, ensure_ascii=False))
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
def _pending_interrupt(snap):
|
||||
for task in snap.tasks:
|
||||
if task.interrupts:
|
||||
return task.interrupts[0].value
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="Drive the fpga-imx6-flow graph in-process.")
|
||||
ap.add_argument("command", choices=["start", "resume", "status"])
|
||||
ap.add_argument("--project-dir", default=DEFAULT_PROJECT)
|
||||
ap.add_argument("--thread-id", default="imx6-skill-1")
|
||||
ap.add_argument("--resume-json", default="")
|
||||
ap.add_argument("--program-mode", default="bitstream", choices=["bitstream", "flash"])
|
||||
ap.add_argument("--bit-path", default="")
|
||||
ap.add_argument("--flash-path", default="")
|
||||
ap.add_argument("--real", action="store_true",
|
||||
help="drive real hardware (workflow tools_* must be wired; default is MOCK dry-run)")
|
||||
ap.add_argument("--max-iterations", type=int, default=8)
|
||||
ap.add_argument("--max-hw-retry", type=int, default=3)
|
||||
args = ap.parse_args()
|
||||
|
||||
project = Path(args.project_dir)
|
||||
if not (project / "app" / "main_graph.py").exists():
|
||||
_emit({"status": "error",
|
||||
"error": f"app/main_graph.py not found under {project}; check --project-dir"}, 1)
|
||||
|
||||
# MOCK by default so the design loop can be dry-run with no hardware attached.
|
||||
os.environ["FPGA_IMX6_MOCK"] = "0" if args.real else "1"
|
||||
sys.path.insert(0, str(project))
|
||||
|
||||
try:
|
||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
||||
from langgraph.types import Command
|
||||
from app.main_graph import build_graph
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_emit({"status": "error",
|
||||
"error": f"import failed: {exc}. Run /langgraph-vivado-install to set up the env."}, 1)
|
||||
|
||||
runs = project / "runs"
|
||||
runs.mkdir(exist_ok=True)
|
||||
db = str(runs / "skill_checkpoint.sqlite")
|
||||
cfg = {"configurable": {"thread_id": args.thread_id}}
|
||||
|
||||
with SqliteSaver.from_conn_string(db) as cp:
|
||||
graph = build_graph(checkpointer=cp)
|
||||
try:
|
||||
if args.command == "start":
|
||||
init = {
|
||||
"iteration": 0,
|
||||
"program_mode": args.program_mode,
|
||||
"bit_path": args.bit_path,
|
||||
"flash_path": args.flash_path,
|
||||
"max_hw_retry": args.max_hw_retry,
|
||||
"max_iterations": args.max_iterations,
|
||||
"history": [],
|
||||
}
|
||||
graph.invoke(init, cfg)
|
||||
elif args.command == "resume":
|
||||
payload = json.loads(args.resume_json) if args.resume_json else {}
|
||||
graph.invoke(Command(resume=payload), cfg)
|
||||
# status: no invoke, just snapshot
|
||||
snap = graph.get_state(cfg)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_emit({"status": "error", "error": str(exc)}, 1)
|
||||
|
||||
if snap.next:
|
||||
_emit({
|
||||
"status": "interrupt",
|
||||
"thread_id": args.thread_id,
|
||||
"mock": not args.real,
|
||||
"next": list(snap.next),
|
||||
"iteration": snap.values.get("iteration"),
|
||||
"interrupt": _pending_interrupt(snap),
|
||||
}, 2)
|
||||
else:
|
||||
_emit({
|
||||
"status": "done",
|
||||
"thread_id": args.thread_id,
|
||||
"iteration": snap.values.get("iteration"),
|
||||
"steps": len(snap.values.get("history", [])),
|
||||
}, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user