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 on-chip XOR trainer (2026-08-07)
# NOW — feat: GF-T fully-on-chip XOR perceptron (2026-08-07)

Last updated: 2026-08-07

## feat: GF-T fully-on-chip 2-layer XOR (perceptron output) (Refs #1764)

- **NEW** spec `specs/ternary/gft_xorpercep.t27` — fully-on-chip 2-layer XOR trainer: fixed hidden h0=relu(x0+x1), h1=relu(x0+x1-1) computed on-FPGA + trainable PERCEPTRON output (pred=z>0, g=pred-y, v_j'=v_j-eta*g*h_j; no hard-sigmoid to stay small); returns (v0'<<32)|v1'
- `test` block PASS; GF-T Python sim converges XOR 4/4 (v->(0.25,-0.50))
- Honest hardware note: as ONE design (fasm 19.5M) it EXCEEDS the openXC7 correctness ceiling -- silicon responds but never updates its weights. This REFINES the ceiling to ~17-18M (works <=16.7M, fails >=19.5M). The working on-chip XOR path is the split (cycle 53): fixed hidden off-chip + output trained on the 16.7M gft_logistic bitstream
- Spec-only; no `gen/`/`coq/` edits; no new `*.sh`; Refs #1764

## 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'
Expand Down
132 changes: 132 additions & 0 deletions specs/ternary/gft_xorpercep.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
module GftXorPercep;
// #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;
}

fn relu(z: u32) -> u32 {
if (z == 0) { return 0; }
if (((z >> 16) & 1) == 1) { return 0; }
return z;
}
// FULLY on-chip 2-layer XOR trainer: fixed hidden h0=relu(x0+x1), h1=relu(x0+x1-1)
// (computed here on the FPGA), TRAINABLE output with a PERCEPTRON rule (no
// hard-sigmoid -> smaller, fits under the correctness ceiling). pred=1 if z>0 else 0;
// g = pred - y ; v_j' = v_j - eta*g*h_j. 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)));
var z : u32 = sadd(smul(v0, h0), smul(v1, h1));
var pred : u32 = 20480;
if (z == 0) { pred = 0; }
if (((z >> 16) & 1) == 1) { pred = 0; }
var g : u32 = sadd(pred, 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,0) y=1: s=1,h0=1,h1=0,z=0,pred=0,g=-1 -> v'=(0.25,0). (placeholder)
test upd { assert_eq(on_comb(0,0,20480,0,20480,19456), 83562883710976); }
Loading