sync: BOWEI 2026-08-29 新增 dpd-mig-flow / sim-rtl-flow 两个 skill,去硬编码

- dpd-mig-flow、sim-rtl-flow 首次入库
- 两者的 LangGraph 检出路径、SMOD 参考工程路径、N9320B/板卡 IP 改为
  引用 ~/skills_env.md(新增 DPD_MIG_FLOW_ROOT / SIM_RTL_FLOW_ROOT /
  XILINX_SMOD_ROOT / SMOD_ATSC_REF 等条目)
- sim-rtl-flow 示例里写死的 /c/Users/Administrator 临时路径改为 $TMPDIR
- 撤出上次遗留的误入库 __pycache__/run_fpga_local.cpython-311.pyc
- .gitignore 忽略 orca-cli(指向 ~/.agents/skills 的外部链接)
This commit is contained in:
bowei
2026-09-07 16:17:45 +08:00
committed by Myfro9
parent 24b9c271e1
commit d631dfef5c
4 changed files with 241 additions and 3 deletions
+1
View File
@@ -369,5 +369,6 @@ __pycache__/
/vercel-react-native-skills/
/vercel-react-view-transitions/
/web-design-guidelines/
/orca-cli/
/.stfolder/
/.claude/
+236
View File
@@ -0,0 +1,236 @@
---
name: dpd-mig-flow
description: >
Run the dpd-mig-flow LangGraph workflow (checkout path 见 `~\skills_env.md` 的
`DPD_MIG_FLOW_ROOT`) — the
pipeline that ports the DPD + CFR functionality (FPGA RTL + i.MX6 app) from the ATSC
reference project (SMOD_ATSC\my_exciter) into **another SMOD project**, then verifies it
on real hardware: survey the reference design, port FPGA and imx6 sides in parallel,
build the bitstream and cross-compile the app (both via the fpga-imx6-flow skill),
debug on the board with the fake-PA distortion model, and gate on two objective
measurements — DPD trains without error and settles to Hold with shoulder ≥ 40 dB, and
the shoulder survives a power cycle. It drives the graph in-process (no langgraph dev)
and hands its 6 interrupts back to this Claude session to execute.
ALWAYS use this skill whenever the user wants to port, migrate, or transplant DPD/CFR
into another SMOD project, or wants to run/start/drive/resume/continue this workflow —
including phrasings like "移植 DPD", "把 DPD 和 CFR 移到 DAB/DVBT2 工程", "跑 dpd-mig-flow",
"启动 DPD 移植流程", "继续 DPD 移植", "resume the DPD migration", "port DPD to the DVB-T2
exciter", "DPD 带肩不达标怎么迭代", "验证 DPD 断电保持", even when they don't name the
project path or say the word "skill". Because the target project is never fixed, this
skill always asks the user which Vivado project to port INTO before starting.
---
## What this skill does
Drives the **dpd-mig-flow** graph (10 nodes / 12 flow-edges, 1:1 with
`Notes/dpd-mig-flow.drawio`; plan in `Notes/Task00.md`). Two node classes:
- **6 interrupt nodes** pause the graph and hand control to **this Claude session**,
which does the real work (read reference RTL/C, copy and rewire code, invoke the
`fpga-imx6-flow` skill to synthesize / cross-compile / debug on the board).
- **2 measurement nodes** run in-process and decide objectively: read DPD state over
HTTP/SSH, measure shoulder with the N9320B, power-cycle the board via the COM relay.
Persistence is a SqliteSaver file, so `start` / `resume` survive across separate process
calls, and each target project gets its own thread.
| Item | Value |
|---|---|
| Project root | 见 `~\skills_env.md` 的 `DPD_MIG_FLOW_ROOT` |
| Conda env | `langgraph` (path 见 `~\skills_env.md` 的 `CONDA_LANGGRAPH_PYTHON`) |
| Graph | `app.main_graph:build_graph` (in-process, no langgraph dev) |
| Driver | `<project>\run.py`(`start` / `resume` / `status` 三个子命令) |
| Checkpoint | `<project>\runs\checkpoint.sqlite`(按 `thread_id` 分区) |
| Reference project | 见 `~\skills_env.md` 的 `SMOD_ATSC_REF`,顶层 `...\sources_1\top\smod_top.v` |
| Target project | **每次运行由用户指定**,见 Step 1 |
| Design source | `Notes/dpd-mig-flow.drawio`,方案 `Notes/Task00.md` |
| Self-test | `<project>\smoke_test.py`(mock,68 项检查) |
```
PY = <CONDA_LANGGRAPH_PYTHON — 见 ~\skills_env.md>
PROJ = <DPD_MIG_FLOW_ROOT — 见 ~\skills_env.md>
RUN = %PROJ%\run.py
```
> **直接用环境里的 python.exe 跑,别用裸 `conda run`**:本机 GBK 控制台下 `conda run` 会用
> GBK 重新编码子进程的 UTF-8 stdout,**遇到中文 interrupt payload 会崩**。用环境
> `python.exe`,或 `conda run --no-capture-output`。
## ⚠️ 硬件时序铁律
**绝不在 USB-Blast 处于连接状态时给板卡上电或断电。** 本工作流的 `check_power_cycle`
节点会真的断电重启,其内部顺序已固定为:
```
断开 USB-Blast → 断电 → 等待 → 上电 → 等 app 就绪
```
`power_on()` / `power_off()` 各自还会兜底确认一次 USB-Blast 已断开。断电保持判定不需要
JTAG,所以默认**不再接回** USB-Blast(需要接回时把 `env_config.json` 的
`power.reconnect_blast` 置 `true`)。手工介入板卡时同样遵守这条。
继电器 COM 号**唯一来源是 `~/vivado_mcp_usb.conf`**(`[power]` / `[usb_blast]`,逐机而异、
不入库),与 vivado-mcp 的 `power_on`/`USB_Blast_on` 共用同一份配置;文件缺失时调 vivado-mcp
的 **`detect_serial_relays`** 工具自动探测生成。
## Step 0 — 确认环境(驱动前必做)
```powershell
$py = @("$env:USERPROFILE\miniconda3\envs\langgraph\python.exe","$env:USERPROFILE\.conda\envs\langgraph\python.exe") | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $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')"
}
```
`[OK]` → 进 Step 1。缺失/导入失败 → 告诉用户先跑 **`/langgraph-vivado-install`**,别硬上。
想先确认工作流本身没坏(不碰任何硬件):
```powershell
& $py "$PROJ\smoke_test.py" # 期望 ALL PASS (68 checks)
```
## Step 1 — 问用户:移植到哪个工程?(这一步不能跳过)
设计图里全程只说"当前 vivado 工程",因为**它本来就不固定**——这条流水线是为"把 ATSC 的
DPD/CFR 搬到任意 SMOD 工程"设计的。所以目标工程**必须每次运行由用户显式给出**:没有默认值、
不沿用上次、不从配置文件里读、不替用户猜。`run.py` 会强制校验,缺参数直接非零退出。
`XILINX_SMOD_ROOT`(见 `~\skills_env.md`)下的候选通常有 `SMOD_DAB`、`SMOD_DVBT2`、`SMOD_DTMB_CG`、
`SMOD_DVBS`、`SMOD_SAT`。用 AskUserQuestion 或直接问,把这 7 项凑齐:
| 参数 | 含义 | 怎么定 |
|---|---|---|
| `--target-vivado` | 目标 Vivado 工程目录 | 用户指定 |
| `--target-xpr` | 目标工程 `.xpr` | 在上面目录里找,找到多个就让用户选 |
| `--target-imx6` | 目标配套 imx6 工程目录 | 通常是工程下的 `imx6` |
| `--standard` | 调制标准 | DAB / DVBT2 / DTMB / DVBS / SAT |
| `--symbol-clk-mhz` | 调制核输出符号采样率时钟 | **决定 DPD/CFR 挂在哪条数据链上**,问用户或从目标顶层读出来核实 |
| `--center-hz` | 频谱测量中心频率 | 决定带肩怎么测 |
| `--ch-bw-hz` | 信道带宽 | 同上 |
参数多,可以先写成档案文件再点名(**档案名仍须每次显式给出**,不许自动挑最新的):
```powershell
& $py $RUN start --target-profile "$PROJ\targets\smod_dab.json"
```
`start` 会校验目录/`.xpr` 真实存在、目标不是参考工程(防止误改参考工程 `my_exciter`),派生
`thread_id`(含工程指纹,不同工程绝不共用 checkpoint),并把参数落到
`runs/<thread_id>/launch.json` 留痕。
## Step 2 — 启动
```powershell
& $py $RUN start `
--target-vivado "<目标工程目录>" `
--target-xpr "<目标工程目录>\<name>.xpr" `
--target-imx6 "<目标工程目录>\imx6" `
--standard DAB --symbol-clk-mhz 8.192 --center-hz 214e6 --ch-bw-hz 1.536e6
```
输出一行 JSON:`{"status":"waiting","thread_id":...,"interrupts":[{"id":...,"task":...,"value":{...}}]}`。
**记下 `thread_id`**,后面每次 resume 都要用。
想先空跑一遍看流程(不碰仪器/串口/板卡):设 `$env:DPD_MIG_MOCK="1"` 再 start。
## Step 3 — 执行 interrupt,然后 resume(主循环)
每个 interrupt 的 `value` 里带 `action`、`instructions`(这一步的完整任务书)、`target`
(本次运行指定的目标工程,**据此决定往哪个工程写**,别靠记忆)、`expected_resume`(该回填什么)。
### ⚠️ 并行阶段会同时抛出 2 个 interrupt
`study_ref` 之后图分成 FPGA 侧和 imx6 侧两条并行支路,`invoke` 会**同时**返回 2 个
interrupt(构建阶段同理)。因此 resume **统一用 `{interrupt_id: payload}` 映射**,
单值形式会因为无法指定目标而报错卡死。可以分批恢复:只给一个 id,另一个保持挂起。
### resume 的正确姿势:写文件,别贴 JSON
Windows PowerShell 5.1 会**吞掉**传给原生 exe 的双引号,直接把 JSON 贴到命令行必然变成非法
JSON。用 `--mapping-file`(最省事,Write 出一个文件即可)或 `--mapping-b64`:
```powershell
# resume.json 内容形如:
# { "<id_of_port_fpga_rtl>": { "fpga_port_done": true, ... },
# "<id_of_port_imx6_src>": { "imx6_port_done": true, ... } }
& $py $RUN resume --thread-id "<thread_id>" --mapping-file "<scratch>\resume.json"
# 随时看卡在哪
& $py $RUN status --thread-id "<thread_id>"
```
### 6 个 interrupt 各自要做什么
| action | 你要做的事 | resume 回填 |
|---|---|---|
| `survey_reference`<br>(#3) | 通读参考工程的 DPD/CFR 用法,产出调研 md + 待移植文件清单。重点:旁路宏 `DPDENABLE`/`CFR_ENABLE`;发射链 `digital_rms_agc → CFR_cfr_conf → dacfr → DPD`;ORx 反馈链 `120M → rx1_dec3_120to40 → orx_dec_hb_40to10 → RAM2 → orx_iq_udp_tx`;SPI3 仲裁 `spi3_agc_arbiter`;DPD 系数存取 `spi_slave_2_DPD_SL.v` | `ref_survey_md`, `ref_file_list` |
| `port_fpga`<br>(#4) | 复制 DPD/CFR 源码进目标工程并加入 `.xpr`;参考 `smod_top.v` 接进目标顶层;**做速率转换**(参考工程 DPD/CFR 跑 40 MHz,目标符号率不同,必须接在调制核输出的正确符号采样率链上并与后续 204b 接口时序匹配);宏旁路;**ORx AGC + SPI3 仲裁与参考工程完全一致**;**保留 fake-PA**;建独立 DPD 寄存器模块(对标 `reg_map_dpd.v`) | `fpga_port_done`, `fpga_ported_files`, `rate_conv_scheme`, `reg_map_dpd_path`, `bypass_macros`, `fake_pa_kept`, `orx_agc_ported` |
| `port_imx6`<br>(#5) | 整理并移植 6 项功能:①寄存器控制 ②**DPD 控制状态流程 md(含 mermaid)** ③前面板键盘+液晶 ④SPI3 仲裁 ⑤网页控制+状态显示 ⑥隐藏菜单参数。入口:`bsp/dpd_proc.c`、`bsp/eeprom.c`、`task/lcd_task.c`、`task/uart_user.c`、`adrv9009/adrv9009_config.c`、`web/www*/sys_set.html`。**顺手把 `env_config.json` 的 `board.endpoints` 按实际接口填实** | `imx6_port_done`, `imx6_ported_files`, `dpd_state_md`, `imx6_feature_md`, `imx6_features_ok`(6 项全 true 才放行) |
| `build_fpga`<br>(#11) | 调 **`fpga-imx6-flow` skill** 综合实现出 bitstream + flash 文件;重点看速率转换到 204b 那段时序有没有违例 | `build_fpga_ok`, `bit_path`(必须真实存在), `flash_path` |
| `build_imx6`<br>(#6) | 调 **`fpga-imx6-flow` skill** 交叉编译出 app | `build_imx6_ok`, `app_path` |
| `hw_debug`<br>(#10) | 调 **`fpga-imx6-flow` skill** 下 bit、部署 app、重启;**开 fake-PA**;网页 DPD start 训练;看 ILA/串口;用 N9320B 看 DPD 前后带肩。离开本节点前确保 fake-PA 已开、DPD 已 start、板卡与频谱仪在线——下一个节点会自动读状态+测带肩来判定 | `debug_log`, `fake_pa_on`(必须 true), `dpd_started`(必须 true), `shoulder_before_db`(可选,用于判定改善量) |
第 2 轮起 `hw_debug` 的 payload 里会带 `previous_failure`:**先看它再动手**。带肩不达标 →
查 DPD 收敛 / ORx AGC / 速率转换;断电后带肩掉了 → 查 DPD 系数 save/load 与 eeprom 持久化。
### 两个判定节点(不需要你介入,除非降级)
- `check_dpd_train`(#15):等训练收敛 → 读 DPD 状态 → 测带肩。四个条件全过才算通过:
训练无报错、状态为 `Hold`、带肩 ≥ 40 dB、(若已知 DPD 前带肩)确有改善。否则回 `hw_debug`。
- `check_power_cycle`(#19):测重启前带肩 → 按铁律断电重启 → 等 app 起来 → 复测 →
变化 ≤ 1.5 dB 且仍 ≥ 40 dB 且状态仍为 Hold 才算通过。否则回 `hw_debug`。
**降级裁决**:仪器/板卡够不着或读数矛盾时,节点会抛 `action: "adjudicate"` 的 interrupt,
payload 的 `evidence` 里带着已采到的数据。你据此给结论:`{"pass": true/false, "reason": "..."}`。
别为了让流程往下走而随手判 true——这两个判据就是整个任务的验收标准。
> 测量结果按 `(thread, iteration)` 落盘缓存(`runs/<thread>/iter<N>/verify_*.json`)。
> interrupt-resume 会让节点从头重跑,缓存保证**一轮之内绝不会把板子断电两次**。
## Step 4 — 结束
两个判定都过 → `task_done`,`status` 变 `done`,汇总落 `runs/<thread_id>/summary.json`
(含最终带肩、断电前后读数、bit/app 路径、产出的几份 md)。把这些回报给用户。
迭代超过 `max_iterations`(默认 10)会优雅报错退出——这是让人介入判断"是移植方案的问题还是
指标定义的问题",不是让你调大上限硬撑。
## 真机运行前的检查清单
`DPD_MIG_MOCK` **不设**(或设为 0)就是真机模式。开跑前确认:
| 项 | 怎么确认 | 缺了怎么办 |
|---|---|---|
| 继电器 COM 号 | `~/vivado_mcp_usb.conf` 有 `[power]` 和 `[usb_blast]` | 调 vivado-mcp 的 `detect_serial_relays` |
| 板卡 DPD 接口 | `env_config.json` 的 `board.endpoints`(或 `ssh_commands`)已按目标工程实际接口填实 | 还是占位模板时 `tools_board` 会**明确报错**,不会对着不存在的接口测出假结论;先完成 `port_imx6` 再来 |
| N9320B | `spectrum.host`(默认取 `~\skills_env.md` 的 `N9320B_HOST`)能 ping 通 | 检查网络/仪器电源 |
| 板卡 | `board.ip`(默认取 `~\skills_env.md` 的 `SMOD_BOARD_IP`)能通 | 先上电 |
| 频谱判据 | `center_hz` / `ch_bw_hz` 是本次目标标准的正确值 | 问用户 |
带肩定义(DPD 前后、断电前后必须用**完全相同**的仪器设置,否则数值不可比):带内参考取
信道内 PSD 均值,肩部取 `±(ch_bw/2 + ch_bw × shoulder_offset_ratio)` 处 PSD,
`shoulder_db = 带内 − 肩部`,取上下边带较差者。RBW/VBW/检测器/平均/参考电平/衰减全部由
`env_config.json` 固定。
## 常见问题
| 现象 | 原因 / 处理 |
|---|---|
| `缺少必填的目标工程参数` | 按设计如此(每次必须显式指定),把 7 个参数补齐 |
| `目标工程不能是参考工程` | 防误改 `my_exciter` 的保护,检查 `--target-vivado` |
| `目标工程参数与本 thread 已记录的不一致` | 中途改了参数。换工程会自动换 thread;只是想改测量频点/带宽就用 `--tag` 开新 thread,别在半途改判据 |
| `resume 参数不是合法 JSON` 且引号丢了 | PowerShell 吞引号,改用 `--mapping-file` / `--mapping-b64` |
| `未知的 interrupt id` | id 过期了(已被恢复过)。先 `status` 拿当前 id |
| `board 接口仍是占位模板` | `port_imx6` 还没把实际网页/命令行接口填进 `env_config.json` |
| 控制台中文乱码/崩溃 | 别用裸 `conda run`,直接用环境 `python.exe` |
| 改了 `app/*.py` 后行为没变 | 无 dev server 缓存,直接重跑即可;若图结构改到与旧 checkpoint 不兼容,换 `--tag` 或删 `runs/checkpoint.sqlite` |
## 相关
- **`fpga-imx6-flow`** — 综合/交叉编译/下载/上板调试都转交它(本图不嵌套调用另一张图,
两张图各有 checkpointer 与 interrupt 语义,进程内嵌套会互相污染)
- **`smod_atsc_dev`** — SMOD 平台事实、两条烧写路径的选择、上电时序
- **`fpga_flash`** / **`fpga_update`** — JTAG 直烧 / 远程升级 mcs
- **`langgraph-vivado-install`** — 创建 `langgraph` conda 环境
+4 -3
View File
@@ -1,7 +1,8 @@
---
name: sim-rtl-flow
description: >
Run the sim-rtl-flow LangGraph workflow at E:\LangGraph_Design\sim-rtl-flow — the
Run the sim-rtl-flow LangGraph workflow (checkout path 见 `~\skills_env.md` 的
`SIM_RTL_FLOW_ROOT`) — 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 →
@@ -228,7 +229,7 @@ regenerated tree has lost last round's attributes. Default, as everywhere here,
`<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'
cat > $TMPDIR/sim_rtl_flow_input.json <<'EOF'
{
"requirements_path": "Notes/requirements.md",
"work_dir": "E:/Xilinx_design/my_project",
@@ -257,7 +258,7 @@ EOF
### 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
"$PY" "$RUN" --thread-id my-design-001 start --input $TMPDIR/sim_rtl_flow_input.json
```
`start` takes `work_dir` from the input JSON and prints where state landed. Verified: