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
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
---
|
||||
name: simulink_rtl_dev
|
||||
description: >
|
||||
Simulink→RTL 的设计规矩与闸门:凡是要生成 RTL 的子系统,它及其所有下级模块**必须用能直接
|
||||
生成 RTL 的 Simulink 模块搭建,不允许用嵌入式 m 语言(MATLAB Function)实现**;确实非用 m 代码
|
||||
不可的,必须**指定具体的 Xilinx IP 来替代,并通过仿真确认 cycle-true**,再登记豁免。提供审计脚本
|
||||
(audit_hdl_blocks.m 盘点 RTL 子树里的 m 代码块)、豁免闸门(check_exemptions.py)、原生模块查库
|
||||
(hdl_block_search.m)、HDL 兼容性检查(check_hdl_support.m)与 m 代码→原生模块对照表。
|
||||
触发词:simulink 设计规范、要生成RTL的模块、MATLAB Function 能不能用、嵌入式m语言、HDL 子系统审计、
|
||||
m代码豁免、cycle-true 豁免、hdl_block_search、audit_hdl_blocks、makehdl 前检查、新建 Simulink 模块。
|
||||
适用于任何 Simulink→RTL 工程。IP 替换与 cycle-true 对比由 rtl-ip-replace skill 执行。
|
||||
---
|
||||
|
||||
# simulink_rtl_dev — 要生成 RTL 的 Simulink 设计规矩
|
||||
|
||||
> 系统级 skill,适用于任何 Simulink→RTL 工程。
|
||||
> 本文中的模型名、子系统名一律是**占位示例**;各工程的具体规格、豁免登记与实测基线,
|
||||
> 放在该工程自己的任务书与 Notes 下,不写进本 skill。
|
||||
|
||||
## 〇、本 skill 的安装位置
|
||||
|
||||
脚本位于:
|
||||
|
||||
```
|
||||
~/.claude/skills/simulink_rtl_dev/scripts/
|
||||
```
|
||||
|
||||
Windows 上即 `%USERPROFILE%\.claude\skills\simulink_rtl_dev\scripts\`。
|
||||
下文所有 `addpath` 与 `python` 调用都按此路径给出。
|
||||
|
||||
## 一、两条铁律
|
||||
|
||||
1. **在 Simulink 设计中,如果明确指定某个模块是要生成 RTL 的,那么这个模块及下属的所有子模块,
|
||||
原则上都不允许使用嵌入式 m 语言的模块来实现,必须使用可以直接生成 RTL 的 Simulink 模块来搭建。**
|
||||
2. **如果一定要使用嵌入式 m 语言来实现的模块,那么必须指定明确的 Xilinx 的 IP 模块来替代,
|
||||
并且在替换过程中一定要通过仿真来确认 cycle true。**
|
||||
|
||||
推论(本 skill 的执行口径):
|
||||
|
||||
- 铁律 1 的判定对象是**"要生成 RTL 的子系统"整棵子树**——`makehdl` 的目标以及它下面的一切,
|
||||
与之无关的仿真件(信道、PA 模型、激励源)不受此规矩约束。
|
||||
- 铁律 2 的"指定 Xilinx IP"不是口头指定:必须落到工程的豁免登记文件
|
||||
(建议 `Notes/<model>_mcode_exemptions.json`),写明**哪个 IP**、**覆盖范围**、
|
||||
**cycle-true 证据文件**(`rtl-ip-replace` 的 `cmp_result.txt`,首行须为 PASS)、批准人与日期。
|
||||
没有证据的豁免 = 违规。
|
||||
- 违规不是"警告",是**闸门**:`check_exemptions.py` 返回非 0 时不得 `makehdl`、不得进综合。
|
||||
|
||||
## 二、先确定"要生成 RTL 的子树"
|
||||
|
||||
按优先级:
|
||||
|
||||
1. 用户/任务书明确指定;
|
||||
2. 工程脚本里的 `makehdl` 调用 —— `grep -rn "makehdl" matlab/**/*.m`
|
||||
(形如 `makehdl('<model>/<HDL 顶层>','TargetLanguage','Verilog')`);
|
||||
3. 模型参数 `hdlget_param(model,'HDLSubsystem')`;
|
||||
4. 命名约定 `*_HDL` 子系统。
|
||||
|
||||
**不要**默认整个模型都是 RTL 目标。信道模型、功放模型、载波/延迟注入、激励源这类**纯仿真件**
|
||||
天生允许用 m 代码,把它们算进审计范围只会制造假违规。
|
||||
接入本 skill 时,先在工程任务书里把"RTL 子树"与"纯仿真件"两份清单写清楚。
|
||||
|
||||
## 三、流程 A:新建 / 修改一个要生成 RTL 的模块(默认路径)
|
||||
|
||||
1. **先查库,再动手**(MATLAB 里):
|
||||
```matlab
|
||||
addpath(fullfile(getenv('USERPROFILE'),'.claude','skills','simulink_rtl_dev','scripts'));
|
||||
hdl_block_search('lms') % 关键字搜 HDL Coder + DSP HDL + Wireless/Vision HDL 库
|
||||
```
|
||||
查到就必须用库里的模块;查不到再看 `reference/block_substitutions.md` 能不能用基础模块拼。
|
||||
**拼得出来就不许写 MATLAB Function。**
|
||||
2. 搭好后跑 HDL 兼容性检查:
|
||||
```matlab
|
||||
check_hdl_support('<model>/<HDL 顶层>', 'Out', 'hdl_support.txt')
|
||||
```
|
||||
3. 跑本 skill 的政策审计 + 闸门(见 §五)。
|
||||
4. 功能仿真复验(收敛/MER 等系统指标不劣化)→ `makehdl` → 交给 **rtl-ip-replace** 做 IP 化与
|
||||
cycle-true 对比 → 进综合。
|
||||
|
||||
## 四、流程 B:确实非 m 代码不可(铁律 2 的唯一合法出口)
|
||||
|
||||
1. 写清**为什么原生模块搭不出来**(算法结构/时序/资源,不接受"太麻烦");
|
||||
2. **指定具体 Xilinx IP**(名字 + 版本 + 覆盖范围),而不是"以后再换";
|
||||
3. 用 **rtl-ip-replace** skill 完成替换,并跑双 DUT 逐拍对比,拿到 `cmp/cmp_result.txt` 首行 PASS;
|
||||
4. 把上述内容登记进工程的豁免文件
|
||||
(格式见 `scripts/exemptions.example.json`,字段缺一不可);
|
||||
5. 重跑闸门,必须显示 `[EXEMPT]` 且 `GATE: PASS`。
|
||||
|
||||
> 位级逐拍等价确实做不到时(典型:CORDIC 内部舍入),按 rtl-ip-replace 的**消费点契约**放宽,
|
||||
> 但差异幅度与性能复验结论必须写进 Result 文档,并在豁免条目的 `evidence_kind` 里注明。
|
||||
|
||||
## 五、审计与闸门(每次改完模型、`makehdl` 之前跑)
|
||||
|
||||
MATLAB 侧(经 matlab MCP 运行):
|
||||
```matlab
|
||||
addpath(fullfile(getenv('USERPROFILE'),'.claude','skills','simulink_rtl_dev','scripts'));
|
||||
audit_hdl_blocks('<model>/<HDL 顶层>', ...
|
||||
'Exemptions','Notes/<model>_mcode_exemptions.json', ...
|
||||
'Out','Notes/<model>_hdl_audit.json');
|
||||
```
|
||||
|
||||
Shell 侧(闸门,退出码即结论):
|
||||
```
|
||||
python "%USERPROFILE%\.claude\skills\simulink_rtl_dev\scripts\check_exemptions.py" ^
|
||||
Notes\<model>_hdl_audit.json Notes\<model>_mcode_exemptions.json
|
||||
```
|
||||
|
||||
审计如何分级:
|
||||
|
||||
| 级别 | 命中的东西 | 处理 |
|
||||
|---|---|---|
|
||||
| `VIOLATION` | MATLAB Function、Truth Table、MATLAB System、S-Function、`Fcn` | 必须改成原生模块,或走流程 B 豁免 |
|
||||
| `EXEMPT` | 已登记且证据齐全的 m 代码块 | 放行(闸门仍会核对证据文件首行是否 PASS) |
|
||||
| `WARN` | Stateflow Chart / State Transition Table | 允许,但**只能做控制**,不许放数据通路算术 |
|
||||
|
||||
审计顺带给出迁移所需信息:脚本行数、是否有 `persistent`、是否有循环、输入输出端口数,
|
||||
以及按关键字给出的**原生模块建议**(sin/cos→NCO、atan2→Complex to Magnitude-Angle、
|
||||
persistent 数组→RAM/Tapped Delay 等)。
|
||||
|
||||
MATLAB Function 块内部那个 ` SFunction ` 子块是实现细节,审计会自动折叠,不会重复报。
|
||||
|
||||
## 六、新工程接入本 skill
|
||||
|
||||
1. 在工程任务书里写明**"RTL 子树"清单**与**"纯仿真件"清单**(§二);
|
||||
2. 跑一次全量审计,把结果作为**基线**记进工程 Notes——**如实记录,不要粉饰**:
|
||||
历史欠账(早于本规矩写成的 m 代码块)照实列出,标明闸门当前是 BLOCKED 还是 PASS;
|
||||
3. 对每个已有违规块,给出**原生模块出路**(查 `hdl_block_search` 与 `block_substitutions.md`)
|
||||
与**迁移优先级**——有现成库块的先换(如 LMS Filter、Farrow Rate Converter),
|
||||
最大最复杂的建议拆成几个子系统分批换;
|
||||
4. 从此**新代码一律按铁律 1 办**,不许再往违规块里追加 m 代码;
|
||||
5. 已完成的局部 IP 化可作为**部分豁免**依据,但 `ip_scope` 必须写清
|
||||
"IP 只覆盖这几处,其余 m 代码仍是欠账",不得整块标 PASS。
|
||||
|
||||
不在当前 `makehdl` 目标里的 m 代码块(如仅用于仿真的源),暂不受闸门约束;
|
||||
哪天要给它生成 RTL,先跑一次本 skill 的审计。
|
||||
|
||||
## 七、脚本清单
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `scripts/audit_hdl_blocks.m` | 政策审计:遍历 RTL 子树,列出所有 m 代码块 + 迁移度量 + 原生模块建议,写 JSON;支持豁免文件 |
|
||||
| `scripts/check_exemptions.py` | 铁律 2 闸门:每个 m 代码块必须有 IP 名 + cycle-true 证据(首行 PASS)+ 批准人/日期,否则退出码 1 |
|
||||
| `scripts/hdl_block_search.m` | 在 HDL Coder / DSP HDL / Wireless HDL / Vision HDL 库里按关键字找可直接生成 RTL 的模块 |
|
||||
| `scripts/check_hdl_support.m` | `checkhdl` 封装:抓出根本无法生成 RTL 的模块 |
|
||||
| `scripts/exemptions.example.json` | 豁免登记模板(字段缺一不可) |
|
||||
| `reference/block_substitutions.md` | m 代码惯用法 → 原生模块对照表(库名/块名在 R2025b 核对过) |
|
||||
|
||||
## 八、与其它 skill 的分工
|
||||
|
||||
- **本 skill**:模型侧的规矩与闸门(不许写 m 代码;要写就得有 IP + cycle-true 证据)。
|
||||
- **rtl-ip-replace**:`makehdl` 之后 RTL 侧的 IP 化(ROM/RAM/CORDIC/FIFO/**定抽头**滤波器)
|
||||
与双 DUT cycle-true 对比 —— 铁律 2 的证据就是它产出的 `cmp_result.txt`。
|
||||
- **sim-rtl-flow**:把整条 Simulink→RTL→硬件的流程编排起来,本 skill 是其中"模块怎么搭"的一环。
|
||||
- 滤波器口径三者一致:**只有固定抽头的滤波器才做 IP 替换**;LMS/Farrow/运行时参考抽头的
|
||||
相关器不替换 —— 但在模型侧,它们照样必须用原生模块(LMS Filter / Farrow Rate Converter)搭。
|
||||
@@ -0,0 +1,65 @@
|
||||
# m 代码惯用法 → 原生可生成 RTL 的 Simulink 模块
|
||||
|
||||
**用途**:Task12-1 铁律 1 要求"要生成 RTL 的模块及其所有子模块必须用能直接生成 RTL 的 Simulink
|
||||
模块搭建"。下表是把 MATLAB Function 里的常见写法翻译成原生模块的对照表。
|
||||
|
||||
**库来源(本机 R2025b 实测存在)**:
|
||||
|
||||
| 库 | `load_system` 名 | 内容 |
|
||||
|---|---|---|
|
||||
| HDL Coder 模块库 | `hdlsllib` | 基础集(241 个):延迟/RAM/FIFO/查找表/位操作/计数器/数学 |
|
||||
| DSP HDL Toolbox | `dsphdlfiltering2` | Discrete FIR Filter、FIR Decimator/Interpolator/Rate Converter、CIC Decimator/Interpolator、Biquad、**LMS Filter**、Channelizer |
|
||||
| | `dsphdlmathfun2` | **Complex to Magnitude-Angle**(CORDIC) |
|
||||
| | `dsphdlxfrm2` | FFT、IFFT |
|
||||
| | `dsphdlsigops2` | **NCO**、**Farrow Rate Converter**、Downsampler、Upsampler |
|
||||
| Wireless HDL | `whdllib` | 调制、纠错编码、I/O |
|
||||
| Vision HDL | `visionhdllib` | 图像流水线 |
|
||||
|
||||
查库命令(skill 自带):`hdl_block_search('lms','farrow')` —— 关键字搜上述所有库。
|
||||
|
||||
## 对照表
|
||||
|
||||
| m 代码写法 | 原生模块 | 备注 |
|
||||
|---|---|---|
|
||||
| `persistent` 标量状态 | **Unit Delay** / **Delay** | 带复位/使能用 `Unit Delay Resettable Synchronous`、`Enabled Delay` |
|
||||
| `persistent` 数组当延迟线 | **Tapped Delay** / `Delay`(Length>1) | 抽头要外露就用 Tapped Delay |
|
||||
| `persistent` 数组当存储、按地址读写 | **Simple Dual Port RAM** / `Single Port RAM` / `Dual Port RAM` | 下游 `rtl-ip-replace` 会换成 `blk_mem_gen` |
|
||||
| 常量表 `sinT/cosT` 查表相位旋转 | **NCO**(dsphdlsigops2)或 `Sine/Cosine HDL Optimized` | 表本身 → `Direct Lookup Table (n-D)` → 下游 `dist_mem_gen` |
|
||||
| 任意常量表查表 | **Direct Lookup Table (n-D)** / `1-D Lookup Table` / `Prelookup` | 内容固定才算查找表 |
|
||||
| `atan2(y,x)` / `abs(z)` 求角求模 | **Complex to Magnitude-Angle**(dsphdlmathfun2,CORDIC) | 下游可换 Xilinx `cordic` IP |
|
||||
| `sqrt` | `Sqrt` / `Reciprocal Sqrt` | |
|
||||
| 除法 / 倒数 | `Divide` / `Math Reciprocal` | 注意流水延迟,别在反馈环里放 |
|
||||
| **定系数 FIR** | **Discrete FIR Filter** | 下游 `fir_compiler`(只有定系数才 IP 化) |
|
||||
| 抽取 / 内插 FIR | **FIR Decimator** / **FIR Interpolator** / `FIR Rate Converter` | 大比例整数变速 → `CIC Decimator/Interpolator` |
|
||||
| **LMS 自适应均衡**(EqCore 那种) | **LMS Filter**(dsphdlfiltering2) | 原生模块搭得出来 ⇒ 不需要豁免;IP 化不适用(抽头非常量) |
|
||||
| **分数延时 / Farrow 重采样**(TxPreOffset 那种) | **Farrow Rate Converter**(dsphdlsigops2) | 同上:能用原生模块就不许写 m 代码 |
|
||||
| FFT / IFFT | **FFT** / **IFFT**(dsphdlxfrm2) | |
|
||||
| 计数器 `k = k+1; if k==N, k=0` | **HDL Counter** / `Counter Free-Running` / `Counter Limited` | |
|
||||
| `if/elseif` 分支选值 | `Switch` / `Multiport Switch` / `Index Vector` | |
|
||||
| `switch state` 控制状态机 | **Stateflow Chart(纯控制)** 或 `Switch Case Action Subsystem` | Chart 只放控制,**不许放数据通路算术**(审计报 WARN) |
|
||||
| 饱和 / 量化 / 定点转换 | `Data Type Conversion`(saturate/round)、`Saturation` | 位宽与舍入方式在这里定死,便于逐拍对齐 |
|
||||
| 移位、拼位、取位 | `Shift Arithmetic`、`Bit Concat`、`Bit Slice`、`Bit Set/Clear/Reduce` | |
|
||||
| 复数乘加 | `Product`(复数信号)、`Multiply-Add`、`Multiply-Accumulate`、`Dot Product` | |
|
||||
| 队列 / 跨速率缓冲 | **HDL FIFO** | 下游 `fifo_generator` |
|
||||
| 相关器(参考抽头运行时捕获) | `Tapped Delay` + `Multiply-Accumulate` + RAM | 抽头非常量 ⇒ **不做 IP 替换**,但仍应用原生模块搭 |
|
||||
| `max/min`、门限比较 | `MinMax`、`Compare To Constant`、`Relational Operator` | |
|
||||
| 边沿检测 | `Detect Rise Positive` / `Detect Fall Negative` | |
|
||||
|
||||
## 与 rtl-ip-replace 的对应关系
|
||||
|
||||
原生模块搭好 → `makehdl` 生成 RTL → `rtl-ip-replace` 再把其中四类结构换成 Xilinx IP:
|
||||
|
||||
| 原生模块 | 生成的 RTL 结构 | 下游 IP |
|
||||
|---|---|---|
|
||||
| Direct Lookup Table (n-D) / 常量表 | 常量 wire 数组 | `dist_mem_gen` |
|
||||
| Simple Dual Port RAM 等 | `SimpleDualPortRAM_generic` | `blk_mem_gen` |
|
||||
| Complex to Magnitude-Angle | CORDIC 迭代锥 | `cordic` |
|
||||
| HDL FIFO | reg 数组 + 指针 | `fifo_generator` |
|
||||
| Discrete FIR Filter(**定系数**) | Coef 模块 + systolic 抽头链 | `fir_compiler` |
|
||||
| LMS Filter / Farrow Rate Converter | 自适应/变系数结构 | **不替换**(抽头非常量) |
|
||||
|
||||
## 判定口诀
|
||||
|
||||
1. 先 `hdl_block_search('关键字')`:库里有 → 必须用库里的,不许写 m 代码;
|
||||
2. 库里没有、且能用基础模块(Delay/Product/Sum/Switch/Lookup/RAM)拼出来 → 拼出来;
|
||||
3. 两者都不行 → 才轮到铁律 2:**指定具体的 Xilinx IP + 仿真证明 cycle-true + 登记豁免**。
|
||||
@@ -0,0 +1,208 @@
|
||||
function rep = audit_hdl_blocks(target, varargin)
|
||||
%AUDIT_HDL_BLOCKS Policy audit of an RTL-bound Simulink subtree (Task12-1).
|
||||
%
|
||||
% Rule 1: everything under a subsystem that is going to be turned into RTL must
|
||||
% be built from Simulink blocks that generate RTL directly -- no embedded
|
||||
% MATLAB (m) code.
|
||||
% Rule 2: an m-code block is only acceptable if a specific Xilinx IP is named
|
||||
% as its replacement AND the swap is proven cycle-true by simulation. Such
|
||||
% blocks must be listed in the exemptions file, with evidence.
|
||||
%
|
||||
% rep = AUDIT_HDL_BLOCKS('model/HdlSubsystem')
|
||||
% rep = AUDIT_HDL_BLOCKS(..., 'Exemptions', 'Notes/hdl_mcode_exemptions.json')
|
||||
% rep = AUDIT_HDL_BLOCKS(..., 'Out', 'hdl_audit.json')
|
||||
%
|
||||
% The report (struct + JSON) lists every offending block with the metrics
|
||||
% needed to plan its migration: script size, persistent state, loops, ports,
|
||||
% and a suggested native-block replacement.
|
||||
%
|
||||
% Verdict: PASS no m-code in the subtree
|
||||
% EXEMPT all m-code blocks are listed as exempted (evidence is
|
||||
% checked separately by check_exemptions.py)
|
||||
% VIOLATION at least one undocumented m-code block
|
||||
|
||||
p = inputParser;
|
||||
p.addParameter('Exemptions', '');
|
||||
p.addParameter('Out', '');
|
||||
p.parse(varargin{:});
|
||||
exFile = p.Results.Exemptions;
|
||||
outFile = p.Results.Out;
|
||||
|
||||
parts = strsplit(target, '/');
|
||||
model = parts{1};
|
||||
if ~bdIsLoaded(model)
|
||||
load_system(model);
|
||||
end
|
||||
|
||||
opts = {'LookUnderMasks', 'all', 'FollowLinks', 'on'};
|
||||
blocks = find_system(target, opts{:}, 'Type', 'Block');
|
||||
|
||||
% ---- classification ------------------------------------------------------
|
||||
% VIOLATION: behaviour written in m code (or opaque to HDL Coder)
|
||||
% WARN : Stateflow control constructs -- allowed for control only, must be
|
||||
% justified; they are not the "embedded m language" this rule targets
|
||||
% but they still hide code from review.
|
||||
VIOL_SF = {'MATLAB Function', 'Truth Table'};
|
||||
WARN_SF = {'Chart', 'State Transition Table'};
|
||||
VIOL_BT = {'MATLABSystem', 'S-Function', 'M-S-Function', 'Fcn', 'MATLABFcn'};
|
||||
|
||||
exempt = local_read_exemptions(exFile);
|
||||
|
||||
items = struct('block', {}, 'kind', {}, 'severity', {}, 'lines', {}, ...
|
||||
'persistent_state', {}, 'loops', {}, 'n_in', {}, 'n_out', {}, ...
|
||||
'suggestion', {}, 'exempt', {}, 'xilinx_ip', {});
|
||||
|
||||
% Stateflow blocks carry an internal ' SFunction ' child; report the owner, not
|
||||
% its implementation detail.
|
||||
sfOwners = {};
|
||||
for k = 1:numel(blocks)
|
||||
if strcmp(get_param(blocks{k}, 'BlockType'), 'SubSystem')
|
||||
try
|
||||
t = get_param(blocks{k}, 'SFBlockType');
|
||||
if ~isempty(t) && ~strcmp(t, 'NONE'), sfOwners{end+1} = blocks{k}; end %#ok<AGROW>
|
||||
catch
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k = 1:numel(blocks)
|
||||
blk = blocks{k};
|
||||
bt = get_param(blk, 'BlockType');
|
||||
sfType = '';
|
||||
if strcmp(bt, 'SubSystem')
|
||||
try, sfType = get_param(blk, 'SFBlockType'); catch, sfType = ''; end
|
||||
end
|
||||
if isempty(sfType) && any(cellfun(@(o) startsWith(blk, [o '/']), sfOwners))
|
||||
continue % inside a Stateflow block already accounted for
|
||||
end
|
||||
|
||||
if any(strcmp(sfType, VIOL_SF))
|
||||
kind = sfType; sev = 'VIOLATION';
|
||||
elseif any(strcmp(sfType, WARN_SF))
|
||||
kind = sfType; sev = 'WARN';
|
||||
elseif any(strcmp(bt, VIOL_BT))
|
||||
kind = bt; sev = 'VIOLATION';
|
||||
else
|
||||
continue
|
||||
end
|
||||
|
||||
[nl, hasPersist, hasLoop, script] = local_script_metrics(blk, kind);
|
||||
ph = get_param(blk, 'PortHandles');
|
||||
isEx = any(strcmp(blk, {exempt.block}));
|
||||
ip = '';
|
||||
if isEx
|
||||
ip = exempt(strcmp(blk, {exempt.block})).xilinx_ip;
|
||||
sev = 'EXEMPT';
|
||||
end
|
||||
|
||||
items(end+1) = struct( ...
|
||||
'block', blk, 'kind', kind, 'severity', sev, 'lines', nl, ...
|
||||
'persistent_state', hasPersist, 'loops', hasLoop, ...
|
||||
'n_in', numel(ph.Inport), 'n_out', numel(ph.Outport), ...
|
||||
'suggestion', local_suggest(script), 'exempt', isEx, 'xilinx_ip', ip); %#ok<AGROW>
|
||||
end
|
||||
|
||||
nViol = sum(strcmp({items.severity}, 'VIOLATION'));
|
||||
nWarn = sum(strcmp({items.severity}, 'WARN'));
|
||||
nEx = sum(strcmp({items.severity}, 'EXEMPT'));
|
||||
if nViol > 0
|
||||
verdict = 'VIOLATION';
|
||||
elseif nEx > 0
|
||||
verdict = 'EXEMPT';
|
||||
else
|
||||
verdict = 'PASS';
|
||||
end
|
||||
|
||||
rep = struct('target', target, 'model', model, ...
|
||||
'generated_at', datestr(now, 'yyyy-mm-dd HH:MM:SS'), ...
|
||||
'exemptions_file', exFile, 'n_blocks_scanned', numel(blocks), ...
|
||||
'n_violation', nViol, 'n_warn', nWarn, 'n_exempt', nEx, ...
|
||||
'verdict', verdict, 'items', {items});
|
||||
|
||||
% ---- report --------------------------------------------------------------
|
||||
fprintf('\n=== HDL m-code audit: %s ===\n', target);
|
||||
fprintf('scanned %d block(s)\n', numel(blocks));
|
||||
for k = 1:numel(items)
|
||||
it = items(k);
|
||||
fprintf(' [%-9s] %-11s %s\n', it.severity, it.kind, it.block);
|
||||
fprintf(' %d line(s), persistent=%d, loops=%d, %d in / %d out\n', ...
|
||||
it.lines, it.persistent_state, it.loops, it.n_in, it.n_out);
|
||||
if ~isempty(it.xilinx_ip)
|
||||
fprintf(' exempt -> Xilinx IP: %s\n', it.xilinx_ip);
|
||||
elseif ~isempty(it.suggestion)
|
||||
fprintf(' suggest: %s\n', it.suggestion);
|
||||
end
|
||||
end
|
||||
fprintf('VERDICT: %s (violation=%d warn=%d exempt=%d)\n\n', verdict, nViol, nWarn, nEx);
|
||||
|
||||
if ~isempty(outFile)
|
||||
fid = fopen(outFile, 'w');
|
||||
fwrite(fid, jsonencode(rep));
|
||||
fclose(fid);
|
||||
fprintf('report -> %s\n', outFile);
|
||||
end
|
||||
end
|
||||
|
||||
% -------------------------------------------------------------------------
|
||||
function ex = local_read_exemptions(f)
|
||||
ex = struct('block', {}, 'xilinx_ip', {});
|
||||
if isempty(f) || ~isfile(f)
|
||||
return
|
||||
end
|
||||
raw = jsondecode(fileread(f));
|
||||
if isfield(raw, 'exemptions')
|
||||
raw = raw.exemptions;
|
||||
end
|
||||
if isstruct(raw)
|
||||
raw = num2cell(raw);
|
||||
end
|
||||
for i = 1:numel(raw)
|
||||
e = raw{i};
|
||||
ip = '';
|
||||
if isfield(e, 'xilinx_ip'), ip = e.xilinx_ip; end
|
||||
ex(end+1) = struct('block', e.block, 'xilinx_ip', ip); %#ok<AGROW>
|
||||
end
|
||||
end
|
||||
|
||||
function [nl, hasPersist, hasLoop, script] = local_script_metrics(blk, kind)
|
||||
script = '';
|
||||
nl = 0; hasPersist = false; hasLoop = false;
|
||||
if ~strcmp(kind, 'MATLAB Function')
|
||||
return
|
||||
end
|
||||
try
|
||||
c = sfroot().find('-isa', 'Stateflow.EMChart', 'Path', blk);
|
||||
if isempty(c), return, end
|
||||
script = c(1).Script;
|
||||
nl = numel(strsplit(script, newline));
|
||||
hasPersist = ~isempty(regexp(script, '\<persistent\>', 'once'));
|
||||
hasLoop = ~isempty(regexp(script, '^\s*(for|while)\>', 'once', 'lineanchors'));
|
||||
catch
|
||||
end
|
||||
end
|
||||
|
||||
function s = local_suggest(script)
|
||||
%LOCAL_SUGGEST keyword -> native Simulink block hints (see reference/block_substitutions.md)
|
||||
s = '';
|
||||
if isempty(script), return, end
|
||||
hits = {};
|
||||
map = { ...
|
||||
'\<(sin|cos)\s*\(', 'Sine/Cosine: NCO HDL Optimized or Direct Lookup Table (n-D)'; ...
|
||||
'\<atan2?\s*\(', 'atan/atan2: Complex to Magnitude-Angle HDL Optimized (CORDIC)'; ...
|
||||
'\<(sqrt|abs)\s*\(', 'sqrt/abs: Sqrt HDL Optimized / Complex to Magnitude-Angle HDL Optimized'; ...
|
||||
'\<(filter|conv)\s*\(', 'FIR: Discrete FIR Filter (HDL) -- keeps taps as a block parameter'; ...
|
||||
'persistent\s+\w*(buf|mem|ram|dl)', 'sample memory: Simple Dual Port RAM / Delay / Tapped Delay'; ...
|
||||
'\<(fft|ifft)\s*\(', 'FFT: FFT HDL Optimized'; ...
|
||||
'switch\s+\w*(state|st)\>', 'control FSM: Stateflow Chart (control only, no datapath math)'; ...
|
||||
'\<mod\s*\(|\+\s*1\s*;.*count', 'counter: HDL Counter / Counter Free-Running'};
|
||||
for i = 1:size(map, 1)
|
||||
if ~isempty(regexp(script, map{i,1}, 'once'))
|
||||
hits{end+1} = map{i,2}; %#ok<AGROW>
|
||||
end
|
||||
end
|
||||
if isempty(hits)
|
||||
s = 'no keyword match -- rebuild from primitives (Delay/Product/Sum/Switch/Lookup Table)';
|
||||
else
|
||||
s = strjoin(hits, ' | ');
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Gate for rule 2 of Task12-1: an m-code block inside an RTL-bound subtree is
|
||||
acceptable ONLY if a specific Xilinx IP is named as its replacement AND the swap
|
||||
is proven cycle-true by simulation.
|
||||
|
||||
python check_exemptions.py <hdl_audit.json> [<exemptions.json>]
|
||||
|
||||
Exit code 0 = the subtree may go to makehdl / synthesis:
|
||||
every m-code block is either gone, or exempted with (a) a named Xilinx IP and
|
||||
(b) an evidence file whose first line is PASS (rtl-ip-replace cmp_result.txt).
|
||||
Exit code 1 = blocked, with the reason printed per block.
|
||||
|
||||
Evidence paths are resolved relative to the repo root (the directory containing
|
||||
Notes/), or absolutely.
|
||||
"""
|
||||
import json, os, sys
|
||||
|
||||
REQUIRED = ('block', 'reason', 'xilinx_ip', 'cycle_true_evidence', 'approved_by', 'date')
|
||||
|
||||
|
||||
def repo_root(start):
|
||||
d = os.path.abspath(start)
|
||||
while True:
|
||||
if os.path.isdir(os.path.join(d, 'Notes')) or os.path.isdir(os.path.join(d, '.git')):
|
||||
return d
|
||||
nd = os.path.dirname(d)
|
||||
if nd == d:
|
||||
return os.path.abspath(start)
|
||||
d = nd
|
||||
|
||||
|
||||
def evidence_ok(path, roots):
|
||||
tried = []
|
||||
for r in ([''] if os.path.isabs(path) else roots):
|
||||
p = path if os.path.isabs(path) else os.path.join(r, path)
|
||||
tried.append(p)
|
||||
if os.path.isfile(p):
|
||||
first = open(p, errors='replace').readline().strip()
|
||||
if not first.upper().startswith('PASS'):
|
||||
return False, f'evidence is not a PASS: "{first}" ({p})'
|
||||
return True, first
|
||||
return False, 'evidence file not found: ' + ' | '.join(tried)
|
||||
|
||||
|
||||
def main():
|
||||
argv = [a for a in sys.argv[1:] if not a.startswith('--root=')]
|
||||
root_opt = [a.split('=', 1)[1] for a in sys.argv[1:] if a.startswith('--root=')]
|
||||
if not argv:
|
||||
sys.exit(__doc__)
|
||||
audit = json.load(open(argv[0], encoding='utf-8-sig'))
|
||||
exf = argv[1] if len(argv) > 1 else audit.get('exemptions_file') or ''
|
||||
roots = [r for r in (root_opt + [repo_root(os.getcwd()),
|
||||
repo_root(os.path.dirname(os.path.abspath(argv[0])) or '.'),
|
||||
os.getcwd()]) if r]
|
||||
|
||||
items = audit.get('items') or []
|
||||
if isinstance(items, dict):
|
||||
items = [items]
|
||||
mcode = [i for i in items if i['severity'] in ('VIOLATION', 'EXEMPT')]
|
||||
warns = [i for i in items if i['severity'] == 'WARN']
|
||||
|
||||
ex = []
|
||||
if exf and os.path.isfile(exf):
|
||||
raw = json.load(open(exf, encoding='utf-8-sig'))
|
||||
ex = raw.get('exemptions', raw if isinstance(raw, list) else [])
|
||||
by_block = {e.get('block'): e for e in ex}
|
||||
|
||||
print(f"target : {audit.get('target')}")
|
||||
print(f"root : {roots[0]}")
|
||||
print(f"audit : {len(items)} flagged block(s) "
|
||||
f"({len(mcode)} m-code, {len(warns)} stateflow-warn)")
|
||||
print(f"exempt : {exf or '(none)'}\n")
|
||||
|
||||
problems = []
|
||||
for it in mcode:
|
||||
blk = it['block']
|
||||
e = by_block.get(blk)
|
||||
if not e:
|
||||
problems.append(f"{blk}\n m-code with no exemption entry -- rebuild it from "
|
||||
f"native Simulink blocks, or file an exemption (rule 2).")
|
||||
print(f" [BLOCKED ] {blk}")
|
||||
continue
|
||||
missing = [k for k in REQUIRED if not str(e.get(k, '')).strip()]
|
||||
if missing:
|
||||
problems.append(f"{blk}\n exemption is incomplete: missing {', '.join(missing)}")
|
||||
print(f" [BLOCKED ] {blk} (incomplete exemption)")
|
||||
continue
|
||||
ok, info = evidence_ok(e['cycle_true_evidence'], roots)
|
||||
if not ok:
|
||||
problems.append(f"{blk}\n {info}")
|
||||
print(f" [BLOCKED ] {blk} ({info})")
|
||||
continue
|
||||
print(f" [EXEMPT ] {blk}\n IP: {e['xilinx_ip']}\n"
|
||||
f" cycle-true: {info} [{e['cycle_true_evidence']}]")
|
||||
|
||||
for it in warns:
|
||||
print(f" [WARN ] {it['block']} ({it['kind']}) -- control-only Stateflow is tolerated; "
|
||||
f"no datapath arithmetic in it")
|
||||
|
||||
stale = [b for b in by_block if b not in {i['block'] for i in items}]
|
||||
for b in stale:
|
||||
print(f" [STALE ] exemption for a block that is no longer m-code (or gone): {b}")
|
||||
|
||||
if problems:
|
||||
print(f'\nGATE: BLOCKED -- {len(problems)} problem(s)\n')
|
||||
for p in problems:
|
||||
print(' * ' + p)
|
||||
print('\nRule (Task12-1): an RTL-bound subsystem and everything under it must be built '
|
||||
'from Simulink blocks that generate RTL directly; embedded m code is allowed only '
|
||||
'against a named Xilinx IP, proven cycle-true.')
|
||||
sys.exit(1)
|
||||
print('\nGATE: PASS -- no undocumented m code in the RTL-bound subtree')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,64 @@
|
||||
function ok = check_hdl_support(target, varargin)
|
||||
%CHECK_HDL_SUPPORT HDL Coder compatibility check for an RTL-bound subsystem.
|
||||
%
|
||||
% ok = CHECK_HDL_SUPPORT('model/HdlSubsystem')
|
||||
% ok = CHECK_HDL_SUPPORT(..., 'Out', 'hdl_support.txt')
|
||||
%
|
||||
% Wraps checkhdl: catches blocks that cannot become RTL at all -- the other
|
||||
% half of Task12-1 rule 1 ("must be built from blocks that generate RTL
|
||||
% directly"). The m-code policy audit is audit_hdl_blocks.m; run both.
|
||||
|
||||
p = inputParser;
|
||||
p.addParameter('Out', '');
|
||||
p.parse(varargin{:});
|
||||
|
||||
model = strtok(target, '/');
|
||||
if ~bdIsLoaded(model)
|
||||
load_system(model);
|
||||
end
|
||||
|
||||
txt = '';
|
||||
threw = false;
|
||||
try
|
||||
txt = evalc('checkhdl(target)');
|
||||
catch e
|
||||
threw = true;
|
||||
txt = sprintf('checkhdl threw: %s\n%s', e.identifier, e.message);
|
||||
end
|
||||
|
||||
% The summary line is localised ("0 个错误、2 个警告" / "0 errors, 2 warnings"),
|
||||
% so count both spellings rather than grepping for the word "error".
|
||||
nErr = local_count(txt, {'(\d+)\s*个错误', '(\d+)\s+errors?'});
|
||||
nWarn = local_count(txt, {'(\d+)\s*个警告', '(\d+)\s+warnings?'});
|
||||
ok = ~threw && nErr == 0;
|
||||
|
||||
rep = regexp(txt, '(?<=file:///)[^''"<>]*_report\.html', 'match', 'once');
|
||||
fprintf('=== checkhdl %s : %s (%d error(s), %d warning(s)) ===\n', ...
|
||||
target, string(ok), nErr, nWarn);
|
||||
if ~isempty(rep)
|
||||
fprintf('report: %s\n', strrep(rep, '/', filesep));
|
||||
end
|
||||
disp(txt);
|
||||
if nWarn > 0
|
||||
fprintf(['NOTE: warnings are not a pass/fail gate here, but read them -- they are usually\n' ...
|
||||
' "this block will cost you area/latency in RTL" hints.\n']);
|
||||
end
|
||||
|
||||
if ~isempty(p.Results.Out)
|
||||
fid = fopen(p.Results.Out, 'w');
|
||||
fwrite(fid, txt);
|
||||
fclose(fid);
|
||||
fprintf('checkhdl log -> %s\n', p.Results.Out);
|
||||
end
|
||||
end
|
||||
|
||||
function n = local_count(txt, pats)
|
||||
n = 0;
|
||||
for i = 1:numel(pats)
|
||||
t = regexp(txt, pats{i}, 'tokens', 'once');
|
||||
if ~isempty(t)
|
||||
n = str2double(t{1});
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"_README": "Rule 2 of Task12-1: m code inside an RTL-bound subtree is only acceptable against a NAMED Xilinx IP, proven cycle-true. Copy this file to Notes/<model>_mcode_exemptions.json and fill it in. Every field is mandatory; check_exemptions.py rejects the entry otherwise.",
|
||||
"model": "dpd_ntn_05",
|
||||
"hdl_target": "dpd_ntn_05/DpdAlign_HDL",
|
||||
"exemptions": [
|
||||
{
|
||||
"block": "dpd_ntn_05/DpdAlign_HDL/RxAlign_HDL/RxAlignCore",
|
||||
"reason": "为什么原生 Simulink 模块搭不出来(必须具体:算法结构/时序/资源,不接受“太麻烦”)",
|
||||
"xilinx_ip": "cordic 6.0 (Arc_Tan) + dist_mem_gen sin/cos ROM + blk_mem_gen SDP",
|
||||
"ip_scope": "IP 覆盖的范围;剩下仍由 m 代码实现的部分要写清楚,它们仍然是欠账",
|
||||
"cycle_true_evidence": "vivado/rtl_ip/dpd_ntn_05/cmp/cmp_result.txt",
|
||||
"evidence_kind": "rtl-ip-replace 双 DUT 逐拍对比(首行须为 PASS)",
|
||||
"approved_by": "bowei",
|
||||
"date": "2026-07-24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
function hits = hdl_block_search(varargin)
|
||||
%HDL_BLOCK_SEARCH Find a native, RTL-generatable Simulink block (Task12-1 rule 1).
|
||||
%
|
||||
% hdl_block_search list every block in the HDL Coder library
|
||||
% hdl_block_search('ram') blocks whose name matches a keyword
|
||||
% hdl_block_search('sin','cos') several keywords (OR)
|
||||
%
|
||||
% Source of truth is the HDL Coder block library (hdlsllib) plus the HDL
|
||||
% Optimized blocks of DSP HDL / Wireless HDL / Vision HDL: anything in there
|
||||
% generates RTL directly, so it is a legal building block. Anything NOT in
|
||||
% there needs checkhdl before you rely on it.
|
||||
|
||||
% Sub-libraries are listed individually: the top-level DSP HDL library holds
|
||||
% links, which find_system will not walk into.
|
||||
libs = {'hdlsllib', ... % HDL Coder baseline set
|
||||
'dsphdlfiltering2', 'dsphdlmathfun2', ... % FIR/CIC/LMS/Biquad, CORDIC
|
||||
'dsphdlxfrm2', 'dsphdlsigops2', 'dsphdlsrcs2', ...% FFT/IFFT, Farrow/NCO/resample
|
||||
'whdllib', 'visionhdllib'};
|
||||
hits = {};
|
||||
for i = 1:numel(libs)
|
||||
try
|
||||
load_system(libs{i});
|
||||
catch
|
||||
continue
|
||||
end
|
||||
blks = find_system(libs{i}, 'SearchDepth', 5, 'Type', 'Block');
|
||||
for k = 1:numel(blks)
|
||||
nm = strrep(get_param(blks{k}, 'Name'), newline, ' ');
|
||||
if isempty(varargin) || any(cellfun(@(p) ~isempty(regexpi(nm, p, 'once')), varargin))
|
||||
hits{end+1, 1} = nm; %#ok<AGROW>
|
||||
hits{end, 2} = blks{k}; %#ok<AGROW>
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if isempty(hits)
|
||||
fprintf(['no HDL library block matches %s\n' ...
|
||||
'-> if you cannot build it from these blocks, rule 2 applies: name the Xilinx IP\n' ...
|
||||
' that will implement it, prove the swap cycle-true, and file the exemption.\n'], ...
|
||||
strjoin(varargin, ', '));
|
||||
return
|
||||
end
|
||||
|
||||
[~, ia] = unique(hits(:, 1));
|
||||
hits = hits(sort(ia), :);
|
||||
fprintf('%d HDL-generatable block(s):\n', size(hits, 1));
|
||||
for k = 1:size(hits, 1)
|
||||
fprintf(' %-42s %s\n', hits{k, 1}, hits{k, 2});
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user