Verified neural units + tools (neural-ddr)
Browse files- DBI.pt +3 -0
- DDR5_units.pt +3 -0
- LICENSE +21 -0
- README.md +62 -0
- neural_ddr/__init__.py +0 -0
- neural_ddr/addr_map.py +43 -0
- neural_ddr/bridge.py +57 -0
- neural_ddr/cmd_decode.py +37 -0
- neural_ddr/common.py +52 -0
- neural_ddr/crc.py +60 -0
- neural_ddr/dbi.py +130 -0
- neural_ddr/ddr5_bridge.py +38 -0
- neural_ddr/ecc.py +92 -0
- requirements.txt +1 -0
- step1_train_verify.py +95 -0
- step2to6_train_verify.py +95 -0
DBI.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2d2b79f7d4faf8495deb2e175dad1dd752b16722bd3e64b2d013d80fe38a49b3
|
| 3 |
+
size 154354
|
DDR5_units.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0d12c4287f9af7967e266f34b872a7c7e6ae8d88ade42067486c5243f0942976
|
| 3 |
+
size 3077986
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Dean Byrne (Quazim0t0)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
tags:
|
| 5 |
+
- verified-units
|
| 6 |
+
- neural-memory
|
| 7 |
+
- ddr5
|
| 8 |
+
- ecc
|
| 9 |
+
- retrocomputing
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# neural-ddr — verified DDR5 memory-logic units
|
| 13 |
+
|
| 14 |
+
Small neural nets that **emulate DDR5 memory logic**, each verified **bit-identical
|
| 15 |
+
to a golden reference over its entire finite input domain (N/N)** — the same
|
| 16 |
+
methodology as [neural-aarch64-units](https://huggingface.co/Quazim0t0/neural-aarch64-units).
|
| 17 |
+
|
| 18 |
+
A dev tool: present DDR5 behaviour (bus encoding, addressing, command decode,
|
| 19 |
+
write-CRC, **on-die ECC**) on a machine that only has DDR3/DDR4. The bytes live in
|
| 20 |
+
the host's real RAM; the neural nets emulate the *logic*.
|
| 21 |
+
|
| 22 |
+
> **Honest by design:** no capacity or speed is invented. This models the
|
| 23 |
+
> data-path logic; it does not turn DDR3 silicon into DDR5.
|
| 24 |
+
|
| 25 |
+
## Units
|
| 26 |
+
|
| 27 |
+
| unit | role | verification |
|
| 28 |
+
|------|------|--------------|
|
| 29 |
+
| `DBI` | data-bus-inversion encode/decode (DDR4/5) | 256/256 · 512/512 |
|
| 30 |
+
| `ADDR_MAP` | address → bank-group/bank/row/column | 4096/4096 |
|
| 31 |
+
| `CMD_DECODE` | control lines → DDR command | 32/32 |
|
| 32 |
+
| `WR_CRC` | write-CRC bit-slice (rippled) | 512/512 |
|
| 33 |
+
| `ODECC` | DDR5 on-die ECC — corrects a single-bit flip | 256/256 · 3328/3328 |
|
| 34 |
+
| `DDR5Bridge` | composes the units over real host RAM | 2048/2048 faults corrected |
|
| 35 |
+
|
| 36 |
+
## Use
|
| 37 |
+
|
| 38 |
+
```bash
|
| 39 |
+
pip install torch
|
| 40 |
+
python step1_train_verify.py # DBI + memory bridge
|
| 41 |
+
python step2to6_train_verify.py # DDR5 units + ECC-correcting bridge
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
Weights: `DBI.pt`, `DDR5_units.pt`.
|
| 45 |
+
|
| 46 |
+
**Create your own verified unit** (template: `neural_ddr/dbi.py`): write the exact
|
| 47 |
+
golden finite function → enumerate the domain (decompose big/linear ones into
|
| 48 |
+
bit/byte slices, see `crc.py`/`ecc.py`) → `common.train` → `common.verify` must be
|
| 49 |
+
bit-exact on 100% of inputs → compose (`ddr5_bridge.py`).
|
| 50 |
+
|
| 51 |
+
## Citation
|
| 52 |
+
|
| 53 |
+
```bibtex
|
| 54 |
+
@misc{byrne2026neuralddr,
|
| 55 |
+
title = {neural-ddr: Verified DDR5 Memory Logic Units},
|
| 56 |
+
author = {Byrne, Dean (Quazim0t0)},
|
| 57 |
+
year = {2026},
|
| 58 |
+
howpublished = {\url{https://huggingface.co/Quazim0t0/neural-ddr}}
|
| 59 |
+
}
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
**Dean Byrne (Quazim0t0)** · 2026
|
neural_ddr/__init__.py
ADDED
|
File without changes
|
neural_ddr/addr_map.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Step 2: ADDR_MAP -- physical address -> (rank/bank-group/bank/row/column).
|
| 3 |
+
|
| 4 |
+
DDR performance depends on which address bits pick the bank vs the row/column,
|
| 5 |
+
because interleaving consecutive addresses across banks/bank-groups exposes
|
| 6 |
+
parallelism. That mapping is a fixed bit-permutation -> a finite function we can
|
| 7 |
+
verify N/N, and it is exactly the kind of per-generation policy the bridge swaps.
|
| 8 |
+
|
| 9 |
+
Small but representative geometry (12-bit address, 4096 configs):
|
| 10 |
+
column = 4 bits, bank = 2, bank-group = 2, row = 4
|
| 11 |
+
laid out as an interleave (bank/bg taken from interior bits for parallelism).
|
| 12 |
+
"""
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
import torch
|
| 15 |
+
from .common import bits_of, int_of, pm
|
| 16 |
+
|
| 17 |
+
ADDR_BITS = 12
|
| 18 |
+
# output bit index -> which input address bit feeds it (the addressing scheme)
|
| 19 |
+
PERM = [0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11] # [col4][bank2][bg2][row4]
|
| 20 |
+
FIELDS = [("col", 4), ("bank", 2), ("bg", 2), ("row", 4)]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def golden_map_bits(addr: int) -> list[int]:
|
| 24 |
+
ib = bits_of(addr, ADDR_BITS)
|
| 25 |
+
return [ib[PERM[o]] for o in range(ADDR_BITS)]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def decode_fields(outbits) -> dict:
|
| 29 |
+
res, i = {}, 0
|
| 30 |
+
for name, w in FIELDS:
|
| 31 |
+
res[name] = int_of(outbits[i:i + w]); i += w
|
| 32 |
+
return res
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def domain():
|
| 36 |
+
X = torch.stack([pm(bits_of(a, ADDR_BITS)) for a in range(2 ** ADDR_BITS)])
|
| 37 |
+
Y = torch.tensor([[float(b) for b in golden_map_bits(a)] for a in range(2 ** ADDR_BITS)])
|
| 38 |
+
return X, Y
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def run(net, addr: int) -> dict:
|
| 42 |
+
y = (net(pm(bits_of(addr, ADDR_BITS)).unsqueeze(0))[0] > 0).int().tolist()
|
| 43 |
+
return decode_fields(y)
|
neural_ddr/bridge.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
The memory bridge: real host RAM as the storage array, DDR bus LOGIC emulated by
|
| 3 |
+
verified neural units.
|
| 4 |
+
|
| 5 |
+
This is the honest core of the "run it on your old PC" idea. The bytes live in a
|
| 6 |
+
buffer in the machine's actual DDR3/DDR4. Every access is routed through the
|
| 7 |
+
neural DDR units for a CHOSEN generation, so the bridge presents that
|
| 8 |
+
generation's *behavior* over ordinary host memory:
|
| 9 |
+
|
| 10 |
+
* DDR3 -> no DBI: bytes drive the bus as-is.
|
| 11 |
+
* DDR4/5 -> DBI: the verified neural DBI unit encodes each byte onto the
|
| 12 |
+
"bus" (>4 zeros -> invert + flag), so <=4 DQ lines are ever
|
| 13 |
+
LOW; reads decode it back, bit-exact.
|
| 14 |
+
|
| 15 |
+
No capacity or speed is created: this models the data-path logic, it does not
|
| 16 |
+
turn DDR3 into DDR5 silicon.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from .dbi import NeuralDBIEncode, NeuralDBIDecode
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class MemoryBridge:
|
| 25 |
+
def __init__(self, size: int, enc: NeuralDBIEncode, dec: NeuralDBIDecode,
|
| 26 |
+
generation: str = "DDR5"):
|
| 27 |
+
self.size = size
|
| 28 |
+
self.enc = enc
|
| 29 |
+
self.dec = dec
|
| 30 |
+
self.generation = generation
|
| 31 |
+
self.dbi = generation in ("DDR4", "DDR5") # DBI introduced in DDR4
|
| 32 |
+
# storage in real host RAM: the (possibly DBI-encoded) data byte + flag.
|
| 33 |
+
self._data = bytearray(size)
|
| 34 |
+
self._flag = bytearray(size)
|
| 35 |
+
# bus statistics
|
| 36 |
+
self.dq_low_total = 0
|
| 37 |
+
self.transfers = 0
|
| 38 |
+
|
| 39 |
+
def write(self, addr: int, value: int) -> None:
|
| 40 |
+
value &= 0xFF
|
| 41 |
+
if self.dbi:
|
| 42 |
+
enc, flag = self.enc.encode(value) # verified neural unit
|
| 43 |
+
else:
|
| 44 |
+
enc, flag = value, 0
|
| 45 |
+
self._data[addr] = enc
|
| 46 |
+
self._flag[addr] = flag
|
| 47 |
+
self.dq_low_total += 8 - bin(enc).count("1") # DQ lines driven LOW
|
| 48 |
+
self.transfers += 1
|
| 49 |
+
|
| 50 |
+
def read(self, addr: int) -> int:
|
| 51 |
+
enc, flag = self._data[addr], self._flag[addr]
|
| 52 |
+
if self.dbi:
|
| 53 |
+
return self.dec.decode(enc, flag) # verified neural unit
|
| 54 |
+
return enc
|
| 55 |
+
|
| 56 |
+
def avg_dq_low(self) -> float:
|
| 57 |
+
return self.dq_low_total / max(1, self.transfers)
|
neural_ddr/cmd_decode.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Step 3: CMD_DECODE -- control lines -> DDR command.
|
| 3 |
+
|
| 4 |
+
A DDR command is a truth table over the control pins (CS_n, ACT_n, and the
|
| 5 |
+
RAS_n/CAS_n/WE_n multiplexed lines). It is a finite function -> N/N verifiable.
|
| 6 |
+
(DDR5 uses a multi-cycle CA encoding; the table here is a compact, self-consistent
|
| 7 |
+
DDR4-style decode that the same unit shape can be retrained to for DDR5.)
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
import torch
|
| 11 |
+
from .common import bits_of, int_of, pm
|
| 12 |
+
|
| 13 |
+
IN, OUT = 5, 4
|
| 14 |
+
CMDS = {"DESELECT": 0, "ACTIVATE": 1, "NOP": 2, "READ": 3, "WRITE": 4,
|
| 15 |
+
"PRECHARGE": 5, "REFRESH": 6, "MRS": 7, "ZQ": 8}
|
| 16 |
+
NAME = {v: k for k, v in CMDS.items()}
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def golden_cmd(v: int) -> int:
|
| 20 |
+
cs, act, ras, cas, we = bits_of(v, 5) # bit0=CS_n .. bit4=WE_n
|
| 21 |
+
if cs == 1:
|
| 22 |
+
return CMDS["DESELECT"]
|
| 23 |
+
if act == 0:
|
| 24 |
+
return CMDS["ACTIVATE"]
|
| 25 |
+
table = {(1, 1, 1): "NOP", (1, 1, 0): "NOP", (1, 0, 1): "READ", (1, 0, 0): "WRITE",
|
| 26 |
+
(0, 1, 1): "PRECHARGE", (0, 1, 0): "REFRESH", (0, 0, 1): "MRS", (0, 0, 0): "ZQ"}
|
| 27 |
+
return CMDS[table[(ras, cas, we)]]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def domain():
|
| 31 |
+
X = torch.stack([pm(bits_of(v, IN)) for v in range(2 ** IN)])
|
| 32 |
+
Y = torch.tensor([[float(b) for b in bits_of(golden_cmd(v), OUT)] for v in range(2 ** IN)])
|
| 33 |
+
return X, Y
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def run(net, v: int) -> int:
|
| 37 |
+
return int_of((net(pm(bits_of(v, IN)).unsqueeze(0))[0] > 0).int().tolist())
|
neural_ddr/common.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared helpers: bit<->int, MLP factory, exhaustive verify, trainer."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
|
| 6 |
+
DEV = "cuda" if torch.cuda.is_available() else "cpu"
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def bits_of(v: int, n: int) -> list[int]:
|
| 10 |
+
return [(v >> k) & 1 for k in range(n)] # LSB-first
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def int_of(bits) -> int:
|
| 14 |
+
return sum((1 << k) for k, b in enumerate(bits) if b > 0)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def pm(bits) -> torch.Tensor:
|
| 18 |
+
return torch.tensor([1.0 if b else -1.0 for b in bits], dtype=torch.float32)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def mlp(inp: int, out: int, h: int = 256, layers: int = 2) -> nn.Sequential:
|
| 22 |
+
mods = [nn.Linear(inp, h), nn.GELU()]
|
| 23 |
+
for _ in range(layers - 1):
|
| 24 |
+
mods += [nn.Linear(h, h), nn.GELU()]
|
| 25 |
+
mods += [nn.Linear(h, out)]
|
| 26 |
+
return nn.Sequential(*mods)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@torch.no_grad()
|
| 30 |
+
def verify(net, X, Ybits) -> tuple[int, int]:
|
| 31 |
+
net = net.to("cpu")
|
| 32 |
+
pred = (net(X) > 0).int()
|
| 33 |
+
ok = (pred == Ybits.int()).all(dim=1).sum().item()
|
| 34 |
+
return ok, X.shape[0]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def train(net, X, Ybits, steps=8000, lr=2e-3, tag="", report=2000):
|
| 38 |
+
net = net.to(DEV)
|
| 39 |
+
Xd, Yd = X.to(DEV), Ybits.to(DEV)
|
| 40 |
+
opt = torch.optim.Adam(net.parameters(), lr=lr)
|
| 41 |
+
sch = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=steps)
|
| 42 |
+
lossfn = nn.BCEWithLogitsLoss()
|
| 43 |
+
for e in range(steps):
|
| 44 |
+
opt.zero_grad()
|
| 45 |
+
loss = lossfn(net(Xd), Yd)
|
| 46 |
+
loss.backward(); opt.step(); sch.step()
|
| 47 |
+
if e % report == 0 or e == steps - 1:
|
| 48 |
+
ok, tot = verify(net, X, Ybits); net.to(DEV)
|
| 49 |
+
print(f" [{tag}] epoch {e:5d} loss {loss.item():.2e} verified {ok}/{tot}")
|
| 50 |
+
if ok == tot:
|
| 51 |
+
print(f" [{tag}] -> N/N"); break
|
| 52 |
+
return net.to("cpu")
|
neural_ddr/crc.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Step 4: WR_CRC -- DDR4/5 write-CRC integrity.
|
| 3 |
+
|
| 4 |
+
CRC is linear over GF(2), so a full-burst CRC over 64+ bits can never be
|
| 5 |
+
enumerated -- but a single-bit slice can. We verify the CRC BIT-SLICE
|
| 6 |
+
exhaustively (state 8b + data bit -> new state, 512 configs) and ripple it,
|
| 7 |
+
exactly the byte-slice/ripple approach of the neural-aarch64 datapath.
|
| 8 |
+
|
| 9 |
+
Polynomial is configurable; default 0x07. Swap it to match the target JEDEC spec.
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
import torch
|
| 13 |
+
from .common import bits_of, int_of, pm
|
| 14 |
+
|
| 15 |
+
POLY = 0x07
|
| 16 |
+
IN, OUT = 9, 8
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def crc_bit(state: int, databit: int) -> int:
|
| 20 |
+
fb = ((state >> 7) & 1) ^ (databit & 1)
|
| 21 |
+
ns = (state << 1) & 0xFF
|
| 22 |
+
if fb:
|
| 23 |
+
ns ^= POLY
|
| 24 |
+
return ns & 0xFF
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def domain():
|
| 28 |
+
xs, ys = [], []
|
| 29 |
+
for s in range(256):
|
| 30 |
+
for d in (0, 1):
|
| 31 |
+
v = (d << 8) | s # bits0-7=state, bit8=databit
|
| 32 |
+
xs.append(pm(bits_of(v, 9)))
|
| 33 |
+
ys.append([float(b) for b in bits_of(crc_bit(s, d), 8)])
|
| 34 |
+
return torch.stack(xs), torch.tensor(ys)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def run_bit(net, state: int, databit: int) -> int:
|
| 38 |
+
v = (databit << 8) | state
|
| 39 |
+
return int_of((net(pm(bits_of(v, 9)).unsqueeze(0))[0] > 0).int().tolist())
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def crc_byte(net, state: int, byte: int) -> int:
|
| 43 |
+
for k in range(7, -1, -1): # MSB-first
|
| 44 |
+
state = run_bit(net, state, (byte >> k) & 1)
|
| 45 |
+
return state
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def crc_burst(net, data_bytes, init=0) -> int:
|
| 49 |
+
s = init
|
| 50 |
+
for b in data_bytes:
|
| 51 |
+
s = crc_byte(net, s, b)
|
| 52 |
+
return s
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def golden_burst(data_bytes, init=0) -> int:
|
| 56 |
+
s = init
|
| 57 |
+
for b in data_bytes:
|
| 58 |
+
for k in range(7, -1, -1):
|
| 59 |
+
s = crc_bit(s, (b >> k) & 1)
|
| 60 |
+
return s
|
neural_ddr/dbi.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Step 1 of neural-DDR: the DBI (Data Bus Inversion) unit, verified N/N.
|
| 3 |
+
|
| 4 |
+
Same methodology as the neural-aarch64 datapath units: DBI is a FIXED finite
|
| 5 |
+
function, so a small MLP can be trained until it is bit-identical to a golden
|
| 6 |
+
reference over its ENTIRE input domain (exhaustive N/N verification).
|
| 7 |
+
|
| 8 |
+
DBI is a real DDR4/DDR5 feature (absent in DDR3) -- so it is exactly the kind of
|
| 9 |
+
per-generation logic the bridge swaps in/out. On the data bus, DDR reads back
|
| 10 |
+
data over DQ lines that are terminated to a pull-up; a line driven LOW burns
|
| 11 |
+
termination power. DBI_dc reduces that: if a byte has more than four 0-bits,
|
| 12 |
+
invert all eight and flag it, so at most four lines are ever LOW.
|
| 13 |
+
|
| 14 |
+
encode(byte) -> 9 bits = [8 encoded data][1 DBI flag]
|
| 15 |
+
inverted = (number_of_zero_bits(byte) > 4)
|
| 16 |
+
data_out = ~byte & 0xFF if inverted else byte # <=4 zeros afterwards
|
| 17 |
+
flag = 1 if inverted else 0
|
| 18 |
+
decode(data_out, flag) -> byte
|
| 19 |
+
byte = ~data_out & 0xFF if flag else data_out
|
| 20 |
+
|
| 21 |
+
Neither storage nor capacity is invented here: this unit only emulates the
|
| 22 |
+
bus-encoding LOGIC. The bytes themselves live in the host's real RAM (bridge).
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import torch
|
| 28 |
+
import torch.nn as nn
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# ---------- golden reference (the finite function we must match) ----------
|
| 32 |
+
def golden_encode(byte: int) -> tuple[int, int]:
|
| 33 |
+
zeros = 8 - bin(byte & 0xFF).count("1")
|
| 34 |
+
if zeros > 4:
|
| 35 |
+
return (~byte) & 0xFF, 1
|
| 36 |
+
return byte & 0xFF, 0
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def golden_decode(data_out: int, flag: int) -> int:
|
| 40 |
+
return (~data_out) & 0xFF if flag else data_out & 0xFF
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# ---------- bit <-> tensor helpers (LSB-first, +/-1 encoding) ----------
|
| 44 |
+
def byte_bits(v: int, n: int = 8) -> torch.Tensor:
|
| 45 |
+
return torch.tensor([1.0 if (v >> k) & 1 else -1.0 for k in range(n)], dtype=torch.float32)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def bits_to_int(bits) -> int:
|
| 49 |
+
return sum((1 << k) for k, b in enumerate(bits) if b > 0)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# ---------- the neural units ----------
|
| 53 |
+
class NeuralDBIEncode(nn.Module):
|
| 54 |
+
"""8 data bits -> 9 bits (8 encoded data + 1 DBI flag)."""
|
| 55 |
+
|
| 56 |
+
def __init__(self):
|
| 57 |
+
super().__init__()
|
| 58 |
+
self.net = nn.Sequential(
|
| 59 |
+
nn.Linear(8, 128), nn.GELU(),
|
| 60 |
+
nn.Linear(128, 128), nn.GELU(),
|
| 61 |
+
nn.Linear(128, 9),
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
def forward(self, x):
|
| 65 |
+
return self.net(x)
|
| 66 |
+
|
| 67 |
+
@torch.no_grad()
|
| 68 |
+
def encode(self, byte: int) -> tuple[int, int]:
|
| 69 |
+
y = (self(byte_bits(byte).unsqueeze(0))[0] > 0).int().tolist()
|
| 70 |
+
return bits_to_int(y[:8]), int(y[8])
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class NeuralDBIDecode(nn.Module):
|
| 74 |
+
"""9 bits (8 data + flag) -> 8 recovered data bits."""
|
| 75 |
+
|
| 76 |
+
def __init__(self):
|
| 77 |
+
super().__init__()
|
| 78 |
+
self.net = nn.Sequential(
|
| 79 |
+
nn.Linear(9, 128), nn.GELU(),
|
| 80 |
+
nn.Linear(128, 128), nn.GELU(),
|
| 81 |
+
nn.Linear(128, 8),
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
def forward(self, x):
|
| 85 |
+
return self.net(x)
|
| 86 |
+
|
| 87 |
+
@torch.no_grad()
|
| 88 |
+
def decode(self, data_out: int, flag: int) -> int:
|
| 89 |
+
inp = torch.cat([byte_bits(data_out), torch.tensor([1.0 if flag else -1.0])]).unsqueeze(0)
|
| 90 |
+
y = (self(inp)[0] > 0).int().tolist()
|
| 91 |
+
return bits_to_int(y)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# ---------- exhaustive datasets + verification ----------
|
| 95 |
+
def encode_domain():
|
| 96 |
+
X = torch.stack([byte_bits(b) for b in range(256)])
|
| 97 |
+
Y = torch.stack([
|
| 98 |
+
torch.tensor([1.0 if (enc >> k) & 1 else -1.0 for k in range(8)] + [1.0 if fl else -1.0])
|
| 99 |
+
for enc, fl in (golden_encode(b) for b in range(256))
|
| 100 |
+
])
|
| 101 |
+
return X, Y
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def decode_domain():
|
| 105 |
+
X, Y = [], []
|
| 106 |
+
for data_out in range(256):
|
| 107 |
+
for flag in (0, 1):
|
| 108 |
+
X.append(torch.cat([byte_bits(data_out), torch.tensor([1.0 if flag else -1.0])]))
|
| 109 |
+
b = golden_decode(data_out, flag)
|
| 110 |
+
Y.append(torch.tensor([1.0 if (b >> k) & 1 else -1.0 for k in range(8)]))
|
| 111 |
+
return torch.stack(X), torch.stack(Y)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@torch.no_grad()
|
| 115 |
+
def verify_encode(unit: NeuralDBIEncode) -> tuple[int, int]:
|
| 116 |
+
ok = 0
|
| 117 |
+
for b in range(256):
|
| 118 |
+
g_enc, g_fl = golden_encode(b)
|
| 119 |
+
n_enc, n_fl = unit.encode(b)
|
| 120 |
+
ok += (n_enc == g_enc and n_fl == g_fl)
|
| 121 |
+
return ok, 256
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@torch.no_grad()
|
| 125 |
+
def verify_decode(unit: NeuralDBIDecode) -> tuple[int, int]:
|
| 126 |
+
ok = 0
|
| 127 |
+
for data_out in range(256):
|
| 128 |
+
for flag in (0, 1):
|
| 129 |
+
ok += (unit.decode(data_out, flag) == golden_decode(data_out, flag))
|
| 130 |
+
return ok, 512
|
neural_ddr/ddr5_bridge.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Step 6: DDR5Bridge -- compose the verified units into a DDR5 logical interface
|
| 3 |
+
backed by the host's real RAM.
|
| 4 |
+
|
| 5 |
+
Write: byte -> neural ECC parity -> 12-bit codeword stored in host RAM
|
| 6 |
+
Read: 12-bit codeword -> neural ECC decode (corrects a single-bit flip) -> byte
|
| 7 |
+
|
| 8 |
+
`inject_fault` flips a bit in stored memory to emulate a DRAM disturbance; the
|
| 9 |
+
neural ECC unit corrects it on read. That is the DDR5 on-die-ECC guarantee,
|
| 10 |
+
presented on a machine that need not have DDR5 at all.
|
| 11 |
+
"""
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
from . import ecc
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class DDR5Bridge:
|
| 17 |
+
def __init__(self, size: int, enc_net, dec_net):
|
| 18 |
+
self.size = size
|
| 19 |
+
self.enc = enc_net
|
| 20 |
+
self.dec = dec_net
|
| 21 |
+
self.mem = [0] * size # host RAM: 12-bit ECC codewords
|
| 22 |
+
|
| 23 |
+
def write(self, addr: int, byte: int) -> None:
|
| 24 |
+
byte &= 0xFF
|
| 25 |
+
par = ecc.enc_run(self.enc, byte) # verified neural parity
|
| 26 |
+
d = [(byte >> k) & 1 for k in range(8)]
|
| 27 |
+
cw = [0] * 12
|
| 28 |
+
for i, pos in enumerate(ecc.DATA_POS):
|
| 29 |
+
cw[pos - 1] = d[i]
|
| 30 |
+
for i, j in enumerate(ecc.PAR_POS):
|
| 31 |
+
cw[j - 1] = par[i]
|
| 32 |
+
self.mem[addr] = sum((1 << k) for k, b in enumerate(cw) if b)
|
| 33 |
+
|
| 34 |
+
def read(self, addr: int) -> int:
|
| 35 |
+
return ecc.dec_run(self.dec, self.mem[addr]) # verified neural correct+decode
|
| 36 |
+
|
| 37 |
+
def inject_fault(self, addr: int, bitpos: int) -> None:
|
| 38 |
+
self.mem[addr] ^= (1 << bitpos) # emulate a single-bit disturbance
|
neural_ddr/ecc.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Step 5: ODECC -- DDR5 on-die ECC (the marquee DDR5 feature).
|
| 3 |
+
|
| 4 |
+
DDR5 adds on-die single-error-correction. We implement a concrete SEC Hamming
|
| 5 |
+
code -- Hamming(12,8): 8 data bits protected by 4 parity bits, correcting any
|
| 6 |
+
single-bit flip in the 12-bit codeword. Real DDR5 protects wider words; the
|
| 7 |
+
logic is identical, just more bits.
|
| 8 |
+
|
| 9 |
+
Two verified units:
|
| 10 |
+
ENC : 8 data bits -> 4 parity bits (domain 256, N/N)
|
| 11 |
+
DEC : 12-bit codeword -> 8 corrected data bits (all no-error + every
|
| 12 |
+
single-bit flip, 256*13 = 3328 inputs, N/N)
|
| 13 |
+
|
| 14 |
+
The DEC unit genuinely CORRECTS a flipped bit -- that is what lets the bridge
|
| 15 |
+
present ECC-protected memory over ordinary host RAM.
|
| 16 |
+
"""
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
import torch
|
| 19 |
+
from .common import bits_of, int_of, pm
|
| 20 |
+
|
| 21 |
+
DATA_POS = [3, 5, 6, 7, 9, 10, 11, 12] # 1-indexed Hamming positions
|
| 22 |
+
PAR_POS = [1, 2, 4, 8]
|
| 23 |
+
ENC_IN, ENC_OUT = 8, 4
|
| 24 |
+
DEC_IN, DEC_OUT = 12, 8
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def encode(data8: int) -> list[int]:
|
| 28 |
+
d = bits_of(data8, 8)
|
| 29 |
+
cw = {p: 0 for p in range(1, 13)}
|
| 30 |
+
for i, pos in enumerate(DATA_POS):
|
| 31 |
+
cw[pos] = d[i]
|
| 32 |
+
par = []
|
| 33 |
+
for j in PAR_POS:
|
| 34 |
+
p = 0
|
| 35 |
+
for pos in range(1, 13):
|
| 36 |
+
if pos in PAR_POS:
|
| 37 |
+
continue
|
| 38 |
+
if pos & j:
|
| 39 |
+
p ^= cw[pos]
|
| 40 |
+
par.append(p)
|
| 41 |
+
return par # for positions 1,2,4,8
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def codeword_int(data8: int) -> int:
|
| 45 |
+
d = bits_of(data8, 8)
|
| 46 |
+
par = encode(data8)
|
| 47 |
+
cw = [0] * 12 # index k = position k+1
|
| 48 |
+
for i, pos in enumerate(DATA_POS):
|
| 49 |
+
cw[pos - 1] = d[i]
|
| 50 |
+
for i, j in enumerate(PAR_POS):
|
| 51 |
+
cw[j - 1] = par[i]
|
| 52 |
+
return int_of(cw)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def decode(cw_int: int) -> int:
|
| 56 |
+
cw = bits_of(cw_int, 12)
|
| 57 |
+
syn = 0
|
| 58 |
+
for j in PAR_POS:
|
| 59 |
+
s = 0
|
| 60 |
+
for pos in range(1, 13):
|
| 61 |
+
if pos & j:
|
| 62 |
+
s ^= cw[pos - 1]
|
| 63 |
+
if s:
|
| 64 |
+
syn |= j
|
| 65 |
+
if 1 <= syn <= 12:
|
| 66 |
+
cw[syn - 1] ^= 1 # correct the flipped bit
|
| 67 |
+
return int_of([cw[pos - 1] for pos in DATA_POS])
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def enc_domain():
|
| 71 |
+
X = torch.stack([pm(bits_of(d, 8)) for d in range(256)])
|
| 72 |
+
Y = torch.tensor([[float(b) for b in encode(d)] for d in range(256)])
|
| 73 |
+
return X, Y
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def dec_domain():
|
| 77 |
+
xs, ys = [], []
|
| 78 |
+
for d in range(256):
|
| 79 |
+
base = codeword_int(d)
|
| 80 |
+
for e in range(13): # 0 = no error, 1..12 flip pos e
|
| 81 |
+
v = base if e == 0 else base ^ (1 << (e - 1))
|
| 82 |
+
xs.append(pm(bits_of(v, 12)))
|
| 83 |
+
ys.append([float(b) for b in bits_of(d, 8)])
|
| 84 |
+
return torch.stack(xs), torch.tensor(ys)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def enc_run(net, d: int) -> list[int]:
|
| 88 |
+
return (net(pm(bits_of(d, 8)).unsqueeze(0))[0] > 0).int().tolist()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def dec_run(net, cw_int: int) -> int:
|
| 92 |
+
return int_of((net(pm(bits_of(cw_int, 12)).unsqueeze(0))[0] > 0).int().tolist())
|
requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
torch
|
step1_train_verify.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Step 1 for neural-DDR: train + exhaustively verify the DBI units, then prove the
|
| 3 |
+
memory bridge round-trips real data through them.
|
| 4 |
+
|
| 5 |
+
(a) encode N/N -> DBI encode matches golden on all 256 bytes
|
| 6 |
+
(b) decode N/N -> DBI decode matches golden on all 512 (data,flag) inputs
|
| 7 |
+
(c) round-trip -> bridge stores every byte at many addresses via the neural
|
| 8 |
+
units and reads it back bit-exact (data integrity)
|
| 9 |
+
(d) DBI works -> DDR4/5 bridge never drives >4 DQ lines LOW; DDR3 does
|
| 10 |
+
(e) generation -> same host RAM, DDR3 vs DDR5 behavior, both correct
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
from neural_ddr.dbi import (
|
| 15 |
+
NeuralDBIEncode, NeuralDBIDecode, encode_domain, decode_domain,
|
| 16 |
+
verify_encode, verify_decode,
|
| 17 |
+
)
|
| 18 |
+
from neural_ddr.bridge import MemoryBridge
|
| 19 |
+
|
| 20 |
+
torch.manual_seed(0)
|
| 21 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def train(unit, X, Y, steps=12000, lr=2e-3, tag="", verify=None):
|
| 25 |
+
X, Y = X.to(dev), Y.to(dev)
|
| 26 |
+
unit = unit.to(dev)
|
| 27 |
+
opt = torch.optim.Adam(unit.parameters(), lr=lr)
|
| 28 |
+
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=steps)
|
| 29 |
+
for epoch in range(steps):
|
| 30 |
+
opt.zero_grad()
|
| 31 |
+
loss = (torch.tanh(unit(X)) - torch.tanh(Y * 4)).pow(2).mean()
|
| 32 |
+
loss.backward(); opt.step(); sched.step()
|
| 33 |
+
if epoch % 2000 == 0 or epoch == steps - 1:
|
| 34 |
+
n_ok, n_tot = verify(unit.cpu()); unit.to(dev)
|
| 35 |
+
print(f" [{tag}] epoch {epoch:5d} loss {loss.item():.2e} verified {n_ok}/{n_tot}")
|
| 36 |
+
if n_ok == n_tot:
|
| 37 |
+
print(f" [{tag}] -> full N/N reached")
|
| 38 |
+
break
|
| 39 |
+
return unit.cpu()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
if __name__ == "__main__":
|
| 43 |
+
print("=" * 60)
|
| 44 |
+
print("STEP 1 -- neural DBI units + memory bridge")
|
| 45 |
+
print("=" * 60)
|
| 46 |
+
|
| 47 |
+
Xe, Ye = encode_domain()
|
| 48 |
+
Xd, Yd = decode_domain()
|
| 49 |
+
print("training DBI encode (8 -> 9):")
|
| 50 |
+
enc = train(NeuralDBIEncode(), Xe, Ye, tag="enc", verify=verify_encode)
|
| 51 |
+
print("training DBI decode (9 -> 8):")
|
| 52 |
+
dec = train(NeuralDBIDecode(), Xd, Yd, tag="dec", verify=verify_decode)
|
| 53 |
+
|
| 54 |
+
ne, te = verify_encode(enc)
|
| 55 |
+
nd, td = verify_decode(dec)
|
| 56 |
+
print("-" * 60)
|
| 57 |
+
print(f"(a) DBI encode verified {ne}/{te} -> {'PASS' if ne==te else 'FAIL'}")
|
| 58 |
+
print(f"(b) DBI decode verified {nd}/{td} -> {'PASS' if nd==td else 'FAIL'}")
|
| 59 |
+
|
| 60 |
+
# (c) round-trip integrity through the bridge (DDR5 mode)
|
| 61 |
+
N = 4096
|
| 62 |
+
br5 = MemoryBridge(N, enc, dec, generation="DDR5")
|
| 63 |
+
bad = 0
|
| 64 |
+
for a in range(N):
|
| 65 |
+
v = (a * 37 + 11) & 0xFF
|
| 66 |
+
br5.write(a, v)
|
| 67 |
+
for a in range(N):
|
| 68 |
+
v = (a * 37 + 11) & 0xFF
|
| 69 |
+
bad += (br5.read(a) != v)
|
| 70 |
+
print(f"(c) bridge round-trip {N-bad}/{N} bytes exact -> {'PASS' if bad==0 else 'FAIL'}")
|
| 71 |
+
|
| 72 |
+
# (d) DBI bus effect: DDR5 caps DQ-low at 4/byte; DDR3 does not
|
| 73 |
+
br3 = MemoryBridge(256, enc, dec, generation="DDR3")
|
| 74 |
+
worst5 = 0
|
| 75 |
+
for v in range(256):
|
| 76 |
+
br5b = MemoryBridge(1, enc, dec, generation="DDR5"); br5b.write(0, v)
|
| 77 |
+
worst5 = max(worst5, int(br5b.dq_low_total))
|
| 78 |
+
br3.write(v % 256, v)
|
| 79 |
+
print(f"(d) DBI bus effect: max DQ-low/byte DDR5={worst5} (<=4?), "
|
| 80 |
+
f"avg DQ-low DDR3={br3.avg_dq_low():.2f} -> {'PASS' if worst5<=4 else 'FAIL'}")
|
| 81 |
+
|
| 82 |
+
# (e) generation swap over the SAME data, both correct
|
| 83 |
+
okg = True
|
| 84 |
+
for gen in ("DDR3", "DDR4", "DDR5"):
|
| 85 |
+
b = MemoryBridge(256, enc, dec, generation=gen)
|
| 86 |
+
for v in range(256):
|
| 87 |
+
b.write(v, v)
|
| 88 |
+
okg = okg and all(b.read(v) == v for v in range(256))
|
| 89 |
+
print(f"(e) generation swap DDR3/4/5 all round-trip -> {'PASS' if okg else 'FAIL'}")
|
| 90 |
+
|
| 91 |
+
if ne == te and nd == td:
|
| 92 |
+
torch.save({"encode": enc.state_dict(), "decode": dec.state_dict(),
|
| 93 |
+
"meta": {"unit": "DBI", "encode_verified": f"{ne}/{te}",
|
| 94 |
+
"decode_verified": f"{nd}/{td}"}}, "DBI.pt")
|
| 95 |
+
print(" saved verified unit -> DBI.pt")
|
step2to6_train_verify.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Steps 2-6: train + exhaustively verify ADDR_MAP, CMD_DECODE, WR_CRC, ODECC,
|
| 3 |
+
then compose them into a DDR5 bridge that corrects real bit-flips over host RAM.
|
| 4 |
+
"""
|
| 5 |
+
import random
|
| 6 |
+
import torch
|
| 7 |
+
from neural_ddr.common import mlp, train, verify
|
| 8 |
+
from neural_ddr import addr_map, cmd_decode, crc, ecc
|
| 9 |
+
from neural_ddr.ddr5_bridge import DDR5Bridge
|
| 10 |
+
|
| 11 |
+
torch.manual_seed(0)
|
| 12 |
+
random.seed(0)
|
| 13 |
+
R = {}
|
| 14 |
+
|
| 15 |
+
print("=" * 62)
|
| 16 |
+
print("STEPS 2-6 -- neural DDR5 units + bridge")
|
| 17 |
+
print("=" * 62)
|
| 18 |
+
|
| 19 |
+
# ---- Step 2: ADDR_MAP ----
|
| 20 |
+
print("Step 2 ADDR_MAP (12 -> 12):")
|
| 21 |
+
Xa, Ya = addr_map.domain()
|
| 22 |
+
net_addr = train(mlp(12, 12, 256, 2), Xa, Ya, steps=8000, tag="addr")
|
| 23 |
+
R["ADDR_MAP"] = verify(net_addr, Xa, Ya)
|
| 24 |
+
|
| 25 |
+
# ---- Step 3: CMD_DECODE ----
|
| 26 |
+
print("Step 3 CMD_DECODE (5 -> 4):")
|
| 27 |
+
Xc, Yc = cmd_decode.domain()
|
| 28 |
+
net_cmd = train(mlp(5, 4, 64, 2), Xc, Yc, steps=6000, tag="cmd")
|
| 29 |
+
R["CMD_DECODE"] = verify(net_cmd, Xc, Yc)
|
| 30 |
+
|
| 31 |
+
# ---- Step 4: WR_CRC (bit-slice) ----
|
| 32 |
+
print("Step 4 WR_CRC bit-slice (9 -> 8):")
|
| 33 |
+
Xr, Yr = crc.domain()
|
| 34 |
+
net_crc = train(mlp(9, 8, 128, 2), Xr, Yr, steps=6000, tag="crc")
|
| 35 |
+
R["WR_CRC"] = verify(net_crc, Xr, Yr)
|
| 36 |
+
|
| 37 |
+
# ---- Step 5: ODECC (encode + decode/correct) ----
|
| 38 |
+
print("Step 5 ODECC encode (8 -> 4):")
|
| 39 |
+
Xe, Ye = ecc.enc_domain()
|
| 40 |
+
net_enc = train(mlp(8, 4, 256, 3), Xe, Ye, steps=15000, tag="ecc-enc")
|
| 41 |
+
R["ODECC_ENC"] = verify(net_enc, Xe, Ye)
|
| 42 |
+
print("Step 5 ODECC decode/correct (12 -> 8):")
|
| 43 |
+
Xd, Yd = ecc.dec_domain()
|
| 44 |
+
net_dec = train(mlp(12, 8, 512, 3), Xd, Yd, steps=20000, tag="ecc-dec")
|
| 45 |
+
R["ODECC_DEC"] = verify(net_dec, Xd, Yd)
|
| 46 |
+
|
| 47 |
+
print("-" * 62)
|
| 48 |
+
for name, (ok, tot) in R.items():
|
| 49 |
+
print(f" {name:12s} verified {ok}/{tot} -> {'PASS' if ok == tot else 'FAIL'}")
|
| 50 |
+
|
| 51 |
+
# ---- behavioural checks ----
|
| 52 |
+
print("-" * 62)
|
| 53 |
+
# CMD_DECODE full table
|
| 54 |
+
cmd_ok = all(cmd_decode.run(net_cmd, v) == cmd_decode.golden_cmd(v) for v in range(32))
|
| 55 |
+
print(f"CMD_DECODE truth table (32/32): {'PASS' if cmd_ok else 'FAIL'} "
|
| 56 |
+
f"e.g. 0b00101 -> {cmd_decode.NAME[cmd_decode.run(net_cmd, 0b00101)]}")
|
| 57 |
+
|
| 58 |
+
# ADDR_MAP sample
|
| 59 |
+
f = addr_map.run(net_addr, 0xABC)
|
| 60 |
+
print(f"ADDR_MAP 0xABC -> col={f['col']} bank={f['bank']} bg={f['bg']} row={f['row']}")
|
| 61 |
+
|
| 62 |
+
# CRC burst vs golden
|
| 63 |
+
bad = 0
|
| 64 |
+
for _ in range(200):
|
| 65 |
+
msg = [random.randint(0, 255) for _ in range(8)]
|
| 66 |
+
bad += (crc.crc_burst(net_crc, msg) != crc.golden_burst(msg))
|
| 67 |
+
print(f"WR_CRC 8-byte bursts (200): {'PASS' if bad == 0 else f'FAIL({bad})'} "
|
| 68 |
+
f"(neural ripple == golden CRC)")
|
| 69 |
+
|
| 70 |
+
# ---- Step 6: DDR5 bridge with real bit-flip correction ----
|
| 71 |
+
N = 2048
|
| 72 |
+
br = DDR5Bridge(N, net_enc, net_dec)
|
| 73 |
+
for a in range(N):
|
| 74 |
+
br.write(a, (a * 89 + 7) & 0xFF)
|
| 75 |
+
# inject one random single-bit fault per location, then read back
|
| 76 |
+
corrected = 0
|
| 77 |
+
for a in range(N):
|
| 78 |
+
br.inject_fault(a, random.randint(0, 11))
|
| 79 |
+
if br.read(a) == ((a * 89 + 7) & 0xFF):
|
| 80 |
+
corrected += 1
|
| 81 |
+
print(f"DDR5Bridge SEC: {corrected}/{N} bytes correct after a random bit-flip each "
|
| 82 |
+
f"-> {'PASS' if corrected == N else 'FAIL'}")
|
| 83 |
+
|
| 84 |
+
allpass = all(ok == tot for ok, tot in R.values()) and cmd_ok and bad == 0 and corrected == N
|
| 85 |
+
print("=" * 62)
|
| 86 |
+
print(f"OVERALL: {'ALL PASS' if allpass else 'some checks failed'}")
|
| 87 |
+
|
| 88 |
+
if all(ok == tot for ok, tot in R.values()):
|
| 89 |
+
torch.save({
|
| 90 |
+
"addr_map": net_addr.state_dict(), "cmd_decode": net_cmd.state_dict(),
|
| 91 |
+
"wr_crc": net_crc.state_dict(), "odecc_enc": net_enc.state_dict(),
|
| 92 |
+
"odecc_dec": net_dec.state_dict(),
|
| 93 |
+
"meta": {k: f"{v[0]}/{v[1]}" for k, v in R.items()},
|
| 94 |
+
}, "DDR5_units.pt")
|
| 95 |
+
print("saved verified units -> DDR5_units.pt")
|