From 814dbf1681afdc2112848ce26dca561bc7a45441 Mon Sep 17 00:00:00 2001 From: Myfro9 <36755290+Myfro9@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:41:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86skill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fpga-goldenref-flow/SKILL.md | 219 +++++++++++++++ .../run_fpga_local.cpython-311.pyc | Bin 0 -> 16925 bytes fpga-goldenref-flow/scripts/run_fpga_local.py | 260 ++++++++++++++++++ fpga-imx6-flow/SKILL.md | 177 ++++++++++++ fpga-imx6-flow/scripts/run_imx6_flow.py | 123 +++++++++ fpga_flash/SKILL.md | 13 +- fpga_flash/scripts/step4_5_program.tcl | 15 + .../.langgraph_api/.langgraph_ops.pckl | Bin 0 -> 42212 bytes langgraph-plan-review/SKILL.md | 27 ++ .../__pycache__/agents.cpython-311.pyc | Bin 0 -> 4953 bytes .../scripts/__pycache__/graph.cpython-311.pyc | Bin 0 -> 900 bytes .../__pycache__/workflow.cpython-311.pyc | Bin 0 -> 15457 bytes .../scripts/manage_dev_server.ps1 | 24 +- 13 files changed, 845 insertions(+), 13 deletions(-) create mode 100644 fpga-goldenref-flow/SKILL.md create mode 100644 fpga-goldenref-flow/scripts/__pycache__/run_fpga_local.cpython-311.pyc create mode 100644 fpga-goldenref-flow/scripts/run_fpga_local.py create mode 100644 fpga-imx6-flow/SKILL.md create mode 100644 fpga-imx6-flow/scripts/run_imx6_flow.py create mode 100644 langgraph-plan-review/.langgraph_api/.langgraph_ops.pckl create mode 100644 langgraph-plan-review/scripts/__pycache__/agents.cpython-311.pyc create mode 100644 langgraph-plan-review/scripts/__pycache__/graph.cpython-311.pyc create mode 100644 langgraph-plan-review/scripts/__pycache__/workflow.cpython-311.pyc diff --git a/fpga-goldenref-flow/SKILL.md b/fpga-goldenref-flow/SKILL.md new file mode 100644 index 0000000..9a16b6b --- /dev/null +++ b/fpga-goldenref-flow/SKILL.md @@ -0,0 +1,219 @@ +--- +name: fpga-goldenref-flow +description: > + Run the fpga-goldenref-flow LangGraph design-and-execution workflow for the + project at E:\LangGraph_Design\fpga-goldenref-flow. Drives the compiled graph + in-process (NO langgraph dev server) via a persistent SQLite checkpointer: + submits a run, surfaces the human-review interrupt (plans are summarised in the + Claude conversation for the user to approve/reject), resumes execution, and + displays the final summary — all within the Claude session. + + Trigger whenever the user says any of: "run FPGA flow", "start FPGA workflow", + "invoke fpga-goldenref-flow", "run goldenref flow", "启动 FPGA 工作流", + "运行 FPGA 设计流程", "跑一下 FPGA flow", "run the FPGA agent", + "execute the FPGA design pipeline", or any request to interact with the + LangGraph agent inside this project. Also trigger when the user wants to approve + or reject the generated plan, or resume a paused fpga-goldenref-flow run. +--- + +## 驱动方式:进程内驱动(无 dev server) + +工作流是一张 LangGraph 图,由 `scripts\run_fpga_local.py` **在本进程内**驱动 +(已移除 langgraph dev server 路径)。直接 `build_graph()` + **SqliteSaver 持久化** +驱动编译好的图,状态存在 `\.fpga_flow_local.sqlite`,按 `thread_id` +持久化,`start`/`resume`/`continue` 各自独立进程调用都能接续。 + +```powershell +conda run -n langgraph python ` + "C:\Users\bowei\.claude\skills\fpga-goldenref-flow\scripts\run_fpga_local.py" ` + start --input-dir "E:\\Notes" --output-dir "E:\\fpga_runs" +``` + +为什么是进程内驱动:`langgraph dev` 是**内存存储的开发服务器**,对本项目这种长耗时、 +多模块、反复中断续跑的执行不可靠——重启丢线程、run 队列被 cancel 残留堵成永久 +`pending`、watchfiles `--reload` 自激。进程内驱动没有这些问题(无队列、无内存丢失、 +无 reload),状态全部落在 SQLite。 + +--- + +## Project info + +| Item | Value | +|---|---| +| Root | `E:\LangGraph_Design\fpga-goldenref-flow` | +| Conda env | `langgraph` (`C:\Users\bowei\.conda\envs\langgraph`) | +| Graph name | `fpga_goldenref_flow` (在 `langgraph.json` 里声明;进程内驱动直接 `build_graph()`) | +| Default input dir | `Notes/` (contains PR00.md, Task00.md) | +| Helper script | `C:\Users\bowei\.claude\skills\fpga-goldenref-flow\scripts\run_fpga_local.py` | + +--- + +## Full workflow + +> **入口约束**:下面每一步都通过 helper 脚本 `run_fpga_local.py` 在 `langgraph` +> conda 环境里执行。不要直接 `import` `app.*` 在别处运行,统一走 helper 脚本以保证 +> SQLite 持久化与退出码一致。 + +### Step 1 — Submit a new run + +**Before calling the script, determine the two key paths:** +- `--input-dir` — the directory that holds the project's requirement files (e.g. `Notes/`). Use an absolute path or a path relative to the current shell's CWD. +- `--output-dir` — the directory where all generated files (task folders, plan.md, summary.md) and the run's checkpoint DB + session file will be written. **This must be inside the caller's project directory, not inside the workflow development directory.** Use the absolute path to the project being worked on, e.g. `E:\MyProject\fpga_runs`. + +Run the helper script using the `langgraph` conda environment: +```powershell +conda run -n langgraph python ` + "C:\Users\bowei\.claude\skills\fpga-goldenref-flow\scripts\run_fpga_local.py" ` + start ` + --input-dir "E:\\Notes" ` + --output-dir "E:\\fpga_runs" +``` + +Optional flag: `--prompt "additional context"` if the user supplied extra requirements. + +> **Rule:** always pass `--output-dir` as an absolute path pointing into the calling project. +> If omitted, files fall back to `/runs` which may or may not be in the project directory. + +The script prints `[INFO] Thread : ` and `[INFO] Output dir : ` (note them +for resume), then node-level progress (`-> node_name`) as the graph runs. + +**Exit codes:** +| Code | Meaning | +|---|---| +| 0 | Workflow completed — jump to Step 4 | +| 1 | Error — read the output, diagnose, report to user | +| 2 | An interrupt fired — read the `[INTERRUPT]` payload's `action` and branch (see below) | +| 3 | Codex unavailable — workflow stopped (no downgrade). Resume from checkpoint later with `continue`. | + +**Two interrupts fire in order — branch on `action` in the `[INTERRUPT]` payload:** +1. `"action": "select_plan_model"` — first, right after decomposition → **Step 1.5**. +2. `"action": "confirm_plan"` — later, after plans are generated → **Step 2**. + +### Step 1.5 — Select plan model (first interrupt) + +When the `[INTERRUPT]` payload has `"action": "select_plan_model"`, the workflow has +finished decomposition and is waiting for the user to pick the Claude model used to +**author/revise** the plans (review stays on Codex). The payload includes `default` +(Opus 4.8) and the decomposition overview. + +Ask the user which model (use AskUserQuestion), offering **Opus 4.8 (default/recommended)**, +**Fable 5**, **Sonnet 4.6**. Then resume with the choice: +```powershell +conda run -n langgraph python ` + "C:\Users\bowei\.claude\skills\fpga-goldenref-flow\scripts\run_fpga_local.py" ` + resume --output-dir "E:\\fpga_runs" ` + --plan-model opus # or: fable | sonnet | a full id like claude-opus-4-8 +``` +The workflow then generates plans with that model and stops again at the +`confirm_plan` interrupt (exit 2) → go to Step 2. + +### Step 2 — Human review (Claude reads plans, asks user) + +When the script exits with code 2 (`confirm_plan`) it prints: +- `[INTERRUPT]` — the raw interrupt payload (may include a message) +- `[PLAN] : ` — absolute paths to generated plan files +- `[REQ] : ` — absolute paths to requirement files + +For each plan path, read the file: +```powershell +Get-Content "" -Encoding utf8 +``` + +Then **summarise the plan for the user** in 3–5 bullet points per task and ask: + +> "FPGA 工作流已生成以下设计计划,请确认是否批准继续执行: +> …bullet points… +> **批准(继续执行)还是拒绝(请说明需要修改的地方)?**" + +Wait for the user's answer before proceeding to Step 3. + +### Step 3 — Resume the workflow + +> **并发安全(MANDATORY)**:`resume` 必须绑定到本次运行的工程,绝不能依赖全局会话文件。 +> **总是带上 `--output-dir`**(与 `start` 时完全相同的绝对路径),这样多个工程并发时 +> 各自的会话文件与 SQLite 互不串。等价地,也可以用 `--thread-id ""` +> 直接指定线程,完全绕过会话文件(最稳)。省略两者只在「同一时间只有一个工程在跑」时才安全。 + +**If the user approves:** +```powershell +conda run -n langgraph python ` + "C:\Users\bowei\.claude\skills\fpga-goldenref-flow\scripts\run_fpga_local.py" ` + resume --output-dir "E:\\fpga_runs" +``` +(Default behaviour is approved — no extra flag needed.) + +**If the user rejects:** +```powershell +conda run -n langgraph python ` + "C:\Users\bowei\.claude\skills\fpga-goldenref-flow\scripts\run_fpga_local.py" ` + resume --output-dir "E:\\fpga_runs" ` + --rejected --feedback "" +``` + +> Use the **same `--output-dir`** you passed at `start` (it is printed on the +> `[INFO] Output dir :` line). Alternatively pass `--thread-id ""` from the +> `[INFO] Thread :` line at start. + +After rejection the workflow loops back to plan generation; you will likely hit +exit code 2 again with new plans — repeat Step 2. + +> **Codex 不可用 (exit 3)**:若任一 resume/start 以 exit 3 退出,说明 codex(gpt-5.5) +> 额度被占满,工作流已停在断点(状态已存 SQLite,不降级)。等 codex 空出来后从断点续跑: +> ```powershell +> conda run -n langgraph python ` +> "C:\Users\bowei\.claude\skills\fpga-goldenref-flow\scripts\run_fpga_local.py" ` +> continue --output-dir "E:\\fpga_runs" +> ``` + +### Step 4 — Show results + +When the script exits with code 0 it prints `[SUMMARY]` followed by the contents +of `\summary.md`. Present this to the user. + +If the user wants to inspect a specific task's plan or requirement: +``` +\\plan.md +\\requirement.md +``` + +The `output-dir` is the value passed to `--output-dir` at start time (printed in the +`[INFO] Output dir :` line) and is also stored in the session file. + +--- + +## Session state (per-project — concurrency safe) + +Between `start` and `resume` the helper writes a small session file **inside the +run's own `output_dir`**, so concurrent projects never share it: +``` +\.fpga_flow_session.json +``` +It holds `thread_id` and `output_dir`. `resume`/`continue` locate it via the +`--output-dir` you pass (hence Step 3 requires it). It is deleted automatically when +the run completes successfully. + +**Isolation guarantees for concurrent projects:** +- Graph state (`MainState`/`FpgaState`, checkpoints) is isolated by `thread_id` + inside that run's own `\.fpga_flow_local.sqlite` — different projects + use different output dirs and different DBs, and cannot see each other's state. +- Generated files are isolated by `output_dir`. +- The session pointer is isolated by living inside each `output_dir`. **Always + pass `--output-dir` (or `--thread-id`) to `resume`** so it never reads another + project's session. + +To abandon a paused run and start fresh, delete that project's +`\.fpga_flow_session.json` (and, if you want a clean checkpoint, +`\.fpga_flow_local.sqlite`) before calling `start`. + +--- + +## Troubleshooting + +| Symptom | Fix | +|---|---| +| `ModuleNotFoundError` | `conda run -n langgraph pip install langgraph langgraph-checkpoint-sqlite` | +| `No session file found` | Must call `start` before `resume` (or pass `--thread-id`) | +| Empty `[PLAN]` lines | Look in `\` for `plan.md` files under each `` folder | +| `interrupted` never advances | Make sure `--rejected` flag is accompanied by `--feedback` text | +| exit 3 (Codex 不可用) | Wait for codex quota, then `continue --output-dir ` from the checkpoint | +| Stale state after editing `app\*` | In-process driver imports fresh each run; just re-run `start`/`continue`. To reset a thread, delete that run's `.fpga_flow_local.sqlite`. | diff --git a/fpga-goldenref-flow/scripts/__pycache__/run_fpga_local.cpython-311.pyc b/fpga-goldenref-flow/scripts/__pycache__/run_fpga_local.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..106e44b6326c3eca3187753fbfd8de3bae9bf51a GIT binary patch literal 16925 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/.fpga_flow_local.sqlite, keyed by thread_id. + +This is the sole driver for the graph (the langgraph dev server path has been +removed). CLI + exit codes: + + start submit a new run; exits 2 at an interrupt + resume answer an interrupt (--plan-model OR approve/--rejected --feedback) + continue continue a thread paused at a normal node boundary (input=None) + +Exit codes: 0 complete · 1 error · 2 interrupted (human action needed) +""" +import os +import sys +import json +import uuid +import argparse +import sqlite3 + +# Drive the graph in-process: import the app package. +FPGA_FLOW_ROOT = r"E:\LangGraph_Design\fpga-goldenref-flow" +if FPGA_FLOW_ROOT not in sys.path: + sys.path.insert(0, FPGA_FLOW_ROOT) + +RECURSION_LIMIT = int(os.getenv("FPGA_FLOW_RECURSION_LIMIT", "300")) + + +def _session_file(output_dir: str = "") -> str: + base = (output_dir or "").strip() or FPGA_FLOW_ROOT + return os.path.join(base, ".fpga_flow_session.json") + + +def _db_path(output_dir: str) -> str: + base = (output_dir or "").strip() or FPGA_FLOW_ROOT + return os.path.join(base, ".fpga_flow_local.sqlite") + + +def save_session(thread_id, output_dir): + with open(_session_file(output_dir), "w", encoding="utf-8") as f: + json.dump({"thread_id": thread_id, "output_dir": output_dir, "driver": "local"}, f) + + +def load_session(output_dir=""): + path = _session_file(output_dir) + if not os.path.exists(path): + print(f"[FAIL] No session file at {path}. Run 'start' first (or pass --thread-id).", flush=True) + sys.exit(1) + with open(path, encoding="utf-8") as f: + return json.load(f) + + +# ── graph + checkpointer ────────────────────────────────────────────────────── + +def _build(output_dir): + """Compile the graph with a persistent SQLite checkpointer (keeps the conn).""" + from langgraph.checkpoint.sqlite import SqliteSaver + from app.main_graph import build_graph + conn = sqlite3.connect(_db_path(output_dir), check_same_thread=False) + saver = SqliteSaver(conn) + try: + saver.setup() # create checkpoint tables if not present + except Exception: + pass + return build_graph(checkpointer=saver), conn + + +def _config(thread_id): + return {"configurable": {"thread_id": thread_id}, "recursion_limit": RECURSION_LIMIT} + + +def _stream(graph, config, payload): + """Run the graph to its next pause, printing node-level progress.""" + for chunk in graph.stream(payload, config, stream_mode="updates"): + if isinstance(chunk, dict): + for node in chunk: + if node and not str(node).startswith("__"): + print(f" -> {node}", flush=True) + + +def _pending_interrupts(graph, config): + """Return list of interrupt payload values currently pending (if any).""" + snap = graph.get_state(config) + vals = [] + for task in (snap.tasks or ()): + for it in (getattr(task, "interrupts", None) or ()): + v = getattr(it, "value", it) + vals.append(v) + return vals, snap + + +def _print_interrupt(graph, config): + vals, snap = _pending_interrupts(graph, config) + for v in vals: + print(f"\n[INTERRUPT] {json.dumps(v, indent=2, default=str, ensure_ascii=False)}", flush=True) + # plan/requirement paths from state + values = snap.values or {} + for serial in values.get("serial_tasks", []): + for pt in serial.get("parallel_tasks", []): + tid = pt.get("task_id", "?") + if pt.get("plan_path"): + print(f"[PLAN] {tid}: {pt['plan_path']}", flush=True) + if pt.get("req_path"): + print(f"[REQ] {tid}: {pt['req_path']}", flush=True) + print("\n[ACTION REQUIRED] Claude will read these plans and ask for your approval.", flush=True) + + +def _show_summary(output_dir): + summary = os.path.join(output_dir, "summary.md") + if os.path.exists(summary): + with open(summary, encoding="utf-8") as f: + print(f"\n[SUMMARY]\n{f.read()}", flush=True) + else: + print(f"[DONE] Workflow completed. (summary not found at {summary})", flush=True) + + +def _finish(graph, config, output_dir): + """After a stream returns, classify: interrupted (2) / done (0).""" + vals, snap = _pending_interrupts(graph, config) + if vals: + print("\n[STATUS] interrupted", flush=True) + _print_interrupt(graph, config) + sys.exit(2) + if snap.next: + # paused without an interrupt (rare) — treat as needing continue + print(f"\n[STATUS] paused (next={list(snap.next)})", flush=True) + sys.exit(2) + print("\n[STATUS] success", flush=True) + _show_summary(output_dir) + sf = _session_file(output_dir) + if os.path.exists(sf): + os.remove(sf) + sys.exit(0) + + +# ── commands ────────────────────────────────────────────────────────────────── + +def cmd_start(args): + raw_out = (args.output_dir or "").strip() + output_dir = os.path.abspath(raw_out) if raw_out else os.path.join( + os.path.dirname(os.path.abspath(args.input_dir)), "runs") + os.makedirs(output_dir, exist_ok=True) + thread_id = str(uuid.uuid4()) + print(f"[INFO] Driver : in-process (no dev server)", flush=True) + print(f"[INFO] Thread : {thread_id}", flush=True) + print(f"[INFO] Output dir : {output_dir}", flush=True) + print(f"[INFO] Checkpoints : {_db_path(output_dir)}", flush=True) + + graph, conn = _build(output_dir) + try: + inp = {"input_dir": args.input_dir, "output_dir": output_dir} + if args.prompt: + inp["user_prompt"] = args.prompt + print("[INFO] Running fpga_flow graph ...\n", flush=True) + save_session(thread_id, output_dir) + _stream(graph, _config(thread_id), inp) + _finish(graph, _config(thread_id), output_dir) + finally: + conn.close() + + +def _resolve_thread(args): + output_dir = (args.output_dir or "").strip() + thread_id = (getattr(args, "thread_id", "") or "").strip() + if not thread_id: + sess = load_session(output_dir) + thread_id = sess["thread_id"] + output_dir = sess.get("output_dir", output_dir) + return thread_id, output_dir + + +def cmd_resume(args): + from langgraph.types import Command + thread_id, output_dir = _resolve_thread(args) + graph, conn = _build(output_dir) + try: + plan_model = (getattr(args, "plan_model", "") or "").strip() + if plan_model: + resume_val = {"plan_model": plan_model} + print(f"[INFO] Resuming {thread_id} plan_model={plan_model}", flush=True) + else: + resume_val = {"approved": not args.rejected, "feedback": args.feedback or ""} + print(f"[INFO] Resuming {thread_id} approved={not args.rejected}", flush=True) + print("", flush=True) + _stream(graph, _config(thread_id), Command(resume=resume_val)) + _finish(graph, _config(thread_id), output_dir) + finally: + conn.close() + + +def cmd_continue(args): + thread_id, output_dir = _resolve_thread(args) + graph, conn = _build(output_dir) + try: + print(f"[INFO] Continuing {thread_id} from checkpoint ...\n", flush=True) + _stream(graph, _config(thread_id), None) + _finish(graph, _config(thread_id), output_dir) + finally: + conn.close() + + +def main(): + p = argparse.ArgumentParser(description="fpga-goldenref-flow IN-PROCESS runner (no dev server)") + sub = p.add_subparsers(dest="cmd") + + ps = sub.add_parser("start") + ps.add_argument("--input-dir", default="Notes/") + ps.add_argument("--output-dir", default="") + ps.add_argument("--prompt", default="") + + pr = sub.add_parser("resume") + pr.add_argument("--plan-model", default="") + pr.add_argument("--rejected", action="store_true", default=False) + pr.add_argument("--feedback", default="") + pr.add_argument("--output-dir", default="") + pr.add_argument("--thread-id", default="") + + pc = sub.add_parser("continue") + pc.add_argument("--output-dir", default="") + pc.add_argument("--thread-id", default="") + + args = p.parse_args() + if not args.cmd: + p.print_help(); sys.exit(1) + + try: + from app.nodes_design import CodexUnavailable + except Exception: + CodexUnavailable = () # type: ignore + + try: + if args.cmd == "start": + cmd_start(args) + elif args.cmd == "resume": + cmd_resume(args) + elif args.cmd == "continue": + cmd_continue(args) + except SystemExit: + raise + except CodexUnavailable as exc: + out = getattr(args, "output_dir", "") or "" + bar = "=" * 64 + print(f"\n{bar}", flush=True) + print("[ALARM] CODEX 不可用 —— 工作流已停止(不降级)", flush=True) + print(f"原因:{exc}", flush=True) + print("codex(gpt-5.5) 与你其它工作共享额度。请等 codex 空出来后,从断点续跑:", flush=True) + print(f' run_fpga_local.py continue --output-dir "{out}"', flush=True) + print("状态已保存到 SQLite,不会丢失;不会回退到 Claude 或更弱模型。", flush=True) + print(bar, flush=True) + sys.exit(3) + + +if __name__ == "__main__": + main() diff --git a/fpga-imx6-flow/SKILL.md b/fpga-imx6-flow/SKILL.md new file mode 100644 index 0000000..94053db --- /dev/null +++ b/fpga-imx6-flow/SKILL.md @@ -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 | `\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-" --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-" --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 +`\runs\` artifacts or the per-step `history`. + +To abandon a paused run and start clean, delete `\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. | diff --git a/fpga-imx6-flow/scripts/run_imx6_flow.py b/fpga-imx6-flow/scripts/run_imx6_flow.py new file mode 100644 index 0000000..ef3da1e --- /dev/null +++ b/fpga-imx6-flow/scripts/run_imx6_flow.py @@ -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 [--program-mode bitstream|flash] [--bit-path ..] + [--flash-path ..] [--thread-id ..] [--real] [--max-iterations N] + resume --project-dir --resume-json '' [--thread-id ..] [--real] + status --project-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() diff --git a/fpga_flash/SKILL.md b/fpga_flash/SKILL.md index e39ce0d..9f80834 100644 --- a/fpga_flash/SKILL.md +++ b/fpga_flash/SKILL.md @@ -118,11 +118,18 @@ python "C:/Users/bowei/.claude/skills/fpga_flash/scripts/program_flash.py" \ - `--mcs-file`:第三步生成的 .mcs 路径 - `--ltx-file`:若设计无 ILA,省略此参数(脚本自动跳过 PROBES.FILE 配置) -此脚本在同一个 Vivado batch 会话中完成:硬件连接 → cfgmem 器件创建 → 擦除/编程/验证。 +此脚本在同一个 Vivado batch 会话中完成:硬件连接 → cfgmem 器件创建 → **载入 flash-loader 比特流(关键步骤)** → 擦除/编程/验证 → 回载用户 bit。 -**验证成功的标志:** 输出包含 `End of startup status: HIGH` 且无 `ERROR` 行。 +**验证成功的标志(真正写入 flash):** 输出必须包含 +`Performing Erase Operation... Erase Operation successful`、 +`Program/Verify Operation successful`、`Flash programming completed successfully`, +并能读到非零的 `Mfg ID`(如 Winbond=`ef`、Micron=`20`)。 +> ⚠️ 仅有 `End of startup status: HIGH` **不代表写了 flash**——那只是把 bit 载入 FPGA +> 易失配置。若看到 `Mfg ID : 0 / Unable to read device properties`,说明 flash-loader +> 没接管 SPI 总线(本 tcl 已用 `create_hw_bitstream`+`program_hw_devices` 载入 +> `spi__pullnone.bit` 修复此问题)。 -ILA core 的 `Dropping logic core` WARNING 是正常现象(golden 镜像不含 ILA core),不影响烧写结果。 +ILA core 的 `Dropping logic core` WARNING 是正常现象(载入 loader/golden 镜像时不含 ILA core),不影响烧写结果。 --- diff --git a/fpga_flash/scripts/step4_5_program.tcl b/fpga_flash/scripts/step4_5_program.tcl index e247fde..a246568 100644 --- a/fpga_flash/scripts/step4_5_program.tcl +++ b/fpga_flash/scripts/step4_5_program.tcl @@ -38,6 +38,21 @@ set_property PROGRAM.FILES [list $mcs_file] \ [get_property PROGRAM.HW_CFGMEM [lindex [get_hw_devices xc7a200t_0] 0]] refresh_hw_device [lindex [get_hw_devices xc7a200t_0] 0] +# --- CRITICAL: load Vivado's flash-loader bitstream into the FPGA FIRST so it +# bridges JTAG<->SPI flash and takes over the SPI bus. Without this, the user +# design (or boot image) holds the SPI pins and the flash read returns +# "Mfg ID : 0 / Unable to read device properties" (program fails silently). +# This is exactly what the Vivado GUI "Program Configuration Memory Device" +# does under the hood before program_hw_cfgmem. --- +set loader_bit [get_property PROGRAM.HW_CFGMEM_BITFILE [lindex [get_hw_devices xc7a200t_0] 0]] +create_hw_bitstream -hw_device [lindex [get_hw_devices xc7a200t_0] 0] $loader_bit +program_hw_devices [lindex [get_hw_devices xc7a200t_0] 0] + +# Erase + program + verify the SPI flash (this is the actual flash write). +program_hw_cfgmem -hw_cfgmem [get_property PROGRAM.HW_CFGMEM [lindex [get_hw_devices xc7a200t_0] 0]] + +# Optionally reload the user bit + probes so the device runs the design (with ILA) +# after flashing. A power-cycle will boot the same content from flash. if {$has_ltx} { set_property PROBES.FILE $ltx_file [get_hw_devices xc7a200t_0] set_property FULL_PROBES.FILE $ltx_file [get_hw_devices xc7a200t_0] diff --git a/langgraph-plan-review/.langgraph_api/.langgraph_ops.pckl b/langgraph-plan-review/.langgraph_api/.langgraph_ops.pckl new file mode 100644 index 0000000000000000000000000000000000000000..c3b8e8ec7a5835abb22d4f21afe27fd8455d8aa4 GIT binary patch literal 42212 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 Gf~fz?!vcT+ literal 0 HcmV?d00001 diff --git a/langgraph-plan-review/scripts/__pycache__/workflow.cpython-311.pyc b/langgraph-plan-review/scripts/__pycache__/workflow.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e61694ac762fefc43aea5dcbb9a52e744d890bef GIT binary patch literal 15457 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 V?3|gjQ{C{5Ll*j>wNS##{|(M(3fTYv literal 0 HcmV?d00001 diff --git a/langgraph-plan-review/scripts/manage_dev_server.ps1 b/langgraph-plan-review/scripts/manage_dev_server.ps1 index 1c55c86..10f7073 100644 --- a/langgraph-plan-review/scripts/manage_dev_server.ps1 +++ b/langgraph-plan-review/scripts/manage_dev_server.ps1 @@ -40,9 +40,9 @@ function Get-StoredPid { } function Test-ServerRunning { - $pid = Get-StoredPid - if ($pid -eq 0) { return $false } - $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + $storedPid = Get-StoredPid + if ($storedPid -eq 0) { return $false } + $proc = Get-Process -Id $storedPid -ErrorAction SilentlyContinue if (-not $proc) { return $false } return (Test-HttpOk "$ApiUrl/ok") } @@ -107,8 +107,8 @@ switch ($Command) { "start" { if (Test-ServerRunning) { - $pid = Get-StoredPid - Write-Host "[dev-server] Already running (PID $pid) at $ApiUrl" + $storedPid = Get-StoredPid + Write-Host "[dev-server] Already running (PID $storedPid) at $ApiUrl" Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl" exit 0 } @@ -122,6 +122,10 @@ switch ($Command) { Remove-Item $StdoutLog -ErrorAction SilentlyContinue Remove-Item $StderrLog -ErrorAction SilentlyContinue + # Force UTF-8 to avoid GBK decode errors on Chinese Windows systems + $env:PYTHONUTF8 = "1" + $env:PYTHONIOENCODING = "utf-8" + $proc = Start-Process -FilePath $lgExe ` -ArgumentList "dev", "--no-browser" ` -WorkingDirectory $SkillDir ` @@ -167,9 +171,9 @@ switch ($Command) { } "stop" { - $pid = Get-StoredPid - if ((Stop-ServerProcess -PidValue $pid)) { - Write-Host "[dev-server] Stopped (PID $pid)" + $storedPid = Get-StoredPid + if ((Stop-ServerProcess -PidValue $storedPid)) { + Write-Host "[dev-server] Stopped (PID $storedPid)" } else { Write-Host "[dev-server] Not running (nothing to stop)" } @@ -178,8 +182,8 @@ switch ($Command) { "status" { if (Test-ServerRunning) { - $pid = Get-StoredPid - Write-Host "[dev-server] Running (PID $pid) at $ApiUrl" + $storedPid = Get-StoredPid + Write-Host "[dev-server] Running (PID $storedPid) at $ApiUrl" Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl" } else { Write-Host "[dev-server] Not running"