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:
bowei
2026-07-27 10:39:53 +08:00
parent 6dddab8923
commit ea4541ee90
23 changed files with 3171 additions and 12 deletions
+154
View File
@@ -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 + 登记豁免**。
+208
View File
@@ -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