Files
bowei ea4541ee90 sync: SHANGHAI-U9 2026-07-27 三个 Simulink→RTL skill 转系统级 + 新增 simulink-rtl-redev
- simulink_rtl_dev / rtl-ip-replace: 从工程级 .claude/skills 迁到用户级并去工程耦合
  (模型名/Notes 文档依赖/工程实测基线移出; addpath 与脚本路径改用户级)
- rtl-ip-replace/scripts/gen_cmp_tb.py: Vivado 路径不再写死, 改读 VIVADO_PATH /
  XILINX_VIVADO (兼容 install root / bin / bin\vivado.bat 三种写法), 回落时提示
  去设 skills_env.md 的 VIVADO_PATH
- simulink-rtl-redev: 新增。原生模型改动后的仿真模型同步规范, 含两个实测过的工具
  check_sync.py (三条时序链新鲜度闸门) 与 measure_k.m (时域/频域/包络三估计器互证)
- sim-rtl-flow/SKILL.md: 加入 sync_sim:* 步骤, 引用上述三个 skill
2026-07-27 10:39:53 +08:00

218 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: rtl-ip-replace
description: >
把 MATLAB/Simulink HDL Coder 生成的 RTL 二次加工:查找表(ROM)→ Vivado dist_mem_gen/blk_mem_gen IP、
RAM(SimpleDualPortRAM_generic 等)→ blk_mem_gen IP、CORDIC → Xilinx CORDIC IP、FIFO(reFIFO 等)→
fifo_generator IP、滤波器(**仅固定抽头**的 FIR/抽取/内插;LMS 自适应、Farrow、运行时参考抽头
的相关器不在替换范畴)→ fir_compiler / cic_compiler IP;改写后的 RTL 只存放在
固定目录 vivado/rtl_ip/<model>/(含 PROVENANCE.log 溯源与 ip/ 子目录),每次重跑先整体备份到
backup_<时间戳>/(含上次原始 RTL src_orig/ 与上次改写后 RTL,作为增量基线);与原始生成 RTL 做
cycle-true 双 DUT 对比仿真,PASS 后才作为 vivado 综合输入,vivado 工程绝不允许直接引用 matlab hdlsrc。
触发词:IP替换、ROM IP、RAM IP、CORDIC IP、FIFO IP、滤波器 IP、FIR IP、查找表替换、rtl_ip、
cycle-true 对比、二次修改rtl、makehdl 后处理、增量替换。
每次 regen_all/makehdl 重新生成 RTL 后都要重跑本流程(默认走增量)。适用于任何 HDL Coder→Vivado 工程。
---
# rtl-ip-replace — HDL Coder RTL 的 Vivado IP 化 + cycle-true 验证
> 系统级 skill,适用于任何 HDL Coder→Vivado 工程。
> 文中的模型名、模块名一律是**占位示例**;各工程的需求规格、实测清单与"暂不实施"名单,
> 记在该工程自己的任务书与 Notes 下,不写进本 skill。
## 〇、安装位置与环境依赖
脚本位于 `~/.claude/skills/rtl-ip-replace/scripts/`
(Windows:`%USERPROFILE%\.claude\skills\rtl-ip-replace\scripts\`)。下文的 `scripts/` 均指此处。
**Vivado 路径来自环境变量,不写死**:`gen_cmp_tb.py` 生成 `run_cmp.bat` 时按
`VIVADO_PATH` → `XILINX_VIVADO` 的顺序取安装根,三种写法都接受——
安装根、其 `bin\`、或 `bin\vivado.bat`。都没设时回落到内置默认值并打印 WARNING。
换机器/换版本只需改环境变量。
**实施对象(`<model>`)不绑定任何默认值,必须由用户在每次调用时明确指定**
(如 "在 `<model>` 上实施");未指定时先问用户,不得自行猜测。
"查找表"指**固定内容**的表(sin、cos、log、atan 等常量表);
运行时写入的数据表不属于替换范围。
**滤波器替换范围(用户指令 2026-07-24)**:**只有固定抽头(常量系数)的滤波器才适用
fir_compiler/cic_compiler 替换**。以下一律**不替换**:
- Farrow/多项式内插器(系数是 mu 等运行时变量的多项式,如 TxPreOffset);
- LMS 等自适应滤波器(逐样本更新权值,如 EqCore);
- 参考抽头为运行时捕获序列的相关器(如 RxAlignCore 的 refdl 相关)。
扫描器只把**能识别到的**这类结构报为 `adaptive_fir_candidate`/`fir_block_candidate`(仅供盘点,
不进替换清单):抽头寄存器组 + 延迟线 + 乘法(EqCore 的 LMS 就是这样被认出来的);
Farrow(TxPreOffset)与相关器(RxAlignCore)没有抽头寄存器组,扫描器不一定报得出来——
**它们由本规则排除,不依赖扫描器识别**。`fir_to_ip.py` 只处理 `fir_coef_bank`(常量系数),
自适应类一律只打印 INVENTORY ONLY,不生成 IP。
**"暂不实施"名单**:工程可以把某些模型/子系统登记为暂不替换(典型:激励源、上游 OFDM 源),
名单写在该工程的任务书里,**待用户明示后才可对其运行本 skill**。本 skill 不内置任何名单。
**双向调整原则(用户指令 2026-07-23)**:替换不必单向迁就原 RTL——**允许修改 MATLAB/Simulink
源设计来适配 Vivado IP 核**(位宽、量化、接口节奏、算法结构),只要**仿真性能不受影响**
(收敛行为、MER 等系统指标不劣化,须用模型仿真复验)。位级逐拍等价做不到时
(典型:CORDIC 的内部舍入),按此原则改源头或按消费点契约放宽,并在 Task12/Result
文档显式记录差异及性能复验结论。
## ★ 强制目录规定(用户指令 2026-07-23,严格执行)
1. **固定目录**:脚本处理后的 RTL 一律存放于 `vivado/rtl_ip/<model>/`。
**Vivado 工程只允许使用该目录下的 RTL,绝对不允许直接引用 `matlab/**/hdlsrc/**` 的自动生成代码**
(对比仿真编译原始代码除外——那只进 xsim,不进综合)。
2. **溯源 log**:固定目录下必须有 `PROVENANCE.log`,写明本次修改所依据的原始 MATLAB RTL 的**绝对路径**、
每个源文件的 sha1/mtime(由 `ipflow_common.py` 自动生成维护)。
3. **IP 子目录**:所有生成的 Xilinx IP(.xci 及产物)放在固定目录的 `ip/` 子目录。
4. **备份先行**:每次重跑本 skill,第一步必须把固定目录现有全部内容移入 `backup_<YYYYMMDD_HHMMSS>/`
子目录,然后全新生成(`backup_outdir.py` 完成,backup_* 目录本身不重复嵌套)。
5. 接入综合前 lint:`.xpr` 中不得再有指向 hdlsrc 的**综合**源(检查:grep hdlsrc *.xpr)。
## 适用范围(HDL Coder 25.2 生成代码的习惯用法,实测归纳)
| 结构 | 生成代码特征 | 目标 IP | cycle-true 约束 |
|---|---|---|---|
| 常量查找表 (ROM) | `wire signed [W-1:0] <tbl> [0:D-1];` + D 条 `assign <tbl>[i] = W'sb...;` + 组合读 `<tbl>[expr]` | `dist_mem_gen`(默认,异步读,天然 cycle-true) | 零改动等价 |
| **isempty-init 表** ★ | 常量表 `P` + `T_1[i]=P[i]` 拷贝 generate + `T_reg_reg` 寄存器阵列(复位清 0,首个 `enb&&s` 拍整表载入常量,此后保持)+ `TMP = s ? T_1 : T` 组合 mux。**这是 13 万 FF 毒源的本体**(sinT/cosT 4096×16) | `dist_mem_gen` + 1 个 `__ipinited` 标志 FF | 读点改写:`T[e] → inited ? ROM[e] : 0`;`TMP[e] → (s\|\|inited) ? ROM[e] : 0`,逐拍等价(复现"载入前读 0") |
| RAM | `SimpleDualPortRAM_generic`(单时钟 SDP,enb 使能,**读旧值 read-first**,读延迟 1 拍) | `blk_mem_gen` SDP,写口 **Read-First**,无输出寄存器 | 碰撞语义必须 Read-First **且 `CONFIG.Assume_Synchronous_Clk true`(C_COMMON_CLK=1)——异步双钟假设下模型不保证碰撞读旧值,实测 dlock 提交后 tx_al 发散 17640 拍** |
| CORDIC | 内联 FSM:`cx/cy/cz` 迭代寄存器 + `atanT` 表,多拍出一次结果 | `cordic`(arctan/rotate) | **无法逐拍等价**;契约=消费点等价:IP 延迟 ≤ 原 FSM 出结果拍数,IP 早出的结果寄存保持到原提交拍再输出 |
| FIFO | `reFIFO` 等:reg 数组 + wr/rd 指针 | `fifo_generator` | 读延迟(standard=1拍/FWFT=0拍)与 empty/full 翻转拍必须逐拍一致;TB 同时比数据和 flag |
| 滤波器(**仅固定抽头**) | **常量**抽头数组 + delay line + 乘加链(成型/内插 FIR 等) | 定系数→`fir_compiler`;大抽取/内插比→`cic_compiler`。**抽头非常量(LMS/Farrow/运行时参考)不在替换范畴** | 系数整数值逐个相同、Full Precision 输出 + 外部按原 RTL 方式截位、延迟差用固定移位寄存器补平后**逐拍 0 失配**;`tvalid` 与原输出有效拍一致 |
注意:`reg` 数组小表(w_out/refdl/t0 等 5~18 深)是真正的运行时数据,**保留原状**,不替换。
## 流程(0~5 步,顺序执行)
设 `HDLSRC` = 该工程 HDL Coder 的输出目录(形如 `matlab/<工程>/hdlsrc/<model>`),
`OUT = vivado/rtl_ip/<model>`(固定目录,见上节规定),脚本在 `~/.claude/skills/rtl-ip-replace/scripts/`。
### 0. 备份(强制第一步)
```
python scripts/backup_outdir.py <OUT>
```
备份后的 `backup_<ts>/` 同时含**上次的原始 MATLAB RTL**(`src_orig/`)与**上次改写后的 RTL**,
即下一步增量判定的基线。
### 0.5 增量判定(Task12 规定 13~16;要全量复核时加 `--full`)
```
python scripts/incr_plan.py plan <HDLSRC> <OUT> --apply
```
- 把新 hdlsrc 与基线 `src_orig/` 逐文件比 sha1:`same` 的文件**直接取回上次改写后的 .v**;
- 打印 `ALL MATLAB sources unchanged` 时,**第 2 步(transform)整步可跳过**;
- 同时把本次源码快照写进 `<OUT>/src_orig/`(下次的基线),计划落 `<OUT>/incr_plan.json`。
### 1. 扫描盘点
```
python scripts/rtl_ip_scan.py <HDLSRC> <OUT>
```
manifest 落在 `<OUT>/manifest.json`。人工 review:const_rom 里"唯一读点"若其实是
`T_1[gv]=P[gv]` 的 generate 拷贝,该表属于 isempty-init 惯用法,**必须走 initrom_to_ip.py,
不得用 rom_to_ip.py 直接改**(会把 ROM 塞进 genvar 循环,语义错误)。
### 2. 生成替换 RTL(另存到 <OUT>,不动原始文件)
```
python scripts/initrom_to_ip.py <HDLSRC> <OUT> # isempty-init 表(sinT/cosT/atanT)
python scripts/rom_to_ip.py <HDLSRC> <OUT> # 剩余纯常量 ROM
python scripts/ram_to_ip.py <HDLSRC> <OUT> # SimpleDualPortRAM → blk_mem_gen
python scripts/fir_to_ip.py <HDLSRC> <OUT> [--rate N ...] # 定系数 FIR → .coe + fir_compiler 登记
# + FIR_PROPOSAL_*.md(不改 RTL)
#(CORDIC/FIFO/滤波器手术引导式手改 <OUT> 里的副本,见下方契约;所需 IP 手工追加进 ip_manifest)
python scripts/namespace_ip.py <OUT> # 最后统一加 _IP 后缀(幂等)
```
产物:改写后 `*.v`、去重 `*.coe`、`ip_manifest.json/.tcl`、`PROVENANCE.log`、`src_orig/`(自动)。
### 2.5 增量结算(决定第 3、4 步能不能省)
```
python scripts/incr_plan.py finalize <HDLSRC> <OUT>
```
生成 `TRANSFORM.diff`(src_orig → 改写后的 removed/added 行,行号已归一化)并与基线比对,输出两个决策:
- `ip_reuse: YES` — `ip_manifest.tcl` + 全部 `.coe` 与基线逐字节相同,基线 `ip/` 已复制回来,**跳过第 3 步**;
- `sim_inherit: YES` — 还满足 `TRANSFORM.diff` 与基线相同、scripts_sha1 未变、文件集未增删、
基线 `cmp_result.txt` 为 PASS ⇒ **跳过第 4 步**,PASS 继承并写入 `PROVENANCE.log`。
任一条不满足即打印 blocker 并要求实跑。**接入综合出板前的最后一次必须 `--full` 全量复核。**
### 3. 生成 Vivado IP(固定目录的 ip/ 子目录) — `ip_reuse: YES` 时跳过
```
vivado -mode batch -source scripts/gen_ip.tcl -tclargs <OUT>/ip_manifest.tcl <OUT>/ip
```
### 4. cycle-true 对比仿真(闸门,不 PASS 不准上综合) — `sim_inherit: YES` 时跳过
```
python scripts/gen_cmp_tb.py <top_module> <HDLSRC> --rtl-ip <OUT>
<OUT>/cmp/run_cmp.bat
```
- 双 DUT:原始 `<top>`(从 HDLSRC 编译,仅用于仿真)vs `<top>_IP`(从 <OUT> 编译);
复位释放后逐拍比较全部输出;CORDIC 输出用 `--compare-at OUT=done信号` 限定提交拍。
- 激励:默认 LFSR;控制密集顶层优先复用 HDL Coder `<top>_tb.v` 向量。
- xelab 库:`-L dist_mem_gen_v8_0 -L blk_mem_gen_v8_4 -L fifo_generator_v13_2 -L cordic_v6_0_14
-L fir_compiler_v7_2_11 -L cic_compiler_v4_0_13 -L xbip_utils_v3_0_9 -L unisims_ver`
+ IP 仿真网表(`--ip-simdir <OUT>/ip`)。
- 判据:`cmp_result.txt` 首行 **PASS(0 失配)**;FAIL 时禁止接入综合。
### 5. 接入综合
- 工程源:`<OUT>/*.v` + `<OUT>/ip/**/*.xci`,顶层实例化 `<top>_IP`;
**移除工程中所有 hdlsrc 综合源引用**(规定 §1/§5)。
- **共享模块名检查(实测踩坑)**:移除被转换模型的 hdlsrc 源前,grep 其余未转换模型是否
实例化**同名**模块(HDL Coder 对多个模型会生成同名的通用模块,如 `SimpleDualPortRAM_generic`,
工程里可能一直借用另一个模型目录下的那一份)——是则把**该模型自己目录下的同名 .v**
加进工程,否则综合报 `module not found`。
- isempty-init 表 IP 化后,`vivado/constrs/mc_paths.tcl` 的 ROM 多周期豁免段(`MC rom=`)删除。
- 重新综合前全清 `.runs/.cache`(残留 `__synthesis_is_running__` 标记会死锁调度)。
## CORDIC 替换契约(引导式)
1. 圈出迭代锥:`cx/cy/cz` 寄存器、`atanT`(isempty-init 表)、迭代计数、结果提交点
(本项目:相关峰种子 candR/candI → phi 种子,1 iter/4 clk)。
2. 包成边界模块 `cordic_kernel_IP(clk, en, start, x_in, y_in, done, phase_out)`,内部用
`cordic` IP(Translate/arctan,流水延迟 L ≤ 原 FSM 提交拍数)。
3. `done`/`phase_out` 按原 FSM 提交拍对齐(早出结果寄存保持)→ 外部逐拍等价。
4. TB 在提交拍断言相等;量化差须靠 IP 位宽/舍入配置调平至 0 或显式记录 ≤1 LSB。
## FIFO 替换契约(引导式)
1. 原模块 `rd_en` 后 1 拍出数 = **Standard**;当拍出数 = **FWFT**。
2. `empty/full/count` 翻转拍逐拍对比;IP 的 `valid/underflow` 不接原逻辑。
3. 深度向上取 2 的幂;复位保持 ≥ 双方要求最大值,双方 ready 后才开始比对。
## 滤波器替换契约(引导式,需求 4b)
1. **先分类(判定门槛就是"抽头是不是常量")**:
- **抽头固定**(成型/抽取/内插/信道化,系数是模型里的常量)→ `fir_compiler`(系数进 .coe);
抽取/内插比很大的级联积分梳状结构 → `cic_compiler`。**只有这一类进替换清单。**
- **抽头非常量** —— LMS/自适应均衡器(EqCore)、Farrow/多项式内插器(TxPreOffset)、
参考抽头为运行时捕获序列的相关器(RxAlignCore)—— **一律不替换,保留原 RTL**。
不要用 Reloadable Coefficients 去凑:那是低速重载通道(一整帧系数经 AXI-Stream 灌入),
表达不了逐样本更新的权值,也表达不了每拍随 mu 变化的 Farrow 系数;硬套只会既破坏
cycle-true 又拿不到面积收益。
(`fir_to_ip.py` 的 `--reload` 只用于**多组固定系数之间切换**的场合,不是自适应的出口。)
2. 系数取 HDL Coder 常量表的**定点整数值**原样写 .coe,不让 IP 再量化。
3. 输出用 **Full Precision**,截位放到 IP 外面按原 RTL 的移位/饱和方式做,**不开 IP 内建 rounding**。
4. 延迟对齐:IP 延迟 L 与原 L0 之差用固定移位寄存器补平;L > L0 只能按双向调整原则改 Simulink 源,
并复验 MER/收敛不劣化。
5. 节奏:输入非满拍时用 `s_axis_data_tvalid`;`m_axis_data_tvalid` 翻转拍与原输出有效拍逐拍一致。
6. 验收:对齐后 TB 逐拍 0 失配。
**IP 侧硬事实(2026-07-24 在本机 Vivado 2018.1 实测,已写进 gen_ip.tcl / fir_to_ip.py)**:
- `fir_compiler` 7.2 合法区间:`Coefficient_Width∈[8,49]`、`Data_Width∈[2,47]`、
`Data_Fractional_Bits∈[0,16]`。HDL Coder 的 full-precision 抽头(如 OFDM 源的 51 位)
**超限**,`fir_to_ip.py` 会拒绝登记该 IP 并写方案,要求先按双向调整原则回 Simulink 重新量化。
- **二进制点一律填 0**(`Coefficient_Fractional_Bits=0`、`Data_Fractional_Bits=0`):core 做整数
MAC,小数位只影响 GUI;填 0 与生成 RTL 的整数语义一致,又避开上面的 0..16 限制。
- 定拍关键项:`RateSpecification=Input_Sample_Period` + `SamplePeriod=1`(禁折叠)、
`Has_ACLKEN=true`(接 `enb`)、`M_DATA_Has_TREADY=false`、`S_DATA_Has_FIFO=false`。
- `cic_compiler` 4.0 的同名参数**取值不同**:`RateSpecification` 只认
`Frequency_Specification | Sample_Period`(**没有** `Input_Sample_Period`,填错直接报
IP_Flow 19-3461)。
- 对比仿真需要 `-L fir_compiler_v7_2_11 -L cic_compiler_v4_0_13`(gen_cmp_tb.py 已加)。
## 新工程接入本 skill
1. 在工程任务书里写明**实施对象清单**与**"暂不实施"名单**(§适用范围);
2. 跑一次全量扫描(第 1 步),把结果作为**基线清单**记进工程 Notes——
各类结构各几处(isempty-init 表 / RAM / CORDIC / FIFO / 定系数滤波器),
以及被报为 `adaptive_fir_candidate` 但按范围规定**保留 RTL** 的那些;
3. 确认 `VIVADO_PATH` 已指向本机 Vivado(§〇),否则 `run_cmp.bat` 跑不起来;
4. 首次运行没有 `src_orig/`/`TRANSFORM.diff` 时会自动退回全量,并顺带把增量基线补齐;
此后每次 `makehdl` 之后仍从第 0 步(备份)重跑,但走增量。
**典型收益点**:isempty-init 常量表(sin/cos 一类)在 HDL Coder 里会展开成 FF 阵列 + 巨型读 mux,
数万到十几万 FF,是时序与布局的头号毒源;IP 化从根上解决。接入后优先扫这一类。