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
62 lines
2.4 KiB
Python
62 lines
2.4 KiB
Python
"""
|
|
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
|