"""Synthesize every generated module with nosis and record the cell counts. python synth.py nosis is a pure-Python SystemVerilog to Lattice ECP5 synthesizer. It is the backend used here rather than Yosys, whose gate counts are not comparable. Counts are LUT4s and slices on an ECP5, not abstract gates. """ import argparse import json import re import subprocess import sys from pathlib import Path from common import read_artifact, write_artifact HERE = Path(__file__).resolve().parent NOSIS_ROOT = Path(r'D:\nosis') def synth_one(src: Path, top: str, build: Path, nosis_root: Path): build.mkdir(parents=True, exist_ok=True) r = subprocess.run( [sys.executable, '-m', 'nosis', str(src), '--top', top, '--stats', '-o', str(build / f'{top}.json')], cwd=str(nosis_root), capture_output=True, text=True) if r.returncode != 0: raise SystemExit(f'nosis failed on {src.name}:\n{r.stdout[-2000:]}{r.stderr[-2000:]}') (build / f'{top}.log').write_text(r.stdout, encoding='utf-8') def grab(pattern, cast=int): m = re.search(pattern, r.stdout) return cast(m.group(1)) if m else None def text(pattern): m = re.search(pattern, r.stdout) return m.group(1) if m else None # Adder trees land on the ECP5 carry chain, so CCU2C rather than LUT4 is the # size that moves; `bound` records which resource the design is limited by. return { 'slices': grab(r'Slices:\s+(\d+)'), 'lut4': grab(r'LUTs:\s+(\d+)'), 'ccu2c': grab(r'CCU2C:\s+(\d+)'), 'ffs': grab(r'FFs:\s+(\d+)'), 'bound': text(r'Bound:\s+(\S+)'), 'critical_path_ns': grab(r'Critical path delay:\s+([\d.]+)', float), 'max_freq_mhz': grab(r'Max frequency \(logic\):\s+([\d.]+)', float), 'device': text(r'Device:\s+(\S+)'), } def main(): ap = argparse.ArgumentParser(description=__doc__) ap.add_argument('--rtl', type=Path, default=HERE / 'rtl') ap.add_argument('--build', type=Path, default=HERE / 'build') ap.add_argument('--nosis', type=Path, default=NOSIS_ROOT) ap.add_argument('--out', type=Path, default=HERE / 'synth.json') args = ap.parse_args() rules = read_artifact(HERE / 'rules.json')['rules'] variants = {} print(f"{'rule':>6}{'dims':>6}{'slices':>8}{'LUT4':>7}{'CCU2C':>7}" f"{'bound':>7}{'ns':>7}") for name in rules: top = f'person_{name}' src = args.rtl / f'{top}.v' if not src.exists(): raise SystemExit(f'{src} missing; run rtl_gen.py first') s = synth_one(src, top, args.build, args.nosis) s['rtl'] = f'rtl/{top}.v' s['n_dims'] = rules[name]['n_dims'] variants[name] = s print(f'{name:>6}{s["n_dims"]:>6}{s["slices"]:>8}{s["lut4"]:>7}' f'{s["ccu2c"]:>7}{s["bound"]:>7}{s["critical_path_ns"]:>7.2f}', flush=True) device = next((v['device'] for v in variants.values() if v['device']), None) write_artifact(args.out, { 'tool': 'nosis', 'target': {'family': 'ecp5', 'device': device}, 'variants': variants, }, generator='synth.py', inputs='signed INT8 channels of the pooled feature vector', note='LUT4 and slice counts on an ECP5, not abstract gate counts') print(f'\n[done] wrote {args.out}') if __name__ == '__main__': main()