#!/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 [] 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()