A 16-neuron Leaky-Integrate-and-Fire (LIF) spiking neural network designed to learn a compact temporal representation of machine state from live hardware and node telemetry, targeting a Xilinx Artix-7 FPGA. That is the intended architecture; nothing here demonstrates that the shipped weights learned such a representation — see Status below.
Spikenaut is the small-supervisor layer of a wider research program, Artificial Interoception / Neuromorphic Supervisor (#7). This repository holds the model artifact — the shipped weight files and the Q8.8 export contract. Two caveats the name invites: the weights are not established as trained (Status, below), and there is no decision contract — nothing in this repository or its history defines what the three output rows mean.
Spikenaut-SNN-v2 architecture: Input to Linear to LIF to Output, with the Q8.8 parameter file feeding each stage and the caveat attached to each
Status — read this first
On "this repository". The .mem artifacts, snn_model.json and config.json published here are
byte-identical to the ones in the GitHub repository, which is
where the model's development history lives. Provenance statements below cite that history — deleted
scripts, commit hashes — because that is where the evidence is; they describe the very files in this
download.
This is a research artifact, not a validated supervisor. Four things a reader should know before using the shipped weights:
The hidden weight matrix is degenerate.
All 256 hidden weights follow a linear ramp, weight[n][i] = 0.75 + n×0.015625 + i×0.00390625. The ramp is verifiable from the artifact: that formula reproduces all 256 shipped values exactly. Whether it is trained output at all is not verifiable here — see the provenance row below. See #2.
The decay rates are placeholder values.
parameters_decay.mem is byte-identical (sha256 6db9fecb…) to the historical converted_parameters/spikenaut_snn_v2_decay.mem, produced by a deleted simple_convert.py::create_parameters whose docstring reads "Create sample parameters" and whose decay line is torch.linspace(0.8, 0.95, 16). They are evenly spaced defaults that were converted to Q8.8 (save_fpga_format runs every value through float_to_q8_8) but never learned — and they set every LIF time constant, since tau = -dt / ln(decay). The model's entire temporal behaviour rests on them. The same script's thresholds and hidden weights do not match the shipped files, so this applies to the decay rates alone.
The output layer is random, not trained.
parameters_output_weights.mem is byte-identical to a historical artifact generated by an unseeded torch.randn(3, 16) * 0.1 and exported without any training step. Any experiment reading the three output rows is evaluating a random readout. There is also no action contract for them: nothing in this repository or its history defines what rows 0-2 mean or how a score becomes a decision. Two ordered triples do exist in the deleted history, and neither is connected to these weights by anything in this repository: class_names = ['kaspa', 'monero', 'other'] in a demo notebook (blockchain classes, not actions), and {'observe': 0, 'buy': 1, 'sell': 2} in dataset/validate_legacy_data.py, which encoded an action field already present on an external trading log as an input feature, not a readout. Both are unlinked candidates, not the output contract. See #2.
Weight provenance is not traceable in this repo.
The hidden weights were imported from an external absolute path (/home/user/Eagle-Lander/DATA/research) by a script since deleted; no training run in this repository links fresh_sync_data.jsonl (8 records) to the shipped matrix. Treat the dataset and epoch count below as externally reported, not verified. See #13.
FPGA parity is unproven.
The hardware numbers below are Vivado synthesis and implementation reports, not board-measured results. Whether the deployed SNN behaves like the software model is an open question, not a settled one. See #6.
The architecture and the Q8.8 export contract are verifiable from the artifacts in this repository. The FPGA resource and power figures are externally reported — no RTL, constraints, Vivado project or synthesis reports are checked in here, so a reader cannot reproduce them from this artifact. Every shipped parameter needs validation or replacement, not the weights alone: the hidden weights are degenerate, the thresholds externally sourced, the decay rates sample defaults that set every LIF time constant, and the output layer random. Retraining the weights while keeping the rest leaves the model's temporal dynamics and its readout unvalidated.
The research question
Can an event-driven SNN maintain a useful temporal representation of an AI system's internal computational state and learn bounded supervisory behavior, while deterministic software remains responsible for hard safety constraints?
A second question: can a larger GPU temporal model teach useful supervisory behavior into a small SNN that eventually runs on dedicated FPGA hardware?
The program is explicitly designed to be able to fail. Demonstrating that a simple baseline beats the SNN, that online plasticity destabilizes it, or that FPGA quantization loses key behavior are all useful outcomes. The requirement is measured evidence, not preserving the hypothesis.
System model
text
1ENVIRONMENT / BODY hardware + workloads + market simulation
2 ↓
3SENSORY DATA telemetry, node sync, paper trajectories
4 ↓
5TEMPORAL RESEARCH MODEL LiquidCortex.jl and simple baselines
6 ↓
7DISTILLATION SynapticDistill.jl
8 ↓
9SMALL SUPERVISOR Spikenaut-SNN ← this repository
10 ↓
11HARDWARE DEPLOYMENT silicon-bridge → silicon-hdl / FPGA
12 ↓
13ACTION PROPOSAL
14 ↓
15HARD SAFETY SHIELD deterministic Rust governor
16 ↓
17BOUNDED ACTUATION / SHADOW EVALUATION
18 └──────────────────────────────────↺
Core safety principle
Learned systems propose; deterministic safety rules constrain.
No learned SNN, LLM, FPGA controller, or online-training loop may disable or raise hard thermal thresholds, override a deterministic emergency pause, silently continue after invalid or NaN model state, or gain unrestricted host control before shadow-mode evidence exists.
Architecture
Spec
Value
Neuron model
Leaky-Integrate-and-Fire (LIF)
Neurons
16
Input channels
16
Weight format
Q8.8 fixed-point
Learning rules
E-prop, OTTT, reward-modulated STDP
Clock
1 kHz (1 ms resolution)
Training speed
35 µs/tick
Memory footprint
1.6 KB
FPGA target
Xilinx Artix-7 xc7a35tcpg236-1 (Basys3)
Inputs — current v2 layout
Channels
Data Source
Function
0-1
DNX (Dynex)
PoUW solver health and neural baselines
2-3
Quai
Live on-chain reflex and sync confidence
4-5
Qubic
Epoch and tick cadence monitoring
6-7
Kaspa
High-frequency DAG settlement tracking
8-9
XMR (Monero)
Node stability and CPU L3 cache contention
10-11
Ocean
Data liquidity and staking prep
12-13
Verus
CPU-heavy validator tracking (AVX-512)
14-15
Thermal
Pain receptors — power and temperature
Channels 14-15 are intended as the network's pain receptors. This is design intent, not shipped behaviour. Nothing in this download acts on them: it contains parameters, not a runtime. The GitHub repository does carry code, and none of that is a runtime either — it verifies the Q8.8 export, lifts the model into a NIR graph, and its telemetry encoder reads channels 14-15 to turn them into spikes and report when a rejected frame touched them. That is routing and diagnostics, not a response: there is no reward signal, no online weight update, and nothing that acts on a temperature reading. The 85 °C threshold belongs to thalamic-relay, a peer process listed below, not to the SNN. Wiring a thermal penalty into training is future work.
Which map the shipped weights use is unknown
The table above is the proposed v2 layout. A second, different layout is recorded in this repository's history, and the two disagree in every column — so at most one of them can describe the shipped weights, and nothing here establishes which, or whether either does. The historical encoder — dataset/generate_spike_data.py, deleted in 50a2627 but recoverable from history — declares:
Every column is assigned differently. But be careful how much this settles: it records what this encoder produced, and — as the provenance section below sets out — no training run in this repository links that encoder or fresh_sync_data.jsonl to the shipped matrix, which was imported from an external path. So this is the layout the historical encoder wrote, not a verified statement of what the shipped weights expect. Treat it as the best available hypothesis about the columns and the only one with code behind it; a traceable training artifact would be needed to promote it to fact. What can be said without qualification is that the two maps disagree in every column, so the v2 table above is not a safe guide either.
Running that encoder end to end over the eight records — including create_spike_train, which allocates np.zeros(16) per record and overwrites only the channels that event touches — gives its normalized_values matrix. This is the rate the encoder assigns each channel, not the spikes it emits; the distinction matters and is taken up below the table.
Two things about this table are easy to get wrong, and I got both wrong before review caught them.
It is rates, not spikes.temporal_encoding turns a normalized value into spike_rate = normalized * 100 Hz, then spike_prob = spike_rate / 1000 per 1 ms tick, then draws 1 if np.random.random() < spike_prob else 0. So the emitted spike_vector is a Bernoulli sample, and even a channel pinned at normalized 1.0 fires with probability 0.1 per tick. Channel 3 sitting at 1.0 across its four kaspa records yields roughly 0.4 expected spikes, not four. Nothing here licenses a claim about the realized spike train.
There is also no np.random.seed anywhere in the encoder, so rerunning it reproduces the rate table above exactly and its spike_vector never. Stated narrowly on purpose: that is a fact about this script, not about the shipped weights. Since nothing here links this encoder to merged_v2, it does not establish that these were the training stimuli, nor that the real ones are lost — they may have been generated or retained somewhere outside this repository. What it does mean is that re-deriving the stimuli from this script is not a route to reproducing them.
The zero-fill still holds, and holds for both representations. A kaspa event never writes channels 4-11, a monero event never writes 0-3 or 8-11, so every one of channels 0-7 is zero on half the records — and a normalized 0 gives spike_prob 0, meaning those halves emit no spikes at all, deterministically. Channel 3 is the extreme case: 1,1,1,1,0,0,0,0 in rate terms, so it can only ever fire on a kaspa record, though on any given run it will mostly not fire at all. Asymmetric, not a clean flag. Channels 0-2 and 4-7 carry the same asymmetry folded into their magnitude.
Five of sixteen channels are dead, not four. Channels 8-11 have no source records at all. Channel 12 is dead for a different and more interesting reason: it is computed as (gpu_temp_c - 40) / 6, giving roughly 0.30-0.88, and then passed to temporal_encoding(..., 'temp'), which normalizes with (value - 40) / 6again and clips at zero. The double subtraction lands every record at zero. Channel 13 carries the same second normalization — power_eff / 5 lands near 0.5, and the 'hashrate' branch then subtracts exactly 0.5 — so six of the eight records clip to zero. It survives on records 3 and 4 alone, where power_eff / 5 is 0.505806 and 0.515066 and genuinely clears the threshold. That residual activity is real signal squeezed through a wrong subtraction, not a rounding artifact.
Channels 14 and 15 do carry signal that 0-11 do not: channels 0-11 read only hashrate_mh, power_w, gpu_temp_c and qubic_tick_trace, while 14 additionally consumes qubic_epoch_progress and 15 consumes reward_hint. Those two fields reach the network only through 14 and 15, so neither channel is redundant — a point that matters when deciding what to keep or repair in a retrain.
What is left, then, is a 16-wide input in which five channels are always zero — deterministically, in rates and spikes alike — one is near-constant, eight (channels 0-7) are silent on exactly half the records — each is written on 4 of the 8, since blockchain routes 0-3 to kaspa records and 4-7 to monero — and every record makes exactly eight attempted writes of the sixteen — but two of those encode to zero, so the actual count of nonzero normalized inputs is six on records 1, 2 and 5-8 and seven on records 3-4. The emitted spike vector is sparser still, since each nonzero value is only a per-tick probability. That is consistent with the degenerate ramp described below, though — like the ramp's cause — not a link this repository can demonstrate, since no training run here connects the dataset to the parameters.
The v2 map is a proposal, not a fixed contract. The replacement state contract is being defined in #20, under one governing rule: logical state variables are not equivalent to physical SNN axons. Signals are never invented or duplicated just to fill 16 slots. Instead an explicit adapter sits between them:
raw state → state adapter → encoder → fixed-width SNN stimuli
The adapter accepts a variable number of legitimate source signals, so the raw feature count can change without the input contract breaking. Every signal must declare its unit, source of truth, timestamp and sampling semantics, valid range, normalization, missing-value and staleness behavior, and provenance. Missing signals are masked, never silently zeroed.
Merged v2 parameters
Parameter
Source
Values
Thresholds (16)
Externally sourced
Graduated 1.125 to 1.594 per neuron
Decay rates (16)
Sample defaults — converted, but never trained
torch.linspace(0.8, 0.95, 16) put through Q8.8, see below
Hidden weights (256)
Externally sourced — degenerate, see below
Range 0.75 to 1.04, 76 unique values
Output weights (48)
Randomly initialized — never trained
Signed: -0.164 to +0.258
The hidden weight matrix is the known problem. Each neuron's 16 weights increase by exactly one Q8.8 step (0x0001, 0.0039):
A healthy trained SNN shows weights moving up and down across a neuron's inputs. #2 attributes the ramp to degenerate training convergence — too few samples, no inhibitory connections, and identical E-prop/OTTT gradients across neurons. Externally reported, like the rest of that diagnosis: no training run in this repository links any of those conditions, or the eight-record dataset, to the externally imported matrix. What is verifiable here is the ramp itself and that it is not an export bug. The export path was independently cross-validated and confirmed correct (#4).
Q8.8 fixed-point format
All .mem files use Q8.8 fixed-point encoding. Each line is one 4-digit hex value:
The artifacts are the product. Nothing here runs the network — there is no inference runtime in this
repository, and no code that acts on a reading.
The tooling that checks these files lives in the GitHub
repository, not here:
tools/verify_q88.py re-derives every Q8.8 word from the JSON floats and compares it against the
.mem files (its --self-test proves it can fail), and the spikenaut-snn Rust crate decodes
snn_model.json and lifts it into a NIR graph. Both read exactly the
artifacts published here, so the checks are reproducible against this download.
Loading in Python
Standard library only — the .mem files are ASCII hex, one 4-digit word per line.
python
1from pathlib import Path
23defload_q8_8(path, signed=False):4"""Read a .mem file of 4-digit hex words into floats."""5 out =[]6for word in Path(path).read_text().split():7 code =int(word,16)8if signed and code >=0x8000:# two's complement9 code -=0x1000010 out.append(code /256.0)11return out
1213base = Path("dataset/merged_v2")14thresholds = load_q8_8(base /"parameters.mem")# 1615decays = load_q8_8(base /"parameters_decay.mem")# 1616weights = load_q8_8(base /"parameters_weights.mem")# 25617outputs = load_q8_8(base /"parameters_output_weights.mem", signed=True)# 481819W =[weights[n *16:(n +1)*16]for n inrange(16)]# row n = neuron n
signed=True matters only for the output layer; the thresholds, decay rates and hidden weights are
all positive in this artifact. Running the above reproduces the figures quoted in this card:
thresholds 16: 1.125 .. 1.59375
decays 16: 0.796875 .. 0.94921875
weights 256: 0.75 .. 1.04296875
outputs 48: min -0.1640625 max 0.2578125
matrix 16x16, all positive: True
If those numbers do not match what you loaded, the download is not this artifact. The
GitHub repository carries a fuller checker,
tools/verify_q88.py, which re-derives every word from snn_model.json rather than trusting the
.mem files.
Loading on FPGA
verilog
1// Load thresholds from Q8.8 hex file2reg[15:0] threshold_ram [0:15];3initial$readmemh("dataset/merged_v2/parameters.mem", threshold_ram);45// Load weights from Q8.8 hex file6reg[15:0] weight_ram [0:255];7initial$readmemh("dataset/merged_v2/parameters_weights.mem", weight_ram);
Training provenance
Externally reported. No training run in this repository produces the shipped weights. The figures below come from the historical record; the parameters themselves were imported from an external path by a script that has since been deleted, and the output layer was never trained at all (see Status above). They are recorded here for continuity, not as verified provenance.
Externally reported diagnosis, not established here.#2 attributes the ramp to the 8-record set: two of its six features (qubic_epoch_progress, reward_hint) are effectively constant — range 0.0009, standard deviation 0.000284 — while dominating spike encoding at a reported 87.5% spike rate each. Both halves of that last clause are wrong against the encoder, and are recorded here only because #2 states them: qubic_epoch_progress has no channel of its own — it reaches the network only after being averaged with qubic_tick_trace on channel 14 — and neither field produces 87.5%. The 'qubic' branch sets spike_rate = clip(value, 0, 1) * 100, giving channel 14 a mean of 97.80 Hz and channel 15 99.98 Hz, which at the 1 ms event step is a spike probability near 0.10, not 0.875. The constancy claim does hold. But since no training run in this repository links that dataset to the shipped matrix, the causal claim cannot be checked from here. What is verifiable from the artifact is the ramp itself.
The issue also cites monotonically converging sync data (0.999912 → 1.0) as producing single-attractor weights. That part does not survive checking, though not because the data is missing: sync_percentis present, on records 5-8, carrying exactly 0.999912, 0.999967, 0.999997, 1.0. It is excluded because encode_single_event never references sync_percent: it binds timestamp, telemetry and blockchain, and every channel value comes from a field inside telemetry. Note that top-level blockchaindoes reach the network — not as a channel value but as routing, selecting whether channels 0-3, 4-7 or 8-11 are written at all, which is what produces the zero-fill pattern above. sync_percent, block_rate and blocks_accepted are the ones never read. The nearest thing that did — reward_hint on channel 15, ranging 0.9991 to 1.0000 — is a real near-constant input and is a better candidate for the same argument.
A replacement corpus, qubic_ticks_snn.jsonl (~27,430 records), and a data adapter are reported in #2, but neither is present in this repository or anywhere in its history — treat both as external and currently uninspectable from the model card. The retrain has not happened, and the trainer needs fixing first (#13): it currently pins W_MIN = 0, row L1-normalizes, writes unsigned Q8.8, and emits no output-weight file.
Known limitations
Degenerate hidden weights. The linear-ramp matrix is verifiable from the artifact; its attribution to an 8-record training set and the retrain-on-27K remedy are both externally reported by #2 and unverifiable here, since no training run in this repository links either dataset to the shipped matrix. Reported fix path: retrain on the 27K-record corpus, add ~4 inhibitory neurons (80:20 E:I), implement K-WTA sparsity. #2, #3, #13
Placeholder decay rates set every LIF time constant. The 16 decay rates are torch.linspace(0.8, 0.95, 16) sample defaults. They were converted to Q8.8 — by simple_convert.py::save_fpga_format, whose float_to_q8_8 is int(value * 256), truncating rather than rounding — but never trained: the conversion started from a linspace, not from learned values. Since tau = -dt/ln(decay_rate) at dt = 1 ms, they fix the membrane time constants at 4.40 ms through 19.19 ms (measured from the shipped file, not from the source linspace) by accident rather than by fit. Retraining the weights does not touch them — they need validation or replacement in their own right. #13
Purely excitatory hidden layer. All 256 hidden weights are positive. What this categorically prevents is lateral competition: with no cross-neuron inhibition there is no winner-take-all, no surround suppression and no divisive normalization. It does not make noise rejection or contrast selectivity impossible — the threshold gates spiking, so subthreshold input is integrated into the membrane potential but does not reach the output, and the graduated thresholds (1.125-1.594) set that gate differently per neuron, which is heterogeneous selectivity without any recurrence. Note it is not memoryless: each LIF neuron's decaying membrane potential retains recent input, so the gap is long-horizon and recurrent memory, not temporal state as such. #3
No FPGA parity evidence. Spike agreement, action agreement, membrane-potential error, and quantization error against the software model have not been measured. Hardware numbers below are synthesis reports. #6
Export tooling clamps negatives.silicon-bridge's encode_q88 currently clamps negative values to zero, which would destroy the signed parameters_output_weights.mem. Signed Q8.8 is a hard requirement before that path is adopted. #15
The output layer is random and has no float source. The 48 signed values in parameters_output_weights.mem were produced by an unseeded torch.randn(3, 16) * 0.1 with no training step, so the three output rows are a random readout. snn_model.json records only the 16 hidden-layer neurons, so they also have no float counterpart here to cross-validate against. They are checked structurally instead (count, encoding, round-trip, sign integrity). A sign-preserving exporter regression would go undetected until the output layer is added to snn_model.json. #4
Upstream dataset hygiene. Sibling telemetry datasets still carry dead columns, schema drift, mixed timestamp formats, synthetic tail records, and stuck values. #2, #3
Hardware baseline
Vivado synthesis and implementation reports for the Basys3 target. These are tool estimates, not board-measured figures.
Component
Spec
CPU
AMD Ryzen 9 9950X
GPU
NVIDIA RTX 5080 (Blackwell SM_120)
FPGA
Digilent Basys3 (Xilinx Artix-7 xc7a35tcpg236-1)
FPGA power
97 mW total (25 mW dynamic, 72 mW static)
FPGA LUTs
1,063 / 20,800 (5.11%)
FPGA registers
1,091 / 41,600 (2.62%)
Timing WNS
3.727 ns (37.27% margin)
OS
Fedora 44
Per the program's evidence rules, any efficiency claim must rest on measured system or hardware evidence rather than spike-operation counts alone. The power figure above does not yet meet that bar.
Roadmap
The program advances through a milestone ladder (#7). Current stage: M0.
Stage
Goal
Exit criterion
M0
Data contracts, no learned control
One session traceable from raw telemetry to a deterministic training record, with hashes and no leakage
M1
Machine Interoception Benchmark v1
Reproducible results table showing where temporal models help or fail, against persistence / linear / memoryless baselines
M2
Teacher → student distillation
Held-out teacher/student agreement plus safety-sensitive disagreement metrics
M3
Bounded online adaptation
Adaptation moves a predeclared metric without breaking safety-sensitive error bounds
M4
FPGA parity
Machine-readable spike/action parity report with documented fixed-point and timing differences
M5
Assisted supervisor under hard Rust shield
Deterministic-only vs assisted controller compared on held-out workloads
M6
LLM / coding-agent nervous-system experiment
Only after M0–M5 produce usable evidence
M3 enforces a two-clock rule: a fast loop for telemetry → spikes → inference → proposal, and a slow loop for outcome → eligibility → bounded parameter update. Weights never change on every raw sensor sample.
Ecosystem
Spikenaut-SNN is the weights and model repository for a larger program. The table below is the
component contract (#5), which deliberately
distinguishes libraries the model repository builds on from peer processes it must not absorb.
Adopted marks what the Rust crate resolves from crates.io today; everything else remains intent.
The manifest that records this lives in the GitHub
repository — there is no build for it in this download.
Published crates are pinned from crates.io only — no git or path pins for adopted dependencies.
The Story
In 2013, a severe concussion left me unable to process the world's data the way I used to. Without access to neuro-rehabilitation, I decided to research on my own, and I started building what would become Spikenaut -- a neuromorphic system that learns from the raw signals of the machines I run every day. Originally inspired by the bottlenecks of my local GPU (RTX 5080), spent loads of money just to find out that I can't run massive LLM's on it for AI tutoring. So naturally my curious mind went on the internet to find alternatives. That is where I found Spiking Neural Networks, a low power alternative to traditional neural networks.
Unfortunately, the neuromorphic field is still in its early stages, and Spikenaut is just the beginning. I created Limen-Neural a GitHub organization over my experimental work in Neuromorphic computing. Meantime I have been modularizing all my work into reusable components in Limen-Neural. Feel free to check it out use the code to your liking, copy and use it in your own projects or use git dependencies. I'm still far from where I want it to be but I can guarantee you in a near future I will be there with benchmarks, docs with wiki and performance improvements.
As of the right now the weights are a mess, merged_v2 is where I am going to continue improving, the rest are more artifacts than anything. So expect updates over the time for new and improve SNN weights.
The name comes from "spike" (neural firing) and "naut" (navigator). This model is the brain -- the trained neural weights that turn raw telemetry into decisions.