File size: 3,590 Bytes
f5498f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""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()