Files
FPGA-Neural/tools/neural_sim/network.py
T
micheleandClaude Sonnet 5 9b5d1055b8 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
2026-09-06 19:51:33 +02:00

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