#!/usr/bin/env python3 """Suffix every module in rtl_ip/ with _IP so original and IP-based RTL can coexist in one xsim `work` library for the dual-DUT cycle-true comparison. - renames `module X` -> `module X_IP` for every module DECLARED in rtl_ip/*.v - rewrites instantiations of those modules accordingly (word-boundary match) - IP core instances (dist_mem_gen/blk_mem_gen/... names from ip_manifest) are left untouched -- they are not declared here. Run LAST, after rom_to_ip.py / ram_to_ip.py / any hand edits (CORDIC, FIFO). Idempotent: a name already ending in _IP is never suffixed twice. INCREMENTAL RUNS -- THE BUG THIS DOCSTRING USED TO DESCRIBE AS A FEATURE. The rename set used to be "modules declared here whose name does NOT already end in _IP", i.e. it treated an already-suffixed declaration as nothing left to do. That is wrong the moment incr_plan.py restores previously-rewritten files and drops ONE freshly regenerated file beside them, which is the normal incremental case: EncAddr.v restored from the last run -> declares EncAddr_IP DpdTrain.v freshly generated -> declares DpdTrain, instantiates EncAddr `EncAddr` was no longer in the rename set, so the new DpdTrain.v kept its bare `EncAddr u_EncAddr (...)` while the file that defines it had been renamed away. The delivery then elaborated only because the dual-DUT bench compiled the GOLDEN tree into the same work library, where a bare `EncAddr` still exists; synthesis, which sees the delivery alone, failed with "module 'EncAddr' not found". The rename set is therefore built from the BASE NAME of every declaration -- X_IP counts as X -- so a regenerated file is namespaced against the whole delivery, not just against the part of it that has not been namespaced yet. """ import argparse, os, re def main(): ap = argparse.ArgumentParser() ap.add_argument('rtl_ip_dir') a = ap.parse_args() d = os.path.abspath(a.rtl_ip_dir) files = [os.path.join(d, f) for f in os.listdir(d) if f.endswith('.v')] mods, already = [], [] for p in files: for m in re.finditer(r'^\s*module\s+(\w+)', open(p, encoding='utf-8', errors='replace').read(), re.M): n = m.group(1) if n.endswith('_IP'): already.append(n[:-3]) mods.append(n[:-3]) # base name: a restored file still OWNS this name else: mods.append(n) mods = sorted(set(mods), key=len, reverse=True) print('modules:', mods) if already: print('already namespaced (kept in the rename set as base names):', sorted(set(already))) for p in files: txt = open(p, encoding='utf-8', errors='replace').read() for mod in mods: txt = re.sub(r'\b%s\b(?!_IP)' % re.escape(mod), mod + '_IP', txt) open(p, 'w', encoding='utf-8').write(txt) print(f'suffixed {len(mods)} modules across {len(files)} files') if __name__ == '__main__': main()