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,61 @@
|
||||
"""
|
||||
Network model: an ordered chain of FCLayer instances, each layer's
|
||||
INT8 output feeding the next layer's INT8 input.
|
||||
|
||||
Scope, deliberately kept small (per this project's own explicit "do
|
||||
not invent unsupported FPGA functionality" instruction): this models a
|
||||
linear chain of fully-connected+activation layers. The real V2
|
||||
hardware's dependency_manager.v is actually more general -- it
|
||||
schedules an arbitrary DAG of neuron "jobs" via producer_ids/required
|
||||
fields, so a layer boundary is not a hardware limitation, only a
|
||||
simulator scope limit for this first phase. A linear chain is exactly
|
||||
what a linear chain of dependency-graph layers computes, so this is
|
||||
faithful for the topologies it supports; it does not yet model
|
||||
arbitrary-DAG job graphs, SPI job submission, or scheduling -- see
|
||||
README.md "Optional future extension" and PRE_PCB_VERIFICATION.md's
|
||||
own dependency-graph description for what the real hardware supports
|
||||
beyond what this simulator currently models.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .layer import FCLayer
|
||||
|
||||
|
||||
class Network:
|
||||
def __init__(self, layers: list[FCLayer]):
|
||||
if not layers:
|
||||
raise ValueError("a Network needs at least one layer")
|
||||
for i in range(1, len(layers)):
|
||||
if layers[i].n_inputs != layers[i - 1].n_neurons:
|
||||
raise ValueError(
|
||||
f"layer {i}'s n_inputs={layers[i].n_inputs} does not match "
|
||||
f"layer {i-1}'s n_neurons={layers[i-1].n_neurons}"
|
||||
)
|
||||
self.layers = layers
|
||||
|
||||
@property
|
||||
def n_inputs(self) -> int:
|
||||
return self.layers[0].n_inputs
|
||||
|
||||
@property
|
||||
def n_outputs(self) -> int:
|
||||
return self.layers[-1].n_neurons
|
||||
|
||||
def forward(self, inputs) -> np.ndarray:
|
||||
"""Runs `inputs` through every layer in order, returning the
|
||||
final layer's INT8 output vector. Also available as
|
||||
`forward_all` if every intermediate tensor is needed."""
|
||||
return self.forward_all(inputs)[-1]
|
||||
|
||||
def forward_all(self, inputs) -> list[np.ndarray]:
|
||||
"""Returns [layer0_output, layer1_output, ..., layerN_output]
|
||||
-- every intermediate tensor, not just the final one (useful
|
||||
for debugging / per-layer golden-vector generation)."""
|
||||
x = np.asarray(inputs, dtype=np.int64)
|
||||
outputs = []
|
||||
for layer in self.layers:
|
||||
x = layer.forward(x)
|
||||
outputs.append(x)
|
||||
return outputs
|
||||
Reference in New Issue
Block a user