- 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
238 lines
12 KiB
Markdown
238 lines
12 KiB
Markdown
---
|
||
name: simulink-rtl-redev
|
||
description: >
|
||
改动原生模型(xxx_h.slx)之后,如何保证仿真模型(xxx_hs.slx)同步、仿真结论仍然算数。
|
||
典型触发场景:Vivado 综合时序不满足要改流水、硬件调试发现问题要改 RTL、定点位宽调整。
|
||
核心:生成脚本不会把原生核的改动翻译成 m 代码,只改一侧 = RTL 已变而仿真还在验证旧行为。
|
||
提供两个工具:同步新鲜度闸门 check_sync.py(三条时序链,退出码即结论)、
|
||
延迟实测 measure_k.m(时域/频域/包络三个估计器互证,不一致返回 NaN 而非猜测值)。
|
||
另含改动分类、验证三步、环内/环外加流水的差别、六条禁令。
|
||
触发词:改了原生模型、同步仿真模型、xxx_h 和 xxx_hs 不一致、时序违例改流水、
|
||
硬件调试改RTL、仿真模型过时、等价闸门、cycle-true 同步、check_sync、measure_k、
|
||
测延迟、K 是多少、加流水延迟多少拍、原生模型 仿真模型 双轨、改完怎么验、
|
||
makehdl 之前要做什么。
|
||
规范全文见工程 Notes/Task16-3.md。适用于任何"原生模型+仿真模型"双轨结构的工程。
|
||
---
|
||
|
||
# simulink-rtl-redev — 原生模型改动后的仿真模型同步
|
||
|
||
## 这个 skill 解决什么
|
||
|
||
两个模型描述同一段算法:
|
||
|
||
| 记号 | 名称 | 内容 | 用途 |
|
||
|---|---|---|---|
|
||
| `xxx_h.slx` | 原生模型(-e) | 目标模块全部用可综合原生块搭建 | **生成 RTL** |
|
||
| `xxx_hs.slx` | 仿真模型(-x/-q) | 由原生模型派生,跑不动的**原生核**换成等价的 **m 实现** | **跑场景仿真** |
|
||
|
||
**核心原则:改了原生模型,必须同步改仿真模型,并重新验证两者行为一致。**
|
||
|
||
原因:从原生模型派生仿真模型的脚本只做"摘掉原生核、换上 m 实现、接线",
|
||
**它不会把原生核的改动翻译成 m 代码**。只改原生模型再重跑生成脚本,
|
||
仿真模型不会跟着变——它只是把同一份未改动的 m 实现重新包一遍。
|
||
|
||
后果:**RTL 已经变了,而仿真还在验证旧行为**。板上出问题时仿真给不出线索,
|
||
因为它测的根本不是同一个设计。
|
||
|
||
## 什么时候需要手工同步
|
||
|
||
只看改动落在哪里:
|
||
|
||
| 改动位置 | 是否需要手工同步 m 实现 |
|
||
|---|---|
|
||
| **原生核之内** | **需要**。这是两套实现的重叠区 |
|
||
| 原生核之外(其余链路) | 不需要。生成脚本原样继承,重生成即可 |
|
||
|
||
## Agent 路径:先跑闸门
|
||
|
||
任何时候怀疑"仿真结论还算不算数",第一件事是跑同步新鲜度闸门。
|
||
|
||
### 一次性:写 manifest
|
||
|
||
在工程里建一份 manifest(建议放 `Notes/sync_manifest_<model>.json`),
|
||
声明这个模型的源与派生产物。**`root` 相对 manifest 所在目录**,其余路径相对 `root`:
|
||
|
||
```json
|
||
{
|
||
"model": "<model>",
|
||
"root": "..",
|
||
|
||
"native_model": "matlab/<proj>/xxx_h.slx",
|
||
"build_scripts": ["matlab/<proj>/build_core.m"],
|
||
"gen_scripts": ["matlab/<proj>/make_sim.m"],
|
||
"mcode_impl": ["matlab/<proj>/core_mcode.m"],
|
||
"runtime_deps": ["matlab/<proj>/pscore.m", "matlab/<proj>/coef.mat"],
|
||
|
||
"sim_model": "matlab/<proj>/xxx_hs.slx",
|
||
"rtl_source_model": "matlab/<proj>/xxx_h_<Mod>_E.slx",
|
||
|
||
"gate_result": "Notes/gate_<model>.txt",
|
||
"gate_tools": ["matlab/<proj>/gate.m", "matlab/<proj>/cmp_core.m"]
|
||
}
|
||
```
|
||
|
||
`runtime_deps` 必填**运行时才从路径解析的 .m** 与运行时读取的数据文件
|
||
(`.mat`、系数表、数据字典)。改它们时模型本身一个字节都不变,漏列等于没查。
|
||
`rtl_source_model` 与 `gate_tools` 可省(省了就不查对应项)。
|
||
|
||
### 每次改完模型:跑闸门
|
||
|
||
```
|
||
cd <工程根>
|
||
python "%USERPROFILE%\.claude\skills\simulink-rtl-redev\check_sync.py" Notes\sync_manifest_<model>.json
|
||
```
|
||
|
||
Linux/macOS 把路径换成 `~/.claude/skills/simulink-rtl-redev/check_sync.py`。
|
||
|
||
**退出码即结论**:`0` 通过,`1` 有过时产物,`2` manifest 有问题(路径不存在等)。
|
||
可直接串进脚本做前置门禁。
|
||
|
||
选项:`--quiet` 不打印文件清单,`--json` 输出机器可读结果。
|
||
|
||
它查三条时序链:
|
||
|
||
```
|
||
链 1(派生新鲜):max(源文件) < min(派生产物)
|
||
链 2(闸门新鲜):max(派生产物) < 闸门结果
|
||
链 3(工具新鲜):max(闸门工具) < 闸门结果
|
||
```
|
||
|
||
**链 1 是最容易漏的那条。** 只查"闸门晚于全部源文件"会放行下面这种情况:
|
||
闸门确实在改完源之后跑的,但仿真模型停留在改动之前。本工具在真实工程上正是这样
|
||
抓出了一个已存在的漂移——链 2、链 3 都 PASS,只有链 1 FAIL。
|
||
|
||
## 同步流程
|
||
|
||
```
|
||
[1] 改原生实现(改构建脚本,不直接手改 .slx)
|
||
|
|
||
[2] 把同样的改动落到 m 实现 <-- 漏掉这步是本 skill 存在的理由
|
||
|
|
||
[3] 重新生成仿真模型
|
||
|
|
||
[4] 若改动带来延迟变化:先实测新增拍数 K
|
||
|
|
||
[5] 跑等价闸门 --> 原生核 与 m 实现 是否一致
|
||
|
|
||
[6] 跑短程冒烟 --> 链路是否还通
|
||
|
|
||
[7] 跑全场景全长 --> 对照基线,指标是否达标
|
||
|
|
||
[8] 从原生模型生成 RTL
|
||
```
|
||
|
||
任一步不过,回到 `[1]` 重走,不要从中间插入——仿真模型一旦重生成,后面的结果全部作废。
|
||
|
||
**`[4]` 必须在 `[5]` 之前**:延迟变了,闸门比对要带偏移,K 没测出来这一步做不了。
|
||
|
||
跑完 `[3]` 之后、进 `[5]` 之前,先用 `check_sync.py` 确认新鲜度,能省掉一次白跑的闸门。
|
||
|
||
## 三类改动的差别
|
||
|
||
| 类别 | 例 | m 实现要改吗 | 延迟 | 场景判据 |
|
||
|---|---|---|---|---|
|
||
| **不改延迟** | 资源共享、块重构、常量折叠 | 通常不用 | 不变 | 与基线**逐位相同** |
|
||
| **改延迟** | 插流水寄存器 | 补等量延迟 | +K | 吸收 K 后与基线**逐位相同** |
|
||
| **改定点** | 位宽、二进制点、饱和/舍入 | **必须改** | 不变 | 行为已变,**重新定性并建立新基线** |
|
||
|
||
- **延迟必须实测,不能按设计意图推算。** 用 `measure_k.m`(下节),至少两种互不依赖的方法测出一致值。
|
||
- **在反馈环内插流水,代价远大于环外。** 环外只是一个可被注入侧参数吸收的常数延迟;
|
||
环内会改变环路群延迟,必须重新定标环路参数(步长、移位、门限),并先在单元台验证
|
||
阶跃响应再上全链。**能搬到环外就搬到环外。**
|
||
|
||
## 实测新增延迟 K:`measure_k.m`
|
||
|
||
流程 `[4]`。B 类(加流水)改动必须先测 K 才能进 `[5]`——闸门的判据本身要用 K 作偏移。
|
||
|
||
```matlab
|
||
addpath(fullfile(getenv('USERPROFILE'),'.claude','skills','simulink-rtl-redev'));
|
||
[K, R] = measure_k(ref, dut); % ref/dut 为实数或复数 IQ 向量,同采样率
|
||
```
|
||
|
||
`ref` 是改动前(或参考抽头)的信号,`dut` 是改动后(滞后 K 拍)的信号。
|
||
返回 `K` = 认定的整数延迟,**方法不一致时返回 `NaN`**,不给猜测值。
|
||
`R` 里有各方法的分项结果。可选参数:`'MaxLag'`(4096)、`'OnsetTol'`(1e-3)、
|
||
`'FracTol'`(0.05)、`'Verbose'`(true)。**只用基础 MATLAB**,不依赖 Signal Processing Toolbox。
|
||
|
||
三个估计器,前两个域完全不同、始终可用,第三个仅在信号有静默前导时适用:
|
||
|
||
| 方法 | 域 | 说明 |
|
||
|---|---|---|
|
||
| 1 互相关 argmax | 时域 | 整数 |
|
||
| 2 互谱相位斜率 | **频域** | 分数;纯延迟使 `angle(Y·conj(X))` 对频率成直线,斜率即 −K |
|
||
| 3 起始点差 | 幅度包络 | 整数;阈值取各自**峰值**的比例,故 DUT 的噪声底不会误触发;连续激励信号判 `n/a` 而非失败 |
|
||
|
||
判据:方法 1 与 2 必须一致;方法 3 适用时也必须一致;插值峰距整数不超过 `FracTol`。
|
||
|
||
**为什么非要两种。** 实测:两路**完全无关**的信号,相关器照样给出 `K = 241`
|
||
(|rho| 仅 0.0198 的假峰)——单靠它会把一个纯噪声峰当成延迟。方法 2 给出 −26.350,
|
||
两者互不相认,工具才判 FAIL。这就是"至少两种互不依赖的方法"的全部理由。
|
||
|
||
实测过的六种情形:
|
||
|
||
| 情形 | 结果 |
|
||
|---|---|
|
||
| 干净整数延迟 40,复数 IQ | PASS,3 法一致 |
|
||
| 增益 0.37× + 全程噪声底 | PASS,2 法一致(方法 3 自动判 n/a) |
|
||
| 连续激励无静默段,延迟 17 | PASS,2 法一致 |
|
||
| **分数延迟 12.5 拍** | **FAIL**:方法 1 给 13、方法 2 给 12.500,且插值峰偏离整数 0.5 |
|
||
| **两路无关信号** | **FAIL**:241 vs −26.350 |
|
||
| 真延迟 40 + 3 阶非线性失真 | PASS,2 法一致(\|rho\| 0.989) |
|
||
|
||
分数延迟那一条尤其要留意:它说明改动**没有引入干净的整数流水延迟**(碰到了分数内插/重采样
|
||
路径)。这时不能四舍五入接着走,要回去查改的是不是环内。
|
||
|
||
## 等价闸门的语义(极易搞错)
|
||
|
||
闸门比对的是**原生核与 m 实现这两套实现**,不是"新行为与旧行为"。因此:
|
||
|
||
- 闸门对**所有类别**的改动都必须跑,判据都是"一致";
|
||
- 即使行为相对上一版变了,只要两侧按同样意图改完,**两套实现对新行为的描述仍必须一致**;
|
||
- 改完两侧仍不一致,说明两边理解有分歧——这正是闸门要抓的。
|
||
|
||
**不要以"行为变了"为由跳过闸门。** 跳过它就等于放弃了对双实现是否同步的唯一检查。
|
||
|
||
闸门的局限:它只证明"在所用激励下一致",不证明全状态空间等价。
|
||
状态机边界、饱和、复位时序这类角落必须补定向用例,不能指望随机激励覆盖到。
|
||
|
||
## 六条禁令
|
||
|
||
| # | 规矩 | 理由 |
|
||
|---|---|---|
|
||
| 1 | 仿真模型是**产物**,不能手改;要改就改生成脚本再重生成 | 手改必被下次重生成冲掉,git 上无法溯源 |
|
||
| 2 | 只改一侧的改动**视为未完成**,不得进入验证 | 两套实现已分叉 |
|
||
| 3 | 没跑过闸门,**不得跑场景仿真,也不得生成 RTL** | 前者测的可能是旧设计,后者错误直接进硅 |
|
||
| 4 | 旧结果**永不覆盖**,每次运行带唯一标签、落唯一目录 | 失败记录是"改动前后对比"的另一半;陈旧编译缓存会静默复现旧结果 |
|
||
| 5 | 判据不得为了通过而修改 | 一条带机理分析的 FAIL,价值高于一个来路不明的 PASS |
|
||
| 6 | 时序违例优先改模型,**禁止**用多周期豁免放宽慢角富余路径 | 布线器会吃满被放松的预算,表现为"报告通过、板上异常" |
|
||
|
||
## Gotchas
|
||
|
||
- **时间戳只是权宜之计。** `copy`/`touch`/版本签出都会扰动 mtime。
|
||
`check_sync.py` 因此只能作自查,不是证明。更可靠的做法是让生成脚本与闸门自动记录
|
||
所用源码的校验和,运行前比对不符即拒绝开跑——那属于工具建设,本 skill 不含。
|
||
- **"闸门比全部源文件新"不等于没问题。** 这正是链 1 存在的理由,见上文。
|
||
- **`runtime_deps` 漏列 = 没查。** 运行时解析的 .m 和 `.mat`/系数表改动时,
|
||
模型校验和纹丝不动。这类依赖必须显式声明,工具无法自动发现
|
||
(`load('x.mat')` 这种字符串依赖追不到)。
|
||
- **manifest 里每个声明的路径都必须存在**,否则 exit 2。这是故意的:
|
||
路径写错而静默跳过,比报错危险得多。
|
||
- **exit 2 与 exit 1 含义不同**:2 是 manifest 坏了(你的配置问题),
|
||
1 是真的有过时产物(你的工程问题)。串脚本时别把两者混为一谈。
|
||
|
||
## Troubleshooting
|
||
|
||
| 症状 | 原因 / 处置 |
|
||
|---|---|
|
||
| `ERROR: manifest refers to something that is missing -> <key>: <path>` | 该路径不存在。exit 2。检查 `root` 与相对路径;`root` 是相对 manifest 所在目录的 |
|
||
| 链 1 FAIL | 派生产物早于源。**先**把原生核改动落到 m 实现,**再**重生成,**再**重跑闸门。只重生成不同步 m 实现无效 |
|
||
| 链 2 FAIL | 闸门跑在当前派生产物之前 —— 重跑闸门 |
|
||
| 链 3 FAIL | 闸门工具在记录的通过之后改过 —— 重跑闸门 |
|
||
| 三条链全 PASS 但板上仍与仿真不符 | 新鲜度没问题,问题在别处:查闸门覆盖(定向用例是否覆盖到出问题的状态),或查 `runtime_deps` 是否漏列 |
|
||
| `ERROR: manifest declares no sources / no derived products` | manifest 缺 `native_model` 一类的键,或值是空列表 |
|
||
|
||
## 规范全文
|
||
|
||
本 skill 是 `Notes/Task16-3.md` 的可执行化。规范原文(含制定过程与十轮评审纠正的
|
||
逻辑错误)见该文档;本 skill 只承载执行路径与工具。
|