- 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
117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
#!/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()
|