CharlesCNorton
Image-level person classification on EUPE-ViT-B features with no free parameters
e8b8483 | """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() | |