--- 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//(含 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。 换机器/换版本只需改环境变量。 **实施对象(``)不绑定任何默认值,必须由用户在每次调用时明确指定** (如 "在 `` 上实施");未指定时先问用户,不得自行猜测。 "查找表"指**固定内容**的表(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//`。 **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_/` 子目录,然后全新生成(`backup_outdir.py` 完成,backup_* 目录本身不重复嵌套)。 5. 接入综合前 lint:`.xpr` 中不得再有指向 hdlsrc 的**综合**源(检查:grep hdlsrc *.xpr)。 ## 适用范围(HDL Coder 25.2 生成代码的习惯用法,实测归纳) | 结构 | 生成代码特征 | 目标 IP | cycle-true 约束 | |---|---|---|---| | 常量查找表 (ROM) | `wire signed [W-1:0] [0:D-1];` + D 条 `assign [i] = W'sb...;` + 组合读 `[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/`), `OUT = vivado/rtl_ip/`(固定目录,见上节规定),脚本在 `~/.claude/skills/rtl-ip-replace/scripts/`。 ### 0. 备份(强制第一步) ``` python scripts/backup_outdir.py ``` 备份后的 `backup_/` 同时含**上次的原始 MATLAB RTL**(`src_orig/`)与**上次改写后的 RTL**, 即下一步增量判定的基线。 ### 0.5 增量判定(Task12 规定 13~16;要全量复核时加 `--full`) ``` python scripts/incr_plan.py plan --apply ``` - 把新 hdlsrc 与基线 `src_orig/` 逐文件比 sha1:`same` 的文件**直接取回上次改写后的 .v**; - 打印 `ALL MATLAB sources unchanged` 时,**第 2 步(transform)整步可跳过**; - 同时把本次源码快照写进 `/src_orig/`(下次的基线),计划落 `/incr_plan.json`。 ### 1. 扫描盘点 ``` python scripts/rtl_ip_scan.py ``` manifest 落在 `/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(另存到 ,不动原始文件) ``` python scripts/initrom_to_ip.py # isempty-init 表(sinT/cosT/atanT) python scripts/rom_to_ip.py # 剩余纯常量 ROM python scripts/ram_to_ip.py # SimpleDualPortRAM → blk_mem_gen python scripts/fir_to_ip.py [--rate N ...] # 定系数 FIR → .coe + fir_compiler 登记 # + FIR_PROPOSAL_*.md(不改 RTL) #(CORDIC/FIFO/滤波器手术引导式手改 里的副本,见下方契约;所需 IP 手工追加进 ip_manifest) python scripts/namespace_ip.py # 最后统一加 _IP 后缀(幂等) ``` 产物:改写后 `*.v`、去重 `*.coe`、`ip_manifest.json/.tcl`、`PROVENANCE.log`、`src_orig/`(自动)。 ### 2.5 增量结算(决定第 3、4 步能不能省) ``` python scripts/incr_plan.py finalize ``` 生成 `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 /ip_manifest.tcl /ip ``` ### 4. cycle-true 对比仿真(闸门,不 PASS 不准上综合) — `sim_inherit: YES` 时跳过 ``` python scripts/gen_cmp_tb.py --rtl-ip /cmp/run_cmp.bat ``` - 双 DUT:原始 ``(从 HDLSRC 编译,仅用于仿真)vs `_IP`(从 编译); 复位释放后逐拍比较全部输出;CORDIC 输出用 `--compare-at OUT=done信号` 限定提交拍。 - 激励:默认 LFSR;控制密集顶层优先复用 HDL Coder `_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 /ip`)。 - 判据:`cmp_result.txt` 首行 **PASS(0 失配)**;FAIL 时禁止接入综合。 ### 5. 接入综合 - 工程源:`/*.v` + `/ip/**/*.xci`,顶层实例化 `_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 化从根上解决。接入后优先扫这一类。