Files
skills-win/rtl-ip-replace/SKILL.md
T
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

16 KiB
Raw Blame History

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,严格执行)

  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(另存到 ,不动原始文件)

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)。
  • 判据: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 化从根上解决。接入后优先扫这一类。