Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/NOW.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# NOW — feat: GF-T 2-layer ReLU XOR net (2026-08-07)
# NOW — feat: GF-T on-chip XOR trainer (2026-08-07)

Last updated: 2026-08-07

## feat: GF-T 2-layer XOR trainer (fixed hidden + trainable output) (Refs #1764)

- **NEW** spec `specs/ternary/gft_xortrain.t27` — on-chip SGD step of a 2-layer XOR net: FIXED analytic hidden layer h0=relu(x0+x1), h1=relu(x0+x1-1) (features that make XOR linearly separable) + TRAINABLE output z=v0*h0+v1*h1 with hard-sigmoid + p-y gradient; `v_j' = v_j - eta*(p-y)*h_j`; returns (v0'<<32)|v1'
- `test` block PASS via `icarus-simulate`; a faithful GF-T Python sim converges to 4/4 XOR by epoch 9
- Proven on a live AX7203 via the SPLIT pattern: the trainable output layer learns on the proven `gft_logistic` bitstream (16.7M, streaming the host-computed hidden features h0,h1,label) -> XOR 4/4, v -> ~[1,-2]. As one single design (fasm 22.6M) it exceeds the measured openXC7 correctness ceiling (2nd confirmation); split big models: fixed part off-chip, trained part on a sub-ceiling bitstream
- Spec-only; no `gen/`/`coq/` edits; no new `*.sh`; Refs #1764

## feat: GF-T 2-layer ReLU network (solves XOR) — proven on AX7203 (Refs #1764)

- **NEW** spec `specs/ternary/gft_xornet.t27` — 2-layer ReLU forward pass: `h=relu(W*x+c)`, `y=v.h+b`. A single linear model cannot separate XOR; the hidden ReLU layer makes it nonlinearly separable. Reuses smul/sadd/relu
Expand Down
145 changes: 145 additions & 0 deletions specs/ternary/gft_xortrain.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
module GftXorTrain;
// #1764 + GF-T: a GF-T SGD weight update -- w' = w - eta * g, the final brick of an
// on-device training step (forward softmax -> loss -> gradient g -> THIS update).
// eta is the (positive) learning rate; g the gradient (signed); w the weight (signed).
// Composes the verified primitives: signed multiply (smul over the RNE magnitude
// mul) + subtract (sadd + neg). Bit-exact to the integer oracle; accuracy is to
// GF-T16 precision (<=1 ULP; ~0.03 abs at the largest magnitudes).
//
// Inputs: w, g, eta signed GF-T16 (u32). Output: updated weight w' GF-T16 (u32).

fn magadd(a: i32, b: i32) -> i32 {
var ao : i32 = a >> 9; var am : i32 = a & 511;
var bo : i32 = b >> 9; var bm : i32 = b & 511;
var ho : i32 = bo; var hm : i32 = bm; var lo : i32 = ao; var lm : i32 = am;
if (ao >= bo) { ho = ao; hm = am; lo = bo; lm = bm; }
var hs : i32 = 512 + hm; var ls : i32 = 512 + lm;
var d : i32 = ho - lo; if (d > 11) { d = 11; }
var losh : i32 = ls >> d; var rem : i32 = ls - (losh << d);
var s : i32 = hs + losh; var off : i32 = ho; var mant : i32 = s - 512;
if (s >= 1024) {
var g : i32 = s & 1; var pre : i32 = s >> 1; mant = pre - 512;
if (g == 1) { if (rem > 0) { mant = mant + 1; } else { if ((pre & 1) == 1) { mant = mant + 1; } } }
off = ho + 1; if (off >= 80) { off = 80; }
} else {
var t : i32 = rem << 1; var hf : i32 = 1 << d;
if (t > hf) { mant = mant + 1; } else { if (t == hf) { if ((s & 1) == 1) { mant = mant + 1; } } }
}
if (mant >= 512) { mant = 0; off = off + 1; if (off >= 80) { off = 80; } }
return (off << 9) | mant;
}

fn magsub(hi: i32, lo: i32) -> i32 {
if (hi == lo) { return 0; }
var ho : i32 = hi >> 9; var hm : i32 = hi & 511;
var lo_o : i32 = lo >> 9; var lm : i32 = lo & 511;
var d : i32 = ho - lo_o; var hs : i32 = (512 + hm) << 14;
var la : i32 = 0; var sticky : i32 = 0;
if (d >= 26) { la = 0; sticky = 1; }
else { var ls : i32 = (512 + lm) << 14; la = ls >> d; if ((ls - (la << d)) > 0) { sticky = 1; } }
var diff : i32 = hs - la; var off : i32 = ho;
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
if (diff < 8388608) { if (off > 1) { diff = diff << 1; off = off - 1; } }
var q : i32 = diff >> 14; var rem : i32 = diff - (q << 14); var half : i32 = 8192; var mant : i32 = q - 512;
if (rem > half) { mant = mant + 1; }
else { if (rem == half) { if (sticky == 1) { mant = mant + 1; } else { if ((q & 1) == 1) { mant = mant + 1; } } } }
if (mant >= 512) { mant = 0; off = off + 1; if (off >= 80) { off = 80; } }
return (off << 9) | mant;
}

fn sadd(a: u32, b: u32) -> u32 {
if (a == 0) { return b; }
if (b == 0) { return a; }
var sa : i32 = (a >> 16) as i32; var ma : i32 = (a & 65535) as i32;
var sb : i32 = (b >> 16) as i32; var mb : i32 = (b & 65535) as i32;
if (sa == sb) { return ((sa << 16) | magadd(ma, mb)) as u32; }
var bsign : i32 = sa;
var r : i32 = magsub(ma, mb);
if (ma < mb) { r = magsub(mb, ma); bsign = sb; }
if (r == 0) { return 0; }
return ((bsign << 16) | r) as u32;
}

fn neg(v: u32) -> u32 {
if (v == 0) { return 0; }
return v ^ 65536;
}

fn magmul(a16: i32, b16: i32) -> i32 {
var ao : i32 = a16 >> 9; var am : i32 = a16 & 511;
var bo : i32 = b16 >> 9; var bm : i32 = b16 & 511;
var prod : i32 = (512 + am) * (512 + bm);
var carry : i32 = 0; if (prod >= 524288) { carry = 1; }
var q : i32 = prod >> 9; var r : i32 = prod & 511; var half : i32 = 256;
if (carry == 1) { q = prod >> 10; r = prod & 1023; half = 512; }
var mant : i32 = q - 512;
if (r > half) { mant = mant + 1; }
if (r == half) { if ((q & 1) == 1) { mant = mant + 1; } }
var sm : i32 = ao + bo + carry;
var out_off : i32 = 0;
if (sm >= 40) { var res : i32 = sm - 40; if (res >= 80) { out_off = 80; } else { out_off = res; } }
if (mant >= 512) { mant = 0; out_off = out_off + 1; if (out_off >= 80) { out_off = 80; } }
return (out_off << 9) | mant;
}

// softmax: p_sel = 2^(l_sel - M) / sum_i 2^(l_i - M), M = max logit.

// signed GF-T multiply: sign = xor of signs, magnitude = RNE magnitude mul.
fn smul(a: u32, b: u32) -> u32 {
if (a == 0) { return 0; }
if (b == 0) { return 0; }
var sgn : i32 = ((a >> 16) & 1) as i32;
var sb : i32 = ((b >> 16) & 1) as i32;
if (sgn != sb) { sgn = 1; } else { sgn = 0; }
var mag : i32 = magmul((a & 65535) as i32, (b & 65535) as i32);
if (mag == 0) { return 0; }
return ((sgn << 16) | mag) as u32;
}

// Hard sigmoid: p = clamp(0.5 + 0.25*z, 0, 1). Piecewise-linear, no division/exp
// (a runtime reciprocal maps to a $div/CARRY4 that the open P&R flow cannot place),
// so this is synthesizable. 0.5=19968, 0.25=19456, 1.0=20480.
fn hard_sigmoid(z: u32) -> u32 {
var q : u32 = sadd(19968, smul(19456, z));
if (q == 0) { return 0; }
if (((q >> 16) & 1) == 1) { return 0; }
var off : i32 = ((q >> 9) & 127) as i32;
var mant : i32 = (q & 511) as i32;
if (off > 40) { return 20480; }
if (off == 40) { if (mant > 0) { return 20480; } }
return q;
}
// GF-T ReLU (from the training stack): max(0, z).
fn relu(z: u32) -> u32 {
if (z == 0) { return 0; }
if (((z >> 16) & 1) == 1) { return 0; }
return z;
}
// On-chip training step of a 2-LAYER XOR net: a FIXED analytic hidden layer
// h0=relu(x0+x1), h1=relu(x0+x1-1) (features that make XOR linearly separable),
// and a TRAINABLE output layer z=v0*h0+v1*h1 with hard-sigmoid + p-y gradient:
// v_j' = v_j - eta*(p-y)*h_j. The board learns v to solve XOR. Returns (v0'<<32)|v1'.
fn on_comb(v0: u32, v1: u32, x0: u32, x1: u32, y: u32, eta: u32) -> u64 {
var s : u32 = sadd(x0, x1);
var h0 : u32 = relu(s);
var h1 : u32 = relu(sadd(s, neg(20480))); // relu(x0+x1 - 1.0)
var z : u32 = sadd(smul(v0, h0), smul(v1, h1));
var p : u32 = hard_sigmoid(z);
var g : u32 = sadd(p, neg(y));
var v0n : u32 = sadd(v0, neg(smul(eta, smul(g, h0))));
var v1n : u32 = sadd(v1, neg(smul(eta, smul(g, h1))));
return ((v0n as u64) << 32) | (v1n as u64);
}
// v=(0,0), corner (1,1): h0=relu(2)=2,h1=relu(1)=1, z=0, p=0.5, y=0(XOR false),
// g=0.5, v'=(0-0.25*0.5*2, 0-0.25*0.5*1)=(-0.25,-0.125). (placeholder)
test upd { assert_eq(on_comb(0,0,20480,20480,0,19456), 365037860506112); }
Loading