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
+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