snnc is a small inference runtime for spiking neural networks (SNNs), written in C11 and
depending only on the C standard library. The engine is a single translation unit (snn.c,
snn.h); a command-line tool and a C library API are provided. Simulation is synchronous
(clock-driven) and feedforward.
The neuron dynamics follow the discrete-time leaky integrate-and-fire formulation used by snnTorch, and are cross-validated against it step by step (see Validation).
A network is a sequence of dense layers. Each layer multiplies its input by a weight matrix, adds
a bias to obtain an input current I, and updates one neuron per output unit. Four neuron models
are available:
-
Leaky integrate-and-fire (LIF) with membrane potential
V, decayβ ∈ (0,1), and thresholdθ:V[t+1] = β·V[t] + I[t] − S[t]·θ , S[t] = 1 if V[t] > θ else 0Reset is either by subtraction (
V −= θ) or to a fixed value (V = V_reset). The subtraction is carried on the stored membrane, matching snnTorch'sreset_delay=Falseconvention, so a membrane left above threshold does not immediately re-fire. -
Integrate-and-fire (IF), i.e. LIF with
β = 1. Its time-averaged firing rate is a clipped-linear function of the input current, which is the basis of ANN-to-SNN conversion. -
Izhikevich (2003), the two-variable model, integrated with two half Euler steps per timestep for numerical stability.
-
Non-spiking integrator, which accumulates membrane potential without firing; useful as a readout layer producing smooth logits.
Network input is encoded onto layer 0 by direct current injection, Poisson rate coding,
deterministic rate coding, or latency (time-to-first-spike). Output is decoded as a spike count,
the final membrane potential, or the first-spike time. The choices and their trade-offs are
documented with citations in RESEARCH.md; the file format and internals are in
DESIGN.md.
The relevant question for a runtime like this is whether its dynamics are correct, not merely
self-consistent. tests/xval_snntorch.py drives snnc and snnTorch's Leaky neuron with
identical weights and input currents, then compares the complete membrane trace and spike train at
every timestep. Across LIF and IF, both reset modes, and single- and multi-input layers, the
membrane potentials agree to floating-point rounding (maximum absolute error ≈ 5×10⁻⁷) with no
spike-timing disagreements over roughly 50,000 step comparisons.
This cross-check found a real bug during development: reset-by-subtraction was being applied one step too early relative to snnTorch, which changes the spike train whenever the membrane stays above threshold across consecutive steps. The runtime was corrected to match the reference.
The C test suite (tests/test_snn.c, run natively and under AddressSanitizer and
UndefinedBehaviorSanitizer) adds analytical checks against closed-form oracles — sub-threshold
steady state V∞ = I/(1−β), the discrete inter-spike interval, and the IF firing period
⌈θ/I⌉ — together with hand-built logic gates and a two-layer XOR, encoder and decoder behaviour,
serialization round-trips, and rejection of malformed model files.
make # build the snnc CLI (C11 compiler only)
make test # C suite, natively and under ASan/UBSan
make demo # train a ReLU MLP on XOR, convert to an SNN, run it
make xval # cross-validate against snnTorch (requires the venv below)The cross-validation needs snnTorch and PyTorch, which are not required to build or use the runtime:
python3 -m venv env && env/bin/pip install snntorch torch
make xval XVAL_PY=env/bin/pythonCommand line:
snnc info model.snn # architecture and parameters
snnc run model.snn --steps 64 1 0 # inference on the input vector (1, 0)
snnc run model.snn --input vec.csv # input read from a fileLibrary:
snn_model *m = snn_model_new(2);
snn_layer_init(&m->layers[0], 784, 128, SNN_LIF);
snn_layer_init(&m->layers[1], 128, 10, SNN_NONSPIKING);
/* fill m->layers[i].weights / .bias, set thresholds, then: */
snn_model_finalize(m);
snn_state *s = snn_state_new(m);
float scores[10];
int label = snn_infer(s, input, 32, scores); /* runs 32 steps, returns argmax */snn_step() advances a single timestep for streaming use; snn_save_file / snn_load_file
persist a model in the SNN1 binary format. The loader is bounds-checked and rejects malformed
input rather than reading out of bounds.
examples/xor.c trains a small ReLU multilayer perceptron on XOR with ordinary
backpropagation, applies data-based per-layer normalization, and converts it to an IF-neuron
network with a non-spiking readout. The converted spiking network reproduces the XOR truth table,
a check that the conversion preserves a non-linearly-separable decision boundary.
The runtime covers feedforward dense layers with synchronous simulation. Convolutional and
recurrent topologies, event-driven scheduling, on-device training, and fixed-point inference are
out of scope; the SNN1 format reserves space to add some of these later.
Key sources are listed in RESEARCH.md. The neuron equations and reset semantics
follow Eshraghian et al., Training Spiking Neural Networks Using Lessons from Deep Learning
(snnTorch); the ANN-to-SNN rate equivalence follows Diehl et al. (2015) and Rueckauer et al.
(2017); the Izhikevich model is from Izhikevich (2003).
MIT. Copyright (c) 2026 Jeremiah Mackey.