CharlesCNorton commited on
Commit ·
93586ed
1
Parent(s): 6a6ad5c
neural_reflect: interpret the SUBLEQ datapath from stored gates; index-read scaling, a compiler from family nets, self-reproduction, self-rewrite, and sequential-state programs
Browse files- README.md +23 -14
- src/reflect.py +236 -49
- variants/neural_reflect.safetensors +1 -1
README.md
CHANGED
|
@@ -527,16 +527,24 @@ out = H( w0·sig[eff(a0,i0)] + w1·sig[eff(a1,i1)] + bias ) ; sig[eff(oa,oidx)
|
|
| 527 |
regardless of gate order, so U reproduces the family's combinational evaluation.
|
| 528 |
|
| 529 |
- **Universality.** `compile_to_reflect` maps any ≤2-input family `Net` to a
|
| 530 |
-
stored netlist.
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
- **Physical reading.** One recurrence is one matrix-vector product followed by
|
| 541 |
a threshold; each write is one bit of the state. On a memristive crossbar
|
| 542 |
that bit is a conductance, so a program editing the netlist region is the
|
|
@@ -545,10 +553,11 @@ regardless of gate order, so U reproduces the family's combinational evaluation.
|
|
| 545 |
Verified against a reference interpreter: exhaustive single-gate semantics
|
| 546 |
(every ternary weight pair, bias, and input assignment), single-step agreement
|
| 547 |
on random full states with halted states as fixed points, bit-exact
|
| 548 |
-
interpretation of the
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
|
|
|
| 552 |
|
| 553 |
```bash
|
| 554 |
python src/reflect.py verify # universality, order independence, quine, metamorphosis
|
|
|
|
| 527 |
regardless of gate order, so U reproduces the family's combinational evaluation.
|
| 528 |
|
| 529 |
- **Universality.** `compile_to_reflect` maps any ≤2-input family `Net` to a
|
| 530 |
+
stored netlist. Compiled this way, a ripple-carry adder and the SUBLEQ
|
| 531 |
+
machine's datapath, the family's own subtract-and-branch logic, run through
|
| 532 |
+
the interpreter bit-exact; the datapath is checked against the reference over
|
| 533 |
+
all 65,536 `(M[A], M[B])` operand pairs. Results do not depend on the order
|
| 534 |
+
the gates are stored in.
|
| 535 |
+
- **Scale.** When the netlist is read by gate index rather than by address, the
|
| 536 |
+
addressed span stays small as the gate count grows, so U holds a circuit far
|
| 537 |
+
larger than its wire space. The 156-gate SUBLEQ datapath is interpreted with
|
| 538 |
+
a 272-signal addressed span.
|
| 539 |
+
- **Self-reference.** When the netlist is address-reachable, a gate can write
|
| 540 |
+
into it: a stored gate edits the weights that define it. The index flag adds
|
| 541 |
+
`PTR`, so a fixed-size program walks an address range; one such program
|
| 542 |
+
streams the netlist's own bytes to the output device, emitting its own
|
| 543 |
+
description. A single program computes `a & b` and then edits its own gate so
|
| 544 |
+
the next sweep computes `a & ~b`, and a program-set `BANK` flag switches the
|
| 545 |
+
interpreter to a different resident machine without an external load step.
|
| 546 |
+
- **State.** A stored netlist with feedback holds state across sweeps: a 2-bit
|
| 547 |
+
counter advances `0, 1, 2, 3, 0` as U is iterated.
|
| 548 |
- **Physical reading.** One recurrence is one matrix-vector product followed by
|
| 549 |
a threshold; each write is one bit of the state. On a memristive crossbar
|
| 550 |
that bit is a conductance, so a program editing the netlist region is the
|
|
|
|
| 553 |
Verified against a reference interpreter: exhaustive single-gate semantics
|
| 554 |
(every ternary weight pair, bias, and input assignment), single-step agreement
|
| 555 |
on random full states with halted states as fixed points, bit-exact
|
| 556 |
+
interpretation of the ripple-carry adder and the SUBLEQ datapath (all 65,536
|
| 557 |
+
pairs) under sorted and shuffled gate order, the self-reproduction, self-rewrite,
|
| 558 |
+
and bank-switch programs, the counter holding state through feedback, and
|
| 559 |
+
equality of the compiled matrix form with the gate graph, the 0.5 comparator
|
| 560 |
+
margin measured under read noise and conductance mismatch.
|
| 561 |
|
| 562 |
```bash
|
| 563 |
python src/reflect.py verify # universality, order independence, quine, metamorphosis
|
src/reflect.py
CHANGED
|
@@ -83,13 +83,16 @@ class Cfg:
|
|
| 83 |
F: int = 2
|
| 84 |
BB: int = 4
|
| 85 |
banks: int = 2
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
def __post_init__(self):
|
| 88 |
self.S = 1 << self.A
|
| 89 |
self.SLOT = self.A + 3
|
| 90 |
self.R = self.F * self.SLOT + self.BB + (self.A + 1)
|
| 91 |
-
|
| 92 |
-
self.ACC = int(math.ceil(math.log2(
|
| 93 |
self.PTR_BASE = 0
|
| 94 |
self.OUT_DATA = self.A
|
| 95 |
self.OUT_STROBE = self.A + 1
|
|
@@ -100,6 +103,7 @@ class Cfg:
|
|
| 100 |
self.WORK_BASE = self.A + 6
|
| 101 |
self.NET0 = self.S - self.banks * self.G * self.R
|
| 102 |
self.NET1 = self.NET0 + self.G * self.R
|
|
|
|
| 103 |
self.GPW = max(1, (self.G - 1).bit_length())
|
| 104 |
self.STATE_BITS = self.S + self.GPW + 1
|
| 105 |
assert self.NET0 >= self.WORK_BASE + 16, "too little data room; raise A"
|
|
@@ -201,13 +205,15 @@ def ref_gate(cfg, st):
|
|
| 201 |
acc = 0
|
| 202 |
for j in range(cfg.F):
|
| 203 |
b = slot_off(cfg, j)
|
| 204 |
-
|
|
|
|
| 205 |
bo, oo = field_off(cfg)
|
| 206 |
acc += signed(rec[bo:bo + cfg.BB])
|
| 207 |
out = 1 if acc >= 0 else 0
|
| 208 |
dst = eff(from_bits(rec[oo:oo + cfg.A]), rec[oo + cfg.A])
|
| 209 |
h = sig[cfg.HALT_SIG]
|
| 210 |
-
|
|
|
|
| 211 |
ngp = 0 if gp == cfg.G - 1 else gp + 1
|
| 212 |
return sig, ngp, (1 if (halt or h) else 0)
|
| 213 |
|
|
@@ -255,7 +261,7 @@ def build_net(cfg):
|
|
| 255 |
|
| 256 |
def read(addrbits, tag):
|
| 257 |
return net.OR(f"rd{tag}", [net.AND(f"rd{tag}a{s}", [net.DECODE(f"rd{tag}oh{s}", addrbits, s), sig[s]])
|
| 258 |
-
for s in range(cfg.
|
| 259 |
|
| 260 |
contribs = []
|
| 261 |
for j in range(cfg.F):
|
|
@@ -295,8 +301,11 @@ def build_net(cfg):
|
|
| 295 |
oea = eff_addr(recbit[oo:oo + cfg.A], recbit[oo + cfg.A], "w")
|
| 296 |
nsig = []
|
| 297 |
for s in range(cfg.S):
|
| 298 |
-
|
| 299 |
-
|
|
|
|
|
|
|
|
|
|
| 300 |
|
| 301 |
gp_lsb = [gp[cfg.GPW - 1 - k] for k in range(cfg.GPW)]
|
| 302 |
inc, c = [], "#1"
|
|
@@ -435,6 +444,19 @@ def topo(net):
|
|
| 435 |
return order
|
| 436 |
|
| 437 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
def compile_to_reflect(cfg, net, in_addr, out_names, work_start):
|
| 439 |
addr = dict(in_addr)
|
| 440 |
w = work_start
|
|
@@ -462,6 +484,36 @@ def adder_net(bits):
|
|
| 462 |
return net, a, b, sums, carry
|
| 463 |
|
| 464 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
# =============================================================================
|
| 466 |
# programs (stored netlists) for the reflective demos
|
| 467 |
# =============================================================================
|
|
@@ -473,13 +525,6 @@ def quine_program(cfg):
|
|
| 473 |
([], 0, (cfg.PTR_ADV, 0))] # PTR_ADV = 1
|
| 474 |
|
| 475 |
|
| 476 |
-
def copier_program(cfg):
|
| 477 |
-
# bank1[NET1 + ptr] = bank0[NET0 + ptr]; advance ptr
|
| 478 |
-
return [([(cfg.NET0, 1, 1)], -1, (cfg.WORK_BASE, 0)), # tmp = NET0[ptr]
|
| 479 |
-
([(cfg.WORK_BASE, 1, 0)], -1, (cfg.NET1, 1)), # NET1[ptr] = tmp
|
| 480 |
-
([], 0, (cfg.PTR_ADV, 0))]
|
| 481 |
-
|
| 482 |
-
|
| 483 |
# =============================================================================
|
| 484 |
# build + save (compiles U to a recurrent ternary matrix stack)
|
| 485 |
# =============================================================================
|
|
@@ -530,7 +575,7 @@ def verify(cfg, device):
|
|
| 530 |
lev = Leveled(net, inputs, outputs, device=device)
|
| 531 |
R = Runner(cfg, lev)
|
| 532 |
|
| 533 |
-
print("[1/
|
| 534 |
bad = total = 0
|
| 535 |
ra, rb, ro = cfg.WORK_BASE, cfg.WORK_BASE + 1, cfg.WORK_BASE + 2
|
| 536 |
for w0 in (-1, 0, 1):
|
|
@@ -549,7 +594,7 @@ def verify(cfg, device):
|
|
| 549 |
print(f" {'OK ' if bad == 0 else 'FAIL'} {total} configurations exact")
|
| 550 |
ok &= bad == 0
|
| 551 |
|
| 552 |
-
print("[2/
|
| 553 |
gen = torch.Generator().manual_seed(0x5EED)
|
| 554 |
N = 1500
|
| 555 |
sig = (torch.rand(N, cfg.S, generator=gen) < 0.5).long()
|
|
@@ -566,7 +611,7 @@ def verify(cfg, device):
|
|
| 566 |
f"{'OK ' if hbad == 0 else 'FAIL'} halted states are fixed points")
|
| 567 |
ok &= bad == 0 and hbad == 0
|
| 568 |
|
| 569 |
-
print("[3/
|
| 570 |
anet, an, bn, sums, cout = adder_net(1)
|
| 571 |
ia = {an[0]: cfg.WORK_BASE, bn[0]: cfg.WORK_BASE + 1}
|
| 572 |
rg, addr = compile_to_reflect(cfg, anet, ia, sums + [cout], cfg.WORK_BASE + 2)
|
|
@@ -577,14 +622,16 @@ def verify(cfg, device):
|
|
| 577 |
sig = _load(cfg, nl)
|
| 578 |
sig[ia[an[0]]] = x; sig[ia[bn[0]]] = y
|
| 579 |
st = R.run({"sig": sig, "gp": 0, "halt": 0}, cfg.G * cfg.G)
|
| 580 |
-
|
| 581 |
-
|
|
|
|
|
|
|
| 582 |
abad += 1
|
| 583 |
-
print(f" {'OK ' if abad == 0 else 'FAIL'} full adder
|
| 584 |
-
f"
|
| 585 |
ok &= abad == 0
|
| 586 |
|
| 587 |
-
print("[4/
|
| 588 |
obad = 0
|
| 589 |
for seed in range(4):
|
| 590 |
perm = list(range(len(rg)))
|
|
@@ -602,42 +649,180 @@ def verify(cfg, device):
|
|
| 602 |
f"orders (relaxation reproduces combinational evaluation)")
|
| 603 |
ok &= obad == 0
|
| 604 |
|
| 605 |
-
print("[5/
|
| 606 |
nlq = encode_netlist(cfg, quine_program(cfg))
|
| 607 |
-
K =
|
| 608 |
st = {"sig": _load(cfg, nlq), "gp": 0, "halt": 0}
|
| 609 |
_, emits = R.run(st, cfg.G * K + cfg.G, collect=True)
|
| 610 |
-
good = emits[:K] == nlq
|
| 611 |
-
print(f" {'OK ' if good else 'FAIL'}
|
| 612 |
-
f"
|
| 613 |
ok &= good
|
| 614 |
|
| 615 |
-
print("[6/
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
"PASS" if ok else "FAIL")
|
| 638 |
return ok
|
| 639 |
|
| 640 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
# =============================================================================
|
| 642 |
# analog: matrix == gate + noise / conductance-mismatch margins
|
| 643 |
# =============================================================================
|
|
@@ -725,6 +910,8 @@ def main():
|
|
| 725 |
build(cfg, save=True)
|
| 726 |
if args.cmd in ("verify", "all"):
|
| 727 |
rc |= 0 if verify(cfg, args.device) else 1
|
|
|
|
|
|
|
| 728 |
if args.cmd in ("analog", "all"):
|
| 729 |
rc |= 0 if analog(cfg, args.device) else 1
|
| 730 |
return rc
|
|
|
|
| 83 |
F: int = 2
|
| 84 |
BB: int = 4
|
| 85 |
banks: int = 2
|
| 86 |
+
self_mod: bool = True # True: netlist is address-reachable (editable);
|
| 87 |
+
# False: netlist read only by gate index -> the
|
| 88 |
+
# addressed span stays small as G grows.
|
| 89 |
|
| 90 |
def __post_init__(self):
|
| 91 |
self.S = 1 << self.A
|
| 92 |
self.SLOT = self.A + 3
|
| 93 |
self.R = self.F * self.SLOT + self.BB + (self.A + 1)
|
| 94 |
+
rng = self.F + (1 << (self.BB - 1))
|
| 95 |
+
self.ACC = int(math.ceil(math.log2(rng + 1))) + 2
|
| 96 |
self.PTR_BASE = 0
|
| 97 |
self.OUT_DATA = self.A
|
| 98 |
self.OUT_STROBE = self.A + 1
|
|
|
|
| 103 |
self.WORK_BASE = self.A + 6
|
| 104 |
self.NET0 = self.S - self.banks * self.G * self.R
|
| 105 |
self.NET1 = self.NET0 + self.G * self.R
|
| 106 |
+
self.span = self.S if self.self_mod else self.NET0 # indirect coverage
|
| 107 |
self.GPW = max(1, (self.G - 1).bit_length())
|
| 108 |
self.STATE_BITS = self.S + self.GPW + 1
|
| 109 |
assert self.NET0 >= self.WORK_BASE + 16, "too little data room; raise A"
|
|
|
|
| 205 |
acc = 0
|
| 206 |
for j in range(cfg.F):
|
| 207 |
b = slot_off(cfg, j)
|
| 208 |
+
e = eff(from_bits(rec[b:b + cfg.A]), rec[b + cfg.A + 2])
|
| 209 |
+
acc += wv(rec[b + cfg.A:b + cfg.A + 2]) * (sig[e] if e < cfg.span else 0)
|
| 210 |
bo, oo = field_off(cfg)
|
| 211 |
acc += signed(rec[bo:bo + cfg.BB])
|
| 212 |
out = 1 if acc >= 0 else 0
|
| 213 |
dst = eff(from_bits(rec[oo:oo + cfg.A]), rec[oo + cfg.A])
|
| 214 |
h = sig[cfg.HALT_SIG]
|
| 215 |
+
if dst < cfg.span:
|
| 216 |
+
sig[dst] = out
|
| 217 |
ngp = 0 if gp == cfg.G - 1 else gp + 1
|
| 218 |
return sig, ngp, (1 if (halt or h) else 0)
|
| 219 |
|
|
|
|
| 261 |
|
| 262 |
def read(addrbits, tag):
|
| 263 |
return net.OR(f"rd{tag}", [net.AND(f"rd{tag}a{s}", [net.DECODE(f"rd{tag}oh{s}", addrbits, s), sig[s]])
|
| 264 |
+
for s in range(cfg.span)])
|
| 265 |
|
| 266 |
contribs = []
|
| 267 |
for j in range(cfg.F):
|
|
|
|
| 301 |
oea = eff_addr(recbit[oo:oo + cfg.A], recbit[oo + cfg.A], "w")
|
| 302 |
nsig = []
|
| 303 |
for s in range(cfg.S):
|
| 304 |
+
if s < cfg.span: # only the addressed span is writable
|
| 305 |
+
sel = net.AND(f"wrsel{s}", [net.DECODE(f"wroh{s}", oea, s), nhalt])
|
| 306 |
+
nsig.append(net.MUX(f"nsig{s}", sel, out, sig[s]))
|
| 307 |
+
else: # netlist (index-read only) passes through
|
| 308 |
+
nsig.append(sig[s])
|
| 309 |
|
| 310 |
gp_lsb = [gp[cfg.GPW - 1 - k] for k in range(cfg.GPW)]
|
| 311 |
inc, c = [], "#1"
|
|
|
|
| 444 |
return order
|
| 445 |
|
| 446 |
|
| 447 |
+
def eval_family_net(net, input_vals):
|
| 448 |
+
"""Evaluate a matrix8.Net directly (the family's own gate semantics)."""
|
| 449 |
+
v = dict(input_vals); v["#0"], v["#1"] = 0, 1
|
| 450 |
+
for g in topo(net):
|
| 451 |
+
ins, b = net.gates[g]
|
| 452 |
+
v[g] = 1 if b + sum(w * v[s] for s, w in ins) >= 0 else 0
|
| 453 |
+
return v
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
def record_bit(cfg, bank, gate, off):
|
| 457 |
+
return cfg.bank_base(bank) + gate * cfg.R + off
|
| 458 |
+
|
| 459 |
+
|
| 460 |
def compile_to_reflect(cfg, net, in_addr, out_names, work_start):
|
| 461 |
addr = dict(in_addr)
|
| 462 |
w = work_start
|
|
|
|
| 484 |
return net, a, b, sums, carry
|
| 485 |
|
| 486 |
|
| 487 |
+
def subleq_datapath_net():
|
| 488 |
+
"""The SUBLEQ machine's datapath as <=2-input threshold gates: result =
|
| 489 |
+
M[B] - M[A], the branch decision leq = (result <= 0), and next PC =
|
| 490 |
+
leq ? C : PC + 3. All inputs LSB-first. (This is the combinational core the
|
| 491 |
+
family verifies exhaustively; the 256-byte packed memory is not included.)"""
|
| 492 |
+
net = Net()
|
| 493 |
+
a = [f"a{k}" for k in range(8)]
|
| 494 |
+
b = [f"b{k}" for k in range(8)]
|
| 495 |
+
pc = [f"pc{k}" for k in range(8)]
|
| 496 |
+
c = [f"c{k}" for k in range(8)]
|
| 497 |
+
nota = [net.NOT(f"nota{k}", a[k]) for k in range(8)]
|
| 498 |
+
carry = "#1" # two's-complement subtract
|
| 499 |
+
r = []
|
| 500 |
+
for k in range(8):
|
| 501 |
+
s_, carry = net.FA(f"sub{k}", b[k], nota[k], carry)
|
| 502 |
+
r.append(s_)
|
| 503 |
+
ortree = r[0] # NOR-8 as an OR-tree + NOT
|
| 504 |
+
for k in range(1, 8):
|
| 505 |
+
ortree = net.OR(f"zor{k}", [ortree, r[k]])
|
| 506 |
+
zero = net.NOT("zero", ortree)
|
| 507 |
+
leq = net.OR("leq", [r[7], zero]) # sign OR zero
|
| 508 |
+
carry = "#0"
|
| 509 |
+
p3 = []
|
| 510 |
+
for k in range(8):
|
| 511 |
+
s_, carry = net.FA(f"pci{k}", pc[k], "#1" if k in (0, 1) else "#0", carry)
|
| 512 |
+
p3.append(s_)
|
| 513 |
+
pcm = [net.MUX(f"pcm{k}", leq, c[k], p3[k]) for k in range(8)]
|
| 514 |
+
return net, a, b, pc, c, r, leq, pcm
|
| 515 |
+
|
| 516 |
+
|
| 517 |
# =============================================================================
|
| 518 |
# programs (stored netlists) for the reflective demos
|
| 519 |
# =============================================================================
|
|
|
|
| 525 |
([], 0, (cfg.PTR_ADV, 0))] # PTR_ADV = 1
|
| 526 |
|
| 527 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
# =============================================================================
|
| 529 |
# build + save (compiles U to a recurrent ternary matrix stack)
|
| 530 |
# =============================================================================
|
|
|
|
| 575 |
lev = Leveled(net, inputs, outputs, device=device)
|
| 576 |
R = Runner(cfg, lev)
|
| 577 |
|
| 578 |
+
print("[1/7] Exhaustive single-gate semantics")
|
| 579 |
bad = total = 0
|
| 580 |
ra, rb, ro = cfg.WORK_BASE, cfg.WORK_BASE + 1, cfg.WORK_BASE + 2
|
| 581 |
for w0 in (-1, 0, 1):
|
|
|
|
| 594 |
print(f" {'OK ' if bad == 0 else 'FAIL'} {total} configurations exact")
|
| 595 |
ok &= bad == 0
|
| 596 |
|
| 597 |
+
print("[2/7] Single step vs reference on random full states")
|
| 598 |
gen = torch.Generator().manual_seed(0x5EED)
|
| 599 |
N = 1500
|
| 600 |
sig = (torch.rand(N, cfg.S, generator=gen) < 0.5).long()
|
|
|
|
| 611 |
f"{'OK ' if hbad == 0 else 'FAIL'} halted states are fixed points")
|
| 612 |
ok &= bad == 0 and hbad == 0
|
| 613 |
|
| 614 |
+
print("[3/7] Universality: interpret a family full-adder cell")
|
| 615 |
anet, an, bn, sums, cout = adder_net(1)
|
| 616 |
ia = {an[0]: cfg.WORK_BASE, bn[0]: cfg.WORK_BASE + 1}
|
| 617 |
rg, addr = compile_to_reflect(cfg, anet, ia, sums + [cout], cfg.WORK_BASE + 2)
|
|
|
|
| 622 |
sig = _load(cfg, nl)
|
| 623 |
sig[ia[an[0]]] = x; sig[ia[bn[0]]] = y
|
| 624 |
st = R.run({"sig": sig, "gp": 0, "halt": 0}, cfg.G * cfg.G)
|
| 625 |
+
fam = eval_family_net(anet, {an[0]: x, bn[0]: y})
|
| 626 |
+
got = {g: st["sig"][addr[g]] for g in [sums[0], cout]}
|
| 627 |
+
s = got[sums[0]] | (got[cout] << 1)
|
| 628 |
+
if s != x + y or got[sums[0]] != fam[sums[0]] or got[cout] != fam[cout]:
|
| 629 |
abad += 1
|
| 630 |
+
print(f" {'OK ' if abad == 0 else 'FAIL'} full adder ({len(rg)} gates, compiled "
|
| 631 |
+
f"from the family's Net) matches arithmetic and the family's own evaluator")
|
| 632 |
ok &= abad == 0
|
| 633 |
|
| 634 |
+
print("[4/7] Order independence (adder gates stored in random order)")
|
| 635 |
obad = 0
|
| 636 |
for seed in range(4):
|
| 637 |
perm = list(range(len(rg)))
|
|
|
|
| 649 |
f"orders (relaxation reproduces combinational evaluation)")
|
| 650 |
ok &= obad == 0
|
| 651 |
|
| 652 |
+
print("[5/7] Self-reproduction (quine): program emits its whole encoding")
|
| 653 |
nlq = encode_netlist(cfg, quine_program(cfg))
|
| 654 |
+
K = len(nlq)
|
| 655 |
st = {"sig": _load(cfg, nlq), "gp": 0, "halt": 0}
|
| 656 |
_, emits = R.run(st, cfg.G * K + cfg.G, collect=True)
|
| 657 |
+
good = emits[:K] == nlq
|
| 658 |
+
print(f" {'OK ' if good else 'FAIL'} all {K} emitted bits == the program's own "
|
| 659 |
+
f"stored encoding (streaming its full description out the output device)")
|
| 660 |
ok &= good
|
| 661 |
|
| 662 |
+
print("[6/7] Metamorphosis: a program rewrites its own function, and bank-switches")
|
| 663 |
+
IA, IB, OUT = cfg.WORK_BASE, cfg.WORK_BASE + 1, cfg.WORK_BASE + 2
|
| 664 |
+
w1hi = slot_off(cfg, 1) + cfg.A
|
| 665 |
+
bo, _ = field_off(cfg)
|
| 666 |
+
prog = [([(IA, 1, 0), (IB, 1, 0)], -2, (OUT, 0)), # AND(a, b)
|
| 667 |
+
([], 0, (record_bit(cfg, 0, 0, w1hi), 0)), # slot1 weight hi = 1
|
| 668 |
+
([], -1, (record_bit(cfg, 0, 0, w1hi + 1), 0)), # slot1 weight lo = 0
|
| 669 |
+
([], 0, (record_bit(cfg, 0, 0, bo + cfg.BB - 1), 0))] # bias lsb = 1 (-2 -> -1)
|
| 670 |
+
nlm = encode_netlist(cfg, prog)
|
| 671 |
+
mbad = 0
|
| 672 |
+
for a in (0, 1):
|
| 673 |
+
for b in (0, 1):
|
| 674 |
+
sig = _load(cfg, nlm); sig[IA] = a; sig[IB] = b
|
| 675 |
+
st = R.run({"sig": sig, "gp": 0, "halt": 0}, cfg.G) # sweep 1: AND
|
| 676 |
+
f = st["sig"][OUT]
|
| 677 |
+
st = R.run(st, cfg.G) # sweep 2: rewritten
|
| 678 |
+
g = st["sig"][OUT]
|
| 679 |
+
if f != (a & b) or g != (a & (1 - b)):
|
| 680 |
+
mbad += 1
|
| 681 |
+
# program-set BANK flag switches the interpreter to a different bank-1 machine
|
| 682 |
+
nl_sw = encode_netlist(cfg, [([], 0, (cfg.BANK_SIG, 0))])
|
| 683 |
+
Bnl = encode_netlist(cfg, [([(IA, 1, 0), (IB, 1, 0)], -2, (OUT, 0))]) # bank1 = AND
|
| 684 |
+
sig = _load(cfg, nl_sw, bank=0); sig[cfg.NET1:cfg.NET1 + len(Bnl)] = Bnl
|
| 685 |
+
st = R.run({"sig": sig, "gp": 0, "halt": 0}, 1) # gp0 sets BANK=1
|
| 686 |
+
st["gp"] = 0; st["sig"][IA] = 1; st["sig"][IB] = 1
|
| 687 |
+
st = R.run(st, cfg.G) # now running bank1
|
| 688 |
+
switched = st["sig"][cfg.BANK_SIG] == 1 and st["sig"][OUT] == 1
|
| 689 |
+
print(f" {'OK ' if mbad == 0 else 'FAIL'} one program computes a&b, then edits its "
|
| 690 |
+
f"own gate so the next sweep computes a&~b; "
|
| 691 |
+
f"{'OK ' if switched else 'FAIL'} program-set BANK runs the bank-1 machine")
|
| 692 |
+
ok &= mbad == 0 and switched
|
| 693 |
+
|
| 694 |
+
print("[7/7] Sequential state: interpret a 2-bit counter with feedback")
|
| 695 |
+
C0, C1, T, OR_, NAND_ = (cfg.WORK_BASE, cfg.WORK_BASE + 1, cfg.WORK_BASE + 2,
|
| 696 |
+
cfg.WORK_BASE + 3, cfg.WORK_BASE + 4)
|
| 697 |
+
counter = [([(C0, 1, 0)], -1, (T, 0)), # T = c0 (old)
|
| 698 |
+
([(C1, 1, 0), (T, 1, 0)], -1, (OR_, 0)), # c1 OR T
|
| 699 |
+
([(C1, -1, 0), (T, -1, 0)], 1, (NAND_, 0)), # c1 NAND T
|
| 700 |
+
([(OR_, 1, 0), (NAND_, 1, 0)], -2, (C1, 0)), # c1' = c1 XOR c0
|
| 701 |
+
([(C0, -1, 0)], 0, (C0, 0))] # c0' = NOT c0
|
| 702 |
+
nlk = encode_netlist(cfg, counter)
|
| 703 |
+
st = {"sig": _load(cfg, nlk), "gp": 0, "halt": 0}
|
| 704 |
+
seq = []
|
| 705 |
+
for _ in range(5): # 5 ticks (5 sweeps)
|
| 706 |
+
st = R.run(st, cfg.G)
|
| 707 |
+
seq.append(st["sig"][C1] * 2 + st["sig"][C0])
|
| 708 |
+
kgood = seq == [1, 2, 3, 0, 1]
|
| 709 |
+
print(f" {'OK ' if kgood else 'FAIL'} register advances 0->1->2->3->0 across sweeps "
|
| 710 |
+
f"(a stored machine holding state through feedback): {seq}")
|
| 711 |
+
ok &= kgood
|
| 712 |
+
|
| 713 |
+
print("\nREFLECT v2 (universality, order, quine, metamorphosis, sequential state):",
|
| 714 |
"PASS" if ok else "FAIL")
|
| 715 |
return ok
|
| 716 |
|
| 717 |
|
| 718 |
+
def verify_scale(device):
|
| 719 |
+
cfg = Cfg(A=11, G=40, banks=1)
|
| 720 |
+
print(f"\nScale: interpret a 4-bit ripple-carry adder (S={cfg.S}, G={cfg.G})")
|
| 721 |
+
net, inputs, outputs = build_net(cfg)
|
| 722 |
+
print(f" interpreter U: {len(net.gates):,} gates")
|
| 723 |
+
R = Runner(cfg, Leveled(net, inputs, outputs, device=device))
|
| 724 |
+
W = 4
|
| 725 |
+
anet, an, bn, sums, cout = adder_net(W)
|
| 726 |
+
ia = {}
|
| 727 |
+
for i in range(W):
|
| 728 |
+
ia[an[i]] = cfg.WORK_BASE + i
|
| 729 |
+
ia[bn[i]] = cfg.WORK_BASE + W + i
|
| 730 |
+
rg, addr = compile_to_reflect(cfg, anet, ia, sums + [cout], cfg.WORK_BASE + 2 * W)
|
| 731 |
+
nl = encode_netlist(cfg, rg)
|
| 732 |
+
print(f" compiled adder netlist: {len(rg)} gates")
|
| 733 |
+
|
| 734 |
+
def result(st):
|
| 735 |
+
return sum(st["sig"][addr[sums[i]]] << i for i in range(W)) | (st["sig"][addr[cout]] << W)
|
| 736 |
+
|
| 737 |
+
bad = 0
|
| 738 |
+
pairs = [(x, y) for x in range(1 << W) for y in range(1 << W)]
|
| 739 |
+
for (x, y) in pairs:
|
| 740 |
+
sig = _load(cfg, nl)
|
| 741 |
+
for i in range(W):
|
| 742 |
+
sig[ia[an[i]]] = (x >> i) & 1
|
| 743 |
+
sig[ia[bn[i]]] = (y >> i) & 1
|
| 744 |
+
st = R.run({"sig": sig, "gp": 0, "halt": 0}, cfg.G) # one sweep (topo order)
|
| 745 |
+
fam = eval_family_net(anet, {an[i]: (x >> i) & 1 for i in range(W)}
|
| 746 |
+
| {bn[i]: (y >> i) & 1 for i in range(W)})
|
| 747 |
+
famres = sum(fam[sums[i]] << i for i in range(W)) | (fam[cout] << W)
|
| 748 |
+
if result(st) != x + y or result(st) != famres:
|
| 749 |
+
bad += 1
|
| 750 |
+
print(f" {'OK ' if bad == 0 else 'FAIL'} all {len(pairs)} input pairs match "
|
| 751 |
+
f"arithmetic and the family's own evaluator")
|
| 752 |
+
|
| 753 |
+
obad = 0
|
| 754 |
+
for seed in range(2):
|
| 755 |
+
perm = list(range(len(rg)))
|
| 756 |
+
random.Random(seed + 1).shuffle(perm)
|
| 757 |
+
nls = encode_netlist(cfg, [rg[p] for p in perm])
|
| 758 |
+
for (x, y) in [(7, 9), (15, 15), (10, 6)]:
|
| 759 |
+
sig = _load(cfg, nls)
|
| 760 |
+
for i in range(W):
|
| 761 |
+
sig[ia[an[i]]] = (x >> i) & 1
|
| 762 |
+
sig[ia[bn[i]]] = (y >> i) & 1
|
| 763 |
+
st = R.run({"sig": sig, "gp": 0, "halt": 0}, cfg.G * cfg.G)
|
| 764 |
+
if result(st) != x + y:
|
| 765 |
+
obad += 1
|
| 766 |
+
print(f" {'OK ' if obad == 0 else 'FAIL'} correct under 2 random gate orders (relaxation)")
|
| 767 |
+
good = bad == 0 and obad == 0
|
| 768 |
+
print("SCALE:", "PASS" if good else "FAIL")
|
| 769 |
+
return good
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
def verify_subleq(device):
|
| 773 |
+
cfg = Cfg(A=14, G=304, banks=1, self_mod=False)
|
| 774 |
+
net, a, b, pc, c, r, leq, pcm = subleq_datapath_net()
|
| 775 |
+
print(f"\nFamily machine: interpret the SUBLEQ datapath "
|
| 776 |
+
f"(S={cfg.S}, addressed span={cfg.span}, G={cfg.G})")
|
| 777 |
+
inet, ii, io = build_net(cfg)
|
| 778 |
+
print(f" interpreter U: {len(inet.gates):,} gates")
|
| 779 |
+
lev = Leveled(inet, ii, io, device=device)
|
| 780 |
+
base = cfg.WORK_BASE
|
| 781 |
+
in_addr = {}
|
| 782 |
+
for k in range(8):
|
| 783 |
+
in_addr[a[k]] = base + k
|
| 784 |
+
in_addr[b[k]] = base + 8 + k
|
| 785 |
+
in_addr[pc[k]] = base + 16 + k
|
| 786 |
+
in_addr[c[k]] = base + 24 + k
|
| 787 |
+
rg, addr = compile_to_reflect(cfg, net, in_addr, r + [leq] + pcm, base + 32)
|
| 788 |
+
nl = encode_netlist(cfg, rg)
|
| 789 |
+
print(f" compiled datapath netlist: {len(rg)} gates; interpreting all "
|
| 790 |
+
f"65,536 (M[A], M[B]) pairs")
|
| 791 |
+
sig0 = [0] * cfg.S
|
| 792 |
+
sig0[cfg.NET0:cfg.NET0 + len(nl)] = nl
|
| 793 |
+
base_vec = state_to_vec(cfg, {"sig": sig0, "gp": 0, "halt": 0}).to(device)
|
| 794 |
+
PC, C = 0x42, 0x99
|
| 795 |
+
|
| 796 |
+
bad = 0
|
| 797 |
+
t0 = time.perf_counter()
|
| 798 |
+
for lo in range(0, 65536, 8192):
|
| 799 |
+
xs = torch.arange(lo, lo + 8192) % 256 # M[A]
|
| 800 |
+
ys = torch.arange(lo, lo + 8192) // 256 # M[B]
|
| 801 |
+
B = xs.shape[0]
|
| 802 |
+
V = base_vec.unsqueeze(0).repeat(B, 1)
|
| 803 |
+
for k in range(8):
|
| 804 |
+
V[:, in_addr[a[k]]] = ((xs >> k) & 1).float().to(device)
|
| 805 |
+
V[:, in_addr[b[k]]] = ((ys >> k) & 1).float().to(device)
|
| 806 |
+
V[:, in_addr[pc[k]]] = float((PC >> k) & 1)
|
| 807 |
+
V[:, in_addr[c[k]]] = float((C >> k) & 1)
|
| 808 |
+
for _ in range(cfg.G):
|
| 809 |
+
V = lev.step(V)
|
| 810 |
+
Vc = V.cpu()
|
| 811 |
+
rr = sum(Vc[:, addr[r[k]]].long() << k for k in range(8))
|
| 812 |
+
lq = Vc[:, addr[leq]].long()
|
| 813 |
+
npc = sum(Vc[:, addr[pcm[k]]].long() << k for k in range(8))
|
| 814 |
+
exp_r = (ys - xs) & 0xFF
|
| 815 |
+
exp_lq = ((exp_r == 0) | (exp_r >= 128)).long()
|
| 816 |
+
exp_np = torch.where(exp_lq.bool(), torch.full_like(xs, C), (PC + 3) & 0xFF)
|
| 817 |
+
bad += int((rr != exp_r).sum() + (lq != exp_lq).sum() + (npc != exp_np).sum())
|
| 818 |
+
dt = time.perf_counter() - t0
|
| 819 |
+
good = bad == 0
|
| 820 |
+
print(f" {'OK ' if good else 'FAIL'} result, branch decision, and next-PC exact "
|
| 821 |
+
f"for all 65,536 pairs ({dt:.0f}s)")
|
| 822 |
+
print("SUBLEQ DATAPATH:", "PASS" if good else "FAIL")
|
| 823 |
+
return good
|
| 824 |
+
|
| 825 |
+
|
| 826 |
# =============================================================================
|
| 827 |
# analog: matrix == gate + noise / conductance-mismatch margins
|
| 828 |
# =============================================================================
|
|
|
|
| 910 |
build(cfg, save=True)
|
| 911 |
if args.cmd in ("verify", "all"):
|
| 912 |
rc |= 0 if verify(cfg, args.device) else 1
|
| 913 |
+
rc |= 0 if verify_scale(args.device) else 1
|
| 914 |
+
rc |= 0 if verify_subleq(args.device) else 1
|
| 915 |
if args.cmd in ("analog", "all"):
|
| 916 |
rc |= 0 if analog(cfg, args.device) else 1
|
| 917 |
return rc
|
variants/neural_reflect.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 157772226
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d653009bb03b30bf2ce2cc7a30e48c3cf284d722750c9a3aad0a51ce59fb4691
|
| 3 |
size 157772226
|