CharlesCNorton
Image-level person classification on EUPE-ViT-B features with no free parameters
e8b8483 | """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') | |
| def test_module_exists(name): | |
| assert (RTL / f'person_{name}.v').exists() | |
| 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']) | |
| 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) | |
| 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] | |