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
10 changes: 9 additions & 1 deletion docs/NOW.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# NOW — feat: GF-T 2-neuron hidden layer trainer (2026-08-07)
# NOW — feat: GF-T on-chip binary classifier (2026-08-07)

Last updated: 2026-08-07

## feat: GF-T on-chip binary logistic classifier — proven on AX7203 (Refs #1764)

- **NEW** spec `specs/ternary/gft_logistic.t27` — on-chip SGD step of a binary classifier: logit `z=w0*x0+w1*x1`, `p=hard_sigmoid(z)=clamp(0.5+0.25*z,0,1)`, gradient `dL/dz=p-y`, update `w_j'=w_j-eta*(p-y)*x_j`; returns `(w0'<<32)|w1'`
- Uses a division-free **hard-sigmoid** (a runtime reciprocal maps to a `$div`/CARRY4 the open P&R flow cannot place); only `smul/sadd/compares`
- `test` blocks (learn, partial-confidence) PASS via `icarus-simulate` per L4
- Proven on a live AX7203 (`uart_logistic.v`): streaming labeled 2D points for a hidden boundary (class1 iff x0+x1>0), the board learns the weights on-chip and classifies **8/8 held-out points correctly (100% generalization)** — classification, not just regression
- Spec-only; no `gen/`/`coq/` edits; no new `*.sh`; Refs #1764

## feat: GF-T 2-neuron hidden layer on-chip trainer — proven on AX7203 (Refs #1764)

- **NEW** spec `specs/ternary/gft_hidden2.t27` — on-chip SGD step of a 2-neuron hidden layer `y=relu(w0*x0)+relu(w1*x1)` (fixed unit output weights): two INDEPENDENT nonlinear units, each with its own relu gate; per-neuron gated grad `dw_j=e*relu'(z_j)*x_j`; updates `w_j'=w_j-eta*dw_j`; returns `(w0'<<32)|w1'`. Reuses `smul/sadd/neg/relu/relu_prime/mag*`
Expand Down
137 changes: 137 additions & 0 deletions specs/ternary/gft_logistic.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
module GftLogistic;
// #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;
}
// One on-chip SGD step of a BINARY classifier with a hard-sigmoid link:
// logit z=w0*x0+w1*x1 ; p=hard_sigmoid(z) ; grad dL/dz=p-y (y in {0,1}) ;
// update w_j' = w_j - eta*(p-y)*x_j. Returns (w0'<<32)|w1'. Learns a linear
// decision boundary on-chip; host streams (x0,x1,label).
fn on_comb(w0: u32, w1: u32, x0: u32, x1: u32, y: u32, eta: u32) -> u64 {
var z : u32 = sadd(smul(w0, x0), smul(w1, x1));
var p : u32 = hard_sigmoid(z);
var g : u32 = sadd(p, neg(y));
var w0n : u32 = sadd(w0, neg(smul(eta, smul(g, x0))));
var w1n : u32 = sadd(w1, neg(smul(eta, smul(g, x1))));
return ((w0n as u64) << 32) | (w1n as u64);
}
// w=[0,0],x=[1,1],y=1,eta=0.5 -> z=0,p=0.5,g=-0.5 -> w'=[+0.25,+0.25] (placeholder).
test learn { assert_eq(on_comb(0,0,20480,20480,20480,19968), 83562883730432); }
// partial-confidence: z=1.5,p=0.875,g=-0.125 -> small positive update.
test partial { assert_eq(on_comb(19968,19968,20736,20736,20480,19968), 86174223847008); }
Loading