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 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 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 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, '\', '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)'; ... '\', 'control FSM: Stateflow Chart (control only, no datapath math)'; ... '\ end end if isempty(hits) s = 'no keyword match -- rebuild from primitives (Delay/Product/Sum/Switch/Lookup Table)'; else s = strjoin(hits, ' | '); end end