CharlesCNorton
Image-level person classification on EUPE-ViT-B features with no free parameters
e8b8483
Raw
History Blame Contribute Delete
3.38 kB
"""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()