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,116 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user