CharlesCNorton
Image-level person classification on EUPE-ViT-B features with a single free parameter
f5498f9
Raw
History Blame Contribute Delete
3.59 kB
"""Synthesize the four decision variants with nosis and write circuit.json.
python synth.py
nosis is a pure-Python SystemVerilog to Lattice ECP5 synthesizer. Counts are
LUT4s, carry cells and slices on that device. Adder trees land on the carry
chain, so CCU2C rather than LUT4 is the resource that moves with the form of the
decision, and `bound` records which resource limits each variant.
"""
import argparse
import re
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent)) # repo root, for `common`
from common import read_artifact, write_artifact # noqa: E402
HERE = Path(__file__).resolve().parent
NOSIS_ROOT = Path(r'D:\nosis')
VARIANTS = {
'sum': ('person_classifier_1p', 'runtime input'),
'sum_folded': ('person_classifier_sum_folded', 'baked'),
'popcount': ('person_classifier_popcount', 'runtime inputs'),
'popcount_folded': ('person_classifier_popcount_folded', 'baked'),
}
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'
f'{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
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),
'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 / 'circuit.json')
args = ap.parse_args()
accuracy = read_artifact(args.out)['accuracy']
variants = {}
print(f"{'variant':>18}{'slices':>8}{'LUT4':>7}{'CCU2C':>7}{'bound':>7}{'ns':>8}")
for name, (top, thresholds) in VARIANTS.items():
src = args.rtl / f'{name}.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.update({'rtl': f'rtl/{name}.v', 'thresholds': thresholds})
variants[name] = s
print(f'{name:>18}{s["slices"]:>8}{s["lut4"]:>7}{s["ccu2c"]:>7}'
f'{s["bound"]:>7}{s["critical_path_ns"]:>8.2f}', flush=True)
device = next((v['device'] for v in variants.values() if v['device']), None)
write_artifact(args.out, {'variants': variants, 'accuracy': accuracy},
generator='synth.py',
tool='nosis', target={'family': 'ecp5', 'device': device},
inputs='40 signed INT8 feature channels at the classifier dims',
note='LUT4, carry and slice counts on an ECP5, not abstract gates')
print(f'\n[done] wrote {args.out}')
if __name__ == '__main__':
main()