- 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
16 KiB
name, description
| name | description |
|---|---|
| rtl-ip-replace | 把 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,严格执行)
- 固定目录:脚本处理后的 RTL 一律存放于
vivado/rtl_ip/<model>/。 Vivado 工程只允许使用该目录下的 RTL,绝对不允许直接引用matlab/**/hdlsrc/**的自动生成代码 (对比仿真编译原始代码除外——那只进 xsim,不进综合)。 - 溯源 log:固定目录下必须有
PROVENANCE.log,写明本次修改所依据的原始 MATLAB RTL 的绝对路径、 每个源文件的 sha1/mtime(由ipflow_common.py自动生成维护)。 - IP 子目录:所有生成的 Xilinx IP(.xci 及产物)放在固定目录的
ip/子目录。 - 备份先行:每次重跑本 skill,第一步必须把固定目录现有全部内容移入
backup_<YYYYMMDD_HHMMSS>/子目录,然后全新生成(backup_outdir.py完成,backup_* 目录本身不重复嵌套)。 - 接入综合前 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(另存到 ,不动原始文件)
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(从 编译); 复位释放后逐拍比较全部输出;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)。
- 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 替换契约(引导式)
- 圈出迭代锥:
cx/cy/cz寄存器、atanT(isempty-init 表)、迭代计数、结果提交点 (本项目:相关峰种子 candR/candI → phi 种子,1 iter/4 clk)。 - 包成边界模块
cordic_kernel_IP(clk, en, start, x_in, y_in, done, phase_out),内部用cordicIP(Translate/arctan,流水延迟 L ≤ 原 FSM 提交拍数)。 done/phase_out按原 FSM 提交拍对齐(早出结果寄存保持)→ 外部逐拍等价。- TB 在提交拍断言相等;量化差须靠 IP 位宽/舍入配置调平至 0 或显式记录 ≤1 LSB。
FIFO 替换契约(引导式)
- 原模块
rd_en后 1 拍出数 = Standard;当拍出数 = FWFT。 empty/full/count翻转拍逐拍对比;IP 的valid/underflow不接原逻辑。- 深度向上取 2 的幂;复位保持 ≥ 双方要求最大值,双方 ready 后才开始比对。
滤波器替换契约(引导式,需求 4b)
- 先分类(判定门槛就是"抽头是不是常量"):
- 抽头固定(成型/抽取/内插/信道化,系数是模型里的常量)→
fir_compiler(系数进 .coe); 抽取/内插比很大的级联积分梳状结构 →cic_compiler。只有这一类进替换清单。 - 抽头非常量 —— LMS/自适应均衡器(EqCore)、Farrow/多项式内插器(TxPreOffset)、
参考抽头为运行时捕获序列的相关器(RxAlignCore)—— 一律不替换,保留原 RTL。
不要用 Reloadable Coefficients 去凑:那是低速重载通道(一整帧系数经 AXI-Stream 灌入),
表达不了逐样本更新的权值,也表达不了每拍随 mu 变化的 Farrow 系数;硬套只会既破坏
cycle-true 又拿不到面积收益。
(
fir_to_ip.py的--reload只用于多组固定系数之间切换的场合,不是自适应的出口。)
- 抽头固定(成型/抽取/内插/信道化,系数是模型里的常量)→
- 系数取 HDL Coder 常量表的定点整数值原样写 .coe,不让 IP 再量化。
- 输出用 Full Precision,截位放到 IP 外面按原 RTL 的移位/饱和方式做,不开 IP 内建 rounding。
- 延迟对齐:IP 延迟 L 与原 L0 之差用固定移位寄存器补平;L > L0 只能按双向调整原则改 Simulink 源, 并复验 MER/收敛不劣化。
- 节奏:输入非满拍时用
s_axis_data_tvalid;m_axis_data_tvalid翻转拍与原输出有效拍逐拍一致。 - 验收:对齐后 TB 逐拍 0 失配。
IP 侧硬事实(2026-07-24 在本机 Vivado 2018.1 实测,已写进 gen_ip.tcl / fir_to_ip.py):
fir_compiler7.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_compiler4.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 步),把结果作为基线清单记进工程 Notes——
各类结构各几处(isempty-init 表 / RAM / CORDIC / FIFO / 定系数滤波器),
以及被报为
adaptive_fir_candidate但按范围规定保留 RTL 的那些; - 确认
VIVADO_PATH已指向本机 Vivado(§〇),否则run_cmp.bat跑不起来; - 首次运行没有
src_orig//TRANSFORM.diff时会自动退回全量,并顺带把增量基线补齐; 此后每次makehdl之后仍从第 0 步(备份)重跑,但走增量。
典型收益点:isempty-init 常量表(sin/cos 一类)在 HDL Coder 里会展开成 FF 阵列 + 巨型读 mux, 数万到十几万 FF,是时序与布局的头号毒源;IP 化从根上解决。接入后优先扫这一类。