feat: neural_sim Python golden functional reference simulator

Adds tools/neural_sim/, a NumPy-based reference implementation of the
FPGA-Neural V2 numeric model (INT8 in/weight, 32-bit wraparound
accumulation, ReLU+saturate out), derived directly from
hardware/v2/rtl/neural_processor.v (not assumed) and reusing
tools/validation/mac_oracle.py's own pre-existing, hand-verified
two's-complement primitives rather than duplicating them.

Provides: neuron/layer/network models, a logical memory model of the
real V2 SDRAM map (weights/activations/results), deterministic
test-vector generators (simple/signed/extremes/zero/random/D-Stress
256x128) with JSON golden-vector export, an FPGA-vs-Python bit-exact
comparison utility, four example networks, a CLI
(`python -m tools.neural_sim ...`), and a 96-test pytest suite (all
passing) covering signed-arithmetic edge cases (including a direct
32-bit wraparound proof), scalar-vs-vectorized neuron cross-checks,
layer/memory/vector/comparison tests.

This is a golden functional reference (bit-exact numeric result),
explicitly NOT a cycle-accurate FPGA simulator -- see
tools/neural_sim/README.md for the full scope statement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-06 19:51:33 +02:00
co-authored by Claude Sonnet 5
parent c4763aab10
commit 9b5d1055b8
18 changed files with 1518 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
import json
import os
from tools.neural_sim import vectors as vec
from tools.neural_sim.layer import FCLayer
def test_simple_positive_manually_predictable():
v = vec.gen_simple_positive()
# inputs=[1..8], weights=all 1 -> sum = 36, ReLU(36)=36
assert v.expected == [36]
def test_zero_vector_is_all_zero_output():
v = vec.gen_zero()
assert v.expected == [0]
def test_extremes_vector_is_self_consistent():
v = vec.gen_extremes()
layer = FCLayer(v.weights, biases=v.biases, activation=v.activation, p_in=v.p_in)
assert layer.forward(v.inputs).tolist() == v.expected
def test_random_vector_is_deterministic_across_calls():
v1 = vec.gen_random(seed=555)
v2 = vec.gen_random(seed=555)
assert v1.inputs == v2.inputs
assert v1.weights == v2.weights
assert v1.expected == v2.expected
def test_random_vector_different_seed_differs():
v1 = vec.gen_random(seed=1)
v2 = vec.gen_random(seed=2)
assert v1.inputs != v2.inputs
def test_d_stress_dimensions_match_the_real_rtl_benchmark():
v = vec.gen_dstress()
assert v.n_neurons == 256
assert v.n_inputs == 128
def test_d_stress_is_deterministic():
v1 = vec.gen_dstress(seed=42)
v2 = vec.gen_dstress(seed=42)
assert v1.expected == v2.expected
def test_export_then_import_round_trip(tmp_path):
v = vec.gen_signed_mix()
path = str(tmp_path / "vec.json")
vec.export_json(v, path)
loaded = vec.load_json(path)
assert loaded == v
def test_export_all_json_contains_every_generator(tmp_path):
path = str(tmp_path / "all.json")
vec.export_all_json(path)
with open(path) as f:
data = json.load(f)
assert set(data.keys()) == set(vec.ALL_GENERATORS.keys())
for name, d in data.items():
assert "expected" in d and "weights" in d and "inputs" in d