CharlesCNorton
Image-level person classification on EUPE-ViT-B features with no free parameters
e8b8483
Raw
History Blame Contribute Delete
3.45 kB
"""The generated RTL carries the selected dims and computes the comparison.
The structural checks run everywhere. The simulation checks run when Icarus
Verilog is available, on PATH or via the IVERILOG and VVP environment variables.
"""
import os
import re
import shutil
import subprocess
import pytest
from conftest import REPO, load
RTL = REPO / 'rtl'
RULES = load('rules.json')['rules']
NAMES = sorted(RULES)
N_VECTORS = 256
def tool(name, env_var):
return os.environ.get(env_var) or shutil.which(name)
IVERILOG, VVP = tool('iverilog', 'IVERILOG'), tool('vvp', 'VVP')
needs_sim = pytest.mark.skipif(not (IVERILOG and VVP),
reason='Icarus Verilog not found')
def source(name):
return (RTL / f'person_{name}.v').read_text(encoding='utf-8')
@pytest.mark.parametrize('name', NAMES)
def test_module_exists(name):
assert (RTL / f'person_{name}.v').exists()
@pytest.mark.parametrize('name', NAMES)
def test_ports_are_exactly_the_selected_dims(name):
r = RULES[name]
declared = re.findall(r'\bf(\d+)\b', source(name).split(');')[0])
assert sorted(int(d) for d in set(declared)) == sorted(r['pos_dims'] + r['neg_dims'])
@pytest.mark.parametrize('name', NAMES)
def test_no_constant_is_baked_in(name):
"""A zero-parameter rule must not contain a fitted threshold."""
body = source(name).split('\n')
body = '\n'.join(l for l in body if not l.strip().startswith('//'))
assert 'localparam' not in body, f'{name} declares a constant'
for lit in re.findall(r"\d+'s?d(\d+)", body):
assert int(lit) == 0, f'{name} compares against a non-zero constant'
def vectors(n, dims, seed=0):
import random
rng = random.Random(seed)
return [{d: rng.randint(-128, 127) for d in dims} for _ in range(n)]
def reference(vec, pos, neg):
return sum(vec[d] for d in pos) > sum(vec[d] for d in neg)
@needs_sim
@pytest.mark.parametrize('name', NAMES)
def test_rtl_matches_the_reference(tmp_path, name):
r = RULES[name]
dims = r['pos_dims'] + r['neg_dims']
vecs = vectors(N_VECTORS, dims)
# Half the vectors are pushed onto the boundary, where > must reject ties.
for i in range(0, len(vecs), 2):
v = vecs[i]
v[r['pos_dims'][0]] = (sum(v[d] for d in r['neg_dims'])
- sum(v[d] for d in r['pos_dims'][1:]))
vecs = [v for v in vecs if all(-128 <= x <= 127 for x in v.values())]
top = f'person_{name}'
conns = ',\n '.join(f'.f{d}(f{d})' for d in dims)
decls = ', '.join(f'f{d}' for d in dims)
lines = []
for v in vecs:
lines.append(' ' + ' '.join(f'f{d} = {v[d]};' for d in dims) + ' #1;'
' $display("%b", out);')
tb = f'''`timescale 1ns/1ps
module tb;
reg signed [7:0] {decls};
wire out;
{top} dut (
{conns},
.person_present(out));
initial begin
{chr(10).join(lines)}
$finish;
end
endmodule
'''
(tmp_path / 'tb.v').write_text(tb)
subprocess.run([IVERILOG, '-g2005', '-o', 'tb.vvp',
str(RTL / f'{top}.v'), 'tb.v'],
cwd=tmp_path, check=True, capture_output=True)
out = subprocess.run([VVP, 'tb.vvp'], cwd=tmp_path, check=True,
capture_output=True, text=True).stdout
got = [l.strip() == '1' for l in out.splitlines() if l.strip() in ('0', '1')]
assert got == [reference(v, r['pos_dims'], r['neg_dims']) for v in vecs]