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
Files changed (3) hide show
  1. README.md +23 -14
  2. src/reflect.py +236 -49
  3. 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. The family's own full-adder cell, compiled this way, is
531
- interpreted bit-exact, and stays correct with its gates stored in arbitrary
532
- order.
533
- - **Self-reference.** Addresses index the whole signal array, so a gate can
534
- write into the netlist region: a stored gate edits the weights that define
535
- it. The index flag adds `PTR`, so a fixed-size program walks an address
536
- range; one such program streams the netlist's own bytes to the output
537
- device, emitting its own description. Another copies its bank into the
538
- second bank and sets the `BANK` flag, so the interpreter runs a different
539
- resident machine without an external load step.
 
 
 
 
 
 
 
 
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 full-adder cell under sorted and shuffled gate order, the
549
- self-reproduction and bank-replacement programs, and equality of the compiled
550
- matrix form with the gate graph, the 0.5 comparator margin measured under read
551
- noise and conductance mismatch.
 
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
- span = self.F + (1 << (self.BB - 1))
92
- self.ACC = int(math.ceil(math.log2(span + 1))) + 2
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
- acc += wv(rec[b + cfg.A:b + cfg.A + 2]) * sig[eff(from_bits(rec[b:b + cfg.A]), rec[b + cfg.A + 2])]
 
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
- sig[dst] = out
 
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.S)])
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
- sel = net.AND(f"wrsel{s}", [net.DECODE(f"wroh{s}", oea, s), nhalt])
299
- nsig.append(net.MUX(f"nsig{s}", sel, out, sig[s]))
 
 
 
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/6] Exhaustive single-gate semantics")
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/6] Single step vs reference on random full states")
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/6] Universality: interpret a family full-adder cell")
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
- s = st["sig"][addr[sums[0]]] | (st["sig"][addr[cout]] << 1)
581
- if s != x + y:
 
 
582
  abad += 1
583
- print(f" {'OK ' if abad == 0 else 'FAIL'} full adder computed bit-exact "
584
- f"through the interpreter ({len(rg)} gates, compiled from the family's Net)")
585
  ok &= abad == 0
586
 
587
- print("[4/6] Order independence (adder gates stored in random order)")
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/6] Self-reproduction (quine): program emits its own encoding")
606
  nlq = encode_netlist(cfg, quine_program(cfg))
607
- K = 48
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[:K]
611
- print(f" {'OK ' if good else 'FAIL'} first {K} emitted bits == the program's "
612
- f"own stored bits (streaming its description out the output device)")
613
  ok &= good
614
 
615
- print("[6/6] Metamorphosis: program fills the other bank and switches to it")
616
- # (a) copier program fills bank1 with a copy of bank0
617
- nlc = encode_netlist(cfg, copier_program(cfg))
618
- st = {"sig": _load(cfg, nlc), "gp": 0, "halt": 0}
619
- st = R.run(st, cfg.G * (cfg.G * cfg.R) + cfg.G)
620
- filled = st["sig"][cfg.NET1:cfg.NET1 + cfg.G * cfg.R] == nlc
621
- # (b) a program-set BANK flag switches the interpreter to a different machine
622
- # preinstalled in bank1 (the full adder), which then runs.
623
- nl_switch = encode_netlist(cfg, [([], 0, (cfg.BANK_SIG, 0))]) # BANK = 1
624
- sig = _load(cfg, nl_switch, bank=0)
625
- sig[cfg.NET1:cfg.NET1 + len(nl)] = nl # bank1 = adder
626
- st = {"sig": sig, "gp": 0, "halt": 0}
627
- st = R.run(st, 1) # gp0 sets BANK=1
628
- st["gp"] = 0; st["sig"][cfg.WORK_BASE] = 1; st["sig"][cfg.WORK_BASE + 1] = 1
629
- st = R.run(st, cfg.G * cfg.G) # now running bank1
630
- switched = (st["sig"][cfg.BANK_SIG] == 1
631
- and (st["sig"][addr[sums[0]]] | (st["sig"][addr[cout]] << 1)) == 2)
632
- print(f" {'OK ' if filled else 'FAIL'} copier wrote bank0 into bank1; "
633
- f"{'OK ' if switched else 'FAIL'} program set BANK and ran the bank-1 machine")
634
- ok &= filled and switched
635
-
636
- print("\nREFLECT v2 (universality, order, quine, metamorphosis):",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:3a9c8eda3eef586ea00b4ff32e511223f54bff3ab6b4e151c69b5318cb106db2
3
  size 157772226
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d653009bb03b30bf2ce2cc7a30e48c3cf284d722750c9a3aad0a51ce59fb4691
3
  size 157772226