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:
@@ -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
|
||||
Reference in New Issue
Block a user