Files
bowei ea4541ee90 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
2026-07-27 10:39:53 +08:00

109 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Replace fully-constant wire-array ROMs with Vivado dist_mem_gen instances.
- Copies every non-tb .v from <hdlsrc> into <hdlsrc>/rtl_ip/ (first run only, or --force).
- For each fully-constant ROM in the manifest:
* extracts the table into a .coe (dedup by content hash: identical tables share one IP config)
* deletes the wire-array declaration and the constant assigns from the copy
* per READ SITE inserts: wire [AW-1:0] <name>_a<k>; wire [W-1:0] <name>_q<k>;
assign <name>_a<k> = <read_expr>;
<ip> u_<name>_r<k> (.a(<name>_a<k>), .spo(<name>_q<k>));
and rewrites `<name>[<read_expr>]` -> `$signed(<name>_q<k>)` (or unsigned).
- Appends dist_mem_gen entries to ip_manifest.json / ip_manifest.tcl.
dist_mem_gen is ASYNC read => bit- and cycle-true against the original combinational read.
Run AFTER rtl_ip_scan.py, BEFORE namespace_ip.py.
"""
import argparse, hashlib, json, os, re, shutil, sys
def to_bits(radix, lit, width):
lit = lit.replace('_', '')
base = {'b': 2, 'd': 10, 'h': 16}[radix]
v = int(lit, base) & ((1 << width) - 1)
return format(v, '0%db' % width)
def main():
ap = argparse.ArgumentParser()
ap.add_argument('hdlsrc')
ap.add_argument('outdir', help='FIXED output dir (e.g. <repo>/vivado/rtl_ip/<model>)')
ap.add_argument('--manifest', default=None)
ap.add_argument('--force', action='store_true')
a = ap.parse_args()
from ipflow_common import copy_sources
hdlsrc = os.path.abspath(a.hdlsrc)
rtl_ip = os.path.abspath(a.outdir)
os.makedirs(rtl_ip, exist_ok=True)
man = json.load(open(a.manifest or os.path.join(rtl_ip, 'manifest.json')))
# 1. copy sources + PROVENANCE.log
copy_sources(hdlsrc, rtl_ip, force=a.force)
ipman_p = os.path.join(rtl_ip, 'ip_manifest.json')
ipman = json.load(open(ipman_p)) if os.path.exists(ipman_p) else {'ips': []}
coe_of_hash, roms_done = {}, 0
CONST_ASSIGN = re.compile(r"^\s*assign\s+(\w+)\[(\d+)\]\s*=\s*(\d+)'s?([bdh])([0-9a-fA-F_]+)\s*;\s*$", re.M)
by_file = {}
for rom in man['const_rom']:
if not rom.get('fully_const'):
print('SKIP (partial const):', rom['name']); continue
by_file.setdefault(rom['file'], []).append(rom)
for fn, roms in by_file.items():
path = os.path.join(rtl_ip, fn)
txt = open(path, encoding='utf-8', errors='replace').read()
for rom in roms:
name, W, D, signed = rom['name'], rom['width'], rom['depth'], rom['signed']
AW = max(1, (D - 1).bit_length())
# collect table
bits = {}
for m in CONST_ASSIGN.finditer(txt):
if m.group(1) == name:
bits[int(m.group(2))] = to_bits(m.group(4), m.group(5), W)
if len(bits) != D:
print(f'ERROR {name}: found {len(bits)}/{D} const assigns -- skipped'); continue
content = '\n'.join(bits[i] for i in range(D))
h = hashlib.sha1((f'{W}x{D}\n' + content).encode()).hexdigest()[:10]
if h not in coe_of_hash:
ipname = f'rom_{W}x{D}_{h}'
coe = os.path.join(rtl_ip, ipname + '.coe')
with open(coe, 'w') as f:
f.write('memory_initialization_radix=2;\nmemory_initialization_vector=\n')
f.write(',\n'.join(bits[i] for i in range(D)) + ';\n')
coe_of_hash[h] = ipname
ipman['ips'].append({'type': 'dist_mem_gen', 'name': ipname, 'width': W,
'depth': D, 'coe': os.path.abspath(coe)})
ipname = coe_of_hash[h]
# remove decl + assigns
txt = re.sub(r'^\s*wire\s+(signed\s+)?\[%d:0\]\s+%s\s+\[0:%d\]\s*;.*\n'
% (W - 1, re.escape(name), D - 1), '', txt, flags=re.M)
txt = re.sub(r"^\s*assign\s+%s\[\d+\]\s*=\s*[^;]+;\s*\n" % re.escape(name), '', txt, flags=re.M)
# rewrite read sites (unique exprs; every occurrence of same expr shares one instance)
inst_lines, k = [], 0
for expr in dict.fromkeys(rom['read_exprs']): # preserve order, dedup
aq, qq = f'{name}_a{k}', f'{name}_q{k}'
inst_lines.append(f' wire [{AW-1}:0] {aq};\n wire [{W-1}:0] {qq};\n'
f' assign {aq} = {expr};\n'
f' {ipname} u_{name}_r{k} (.a({aq}), .spo({qq}));\n')
rd = f'$signed({qq})' if signed else qq
txt = txt.replace(f'{name}[{expr}]', rd)
k += 1
# place instances right before `endmodule`
txt = txt.replace('endmodule', ' // ---- rtl-ip-replace: %s -> %s (%d read site(s)) ----\n%s\nendmodule'
% (name, ipname, k, ''.join(inst_lines)), 1)
roms_done += 1
print(f'ROM {name} ({W}x{D}, {k} reads) -> {ipname} [{fn}]')
open(path, 'w', encoding='utf-8').write(txt)
json.dump(ipman, open(ipman_p, 'w'), indent=1)
with open(os.path.join(rtl_ip, 'ip_manifest.tcl'), 'w') as f:
for ip in ipman['ips']:
f.write('lappend ips {%s}\n' % ' '.join(f'{k} {{{v}}}' for k, v in ip.items()))
print(f'done: {roms_done} ROMs, {len(coe_of_hash)} unique tables; ip_manifest updated')
if __name__ == '__main__':
main()