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,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
|
||||
Reference in New Issue
Block a user