CharlesCNorton commited on
Commit ·
7e7dca3
1
Parent(s): 93586ed
neural_reflect: host a complete SUBLEQ machine (32-byte memory, fetch-execute loop) inside the interpreter, its memory accessed through indexed addressing
Browse files- README.md +16 -5
- src/reflect.py +198 -7
README.md
CHANGED
|
@@ -536,6 +536,15 @@ regardless of gate order, so U reproduces the family's combinational evaluation.
|
|
| 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
|
|
@@ -554,13 +563,15 @@ 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,
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
|
|
|
|
|
|
| 561 |
|
| 562 |
```bash
|
| 563 |
-
python src/reflect.py verify # universality,
|
| 564 |
python src/reflect.py analog # matrix == gate + noise / conductance-mismatch margin
|
| 565 |
```
|
| 566 |
|
|
|
|
| 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 |
+
- **A complete machine hosted inside the interpreter.** A stored microprogram
|
| 540 |
+
holds a whole SUBLEQ machine: its 32-byte memory lives in signals, and the
|
| 541 |
+
interpreter's own indexed addressing (set `PTR` to a byte address, read or
|
| 542 |
+
write the eight bit-planes) serves as the machine's memory-access hardware.
|
| 543 |
+
266 stored gates fetch three operand bytes, read two data bytes, subtract,
|
| 544 |
+
write the result back, and branch, one instruction per sweep; the memory and
|
| 545 |
+
program counter carry across sweeps. One instruction is exact over 4,096
|
| 546 |
+
random `(memory, PC)` states, and a stored countdown program runs a loop to a
|
| 547 |
+
halt with its final memory matching the reference emulator.
|
| 548 |
- **Self-reference.** When the netlist is address-reachable, a gate can write
|
| 549 |
into it: a stored gate edits the weights that define it. The index flag adds
|
| 550 |
`PTR`, so a fixed-size program walks an address range; one such program
|
|
|
|
| 563 |
(every ternary weight pair, bias, and input assignment), single-step agreement
|
| 564 |
on random full states with halted states as fixed points, bit-exact
|
| 565 |
interpretation of the ripple-carry adder and the SUBLEQ datapath (all 65,536
|
| 566 |
+
pairs) under sorted and shuffled gate order, one hosted SUBLEQ instruction over
|
| 567 |
+
4,096 random memory states and a stored program run to a halt, the
|
| 568 |
+
self-reproduction, self-rewrite, and bank-switch programs, the counter holding
|
| 569 |
+
state through feedback, and equality of the compiled matrix form with the gate
|
| 570 |
+
graph, the 0.5 comparator margin measured under read noise and conductance
|
| 571 |
+
mismatch.
|
| 572 |
|
| 573 |
```bash
|
| 574 |
+
python src/reflect.py verify # universality, a hosted SUBLEQ machine, quine, metamorphosis
|
| 575 |
python src/reflect.py analog # matrix == gate + noise / conductance-mismatch margin
|
| 576 |
```
|
| 577 |
|
src/reflect.py
CHANGED
|
@@ -457,18 +457,25 @@ 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
|
| 463 |
rgates = []
|
| 464 |
for g in topo(net):
|
| 465 |
ins, bias = net.gates[g]
|
| 466 |
assert len(ins) <= cfg.F
|
| 467 |
-
|
|
|
|
|
|
|
|
|
|
| 468 |
slots = [(addr[s], wt, 0) for s, wt in ins]
|
| 469 |
rgates.append((slots, bias, (addr[g], 0)))
|
| 470 |
-
|
| 471 |
-
return rgates, addr
|
| 472 |
|
| 473 |
|
| 474 |
def adder_net(bits):
|
|
@@ -614,7 +621,7 @@ def verify(cfg, device):
|
|
| 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,
|
| 618 |
nl = encode_netlist(cfg, rg)
|
| 619 |
abad = 0
|
| 620 |
for x in (0, 1):
|
|
@@ -727,7 +734,7 @@ def verify_scale(device):
|
|
| 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,
|
| 731 |
nl = encode_netlist(cfg, rg)
|
| 732 |
print(f" compiled adder netlist: {len(rg)} gates")
|
| 733 |
|
|
@@ -784,7 +791,7 @@ def verify_subleq(device):
|
|
| 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,
|
| 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")
|
|
@@ -823,6 +830,189 @@ def verify_subleq(device):
|
|
| 823 |
return good
|
| 824 |
|
| 825 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
# =============================================================================
|
| 827 |
# analog: matrix == gate + noise / conductance-mismatch margins
|
| 828 |
# =============================================================================
|
|
@@ -912,6 +1102,7 @@ def main():
|
|
| 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
|
|
|
|
| 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, fixed=None):
|
| 461 |
+
"""Lay a <=2-input acyclic Net out as reflect gate records in topological
|
| 462 |
+
order. Primary inputs sit at in_addr; gate outputs get fresh work signals
|
| 463 |
+
from work_start up, except those named in `fixed` (gate -> signal), which
|
| 464 |
+
are placed at the given signal. Returns (records, address map, next work)."""
|
| 465 |
+
fixed = fixed or {}
|
| 466 |
addr = dict(in_addr)
|
| 467 |
w = work_start
|
| 468 |
rgates = []
|
| 469 |
for g in topo(net):
|
| 470 |
ins, bias = net.gates[g]
|
| 471 |
assert len(ins) <= cfg.F
|
| 472 |
+
if g in fixed:
|
| 473 |
+
addr[g] = fixed[g]
|
| 474 |
+
else:
|
| 475 |
+
addr[g] = w; w += 1
|
| 476 |
slots = [(addr[s], wt, 0) for s, wt in ins]
|
| 477 |
rgates.append((slots, bias, (addr[g], 0)))
|
| 478 |
+
return rgates, addr, w
|
|
|
|
| 479 |
|
| 480 |
|
| 481 |
def adder_net(bits):
|
|
|
|
| 621 |
print("[3/7] Universality: interpret a family full-adder cell")
|
| 622 |
anet, an, bn, sums, cout = adder_net(1)
|
| 623 |
ia = {an[0]: cfg.WORK_BASE, bn[0]: cfg.WORK_BASE + 1}
|
| 624 |
+
rg, addr, _ = compile_to_reflect(cfg,anet, ia, sums + [cout], cfg.WORK_BASE + 2)
|
| 625 |
nl = encode_netlist(cfg, rg)
|
| 626 |
abad = 0
|
| 627 |
for x in (0, 1):
|
|
|
|
| 734 |
for i in range(W):
|
| 735 |
ia[an[i]] = cfg.WORK_BASE + i
|
| 736 |
ia[bn[i]] = cfg.WORK_BASE + W + i
|
| 737 |
+
rg, addr, _ = compile_to_reflect(cfg,anet, ia, sums + [cout], cfg.WORK_BASE + 2 * W)
|
| 738 |
nl = encode_netlist(cfg, rg)
|
| 739 |
print(f" compiled adder netlist: {len(rg)} gates")
|
| 740 |
|
|
|
|
| 791 |
in_addr[b[k]] = base + 8 + k
|
| 792 |
in_addr[pc[k]] = base + 16 + k
|
| 793 |
in_addr[c[k]] = base + 24 + k
|
| 794 |
+
rg, addr, _ = compile_to_reflect(cfg,net, in_addr, r + [leq] + pcm, base + 32)
|
| 795 |
nl = encode_netlist(cfg, rg)
|
| 796 |
print(f" compiled datapath netlist: {len(rg)} gates; interpreting all "
|
| 797 |
f"65,536 (M[A], M[B]) pairs")
|
|
|
|
| 830 |
return good
|
| 831 |
|
| 832 |
|
| 833 |
+
# =============================================================================
|
| 834 |
+
# a complete stored machine (SUBLEQ, 32-byte memory) hosted in the interpreter
|
| 835 |
+
# =============================================================================
|
| 836 |
+
|
| 837 |
+
def _subtract_net():
|
| 838 |
+
net = Net()
|
| 839 |
+
x = [f"x{k}" for k in range(8)]; y = [f"y{k}" for k in range(8)]
|
| 840 |
+
nx = [net.NOT(f"nx{k}", x[k]) for k in range(8)]
|
| 841 |
+
carry = "#1"; r = []
|
| 842 |
+
for k in range(8):
|
| 843 |
+
s_, carry = net.FA(f"sb{k}", y[k], nx[k], carry)
|
| 844 |
+
r.append(s_)
|
| 845 |
+
ot = r[0]
|
| 846 |
+
for k in range(1, 8):
|
| 847 |
+
ot = net.OR(f"zo{k}", [ot, r[k]])
|
| 848 |
+
leq = net.OR("leq", [r[7], net.NOT("z", ot)])
|
| 849 |
+
return net, x, y, r, leq
|
| 850 |
+
|
| 851 |
+
|
| 852 |
+
def _incr_net(): # 5-bit address arithmetic (mod 32)
|
| 853 |
+
net = Net()
|
| 854 |
+
pc = [f"p{k}" for k in range(5)]
|
| 855 |
+
|
| 856 |
+
def addc(tag, cst):
|
| 857 |
+
carry = "#0"; out = []
|
| 858 |
+
for k in range(5):
|
| 859 |
+
s_, carry = net.FA(f"{tag}{k}", pc[k], "#1" if (cst >> k) & 1 else "#0", carry)
|
| 860 |
+
out.append(s_)
|
| 861 |
+
return out
|
| 862 |
+
return net, pc, addc("i1", 1), addc("i2", 2), addc("i3", 3)
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
def _branch_net():
|
| 866 |
+
net = Net()
|
| 867 |
+
c = [f"c{k}" for k in range(5)]; q = [f"q{k}" for k in range(5)]
|
| 868 |
+
npc = [net.MUX(f"nb{k}", "leq", c[k], q[k]) for k in range(5)]
|
| 869 |
+
halt = npc[0]
|
| 870 |
+
for k in range(1, 5):
|
| 871 |
+
halt = net.AND(f"hb{k}", [halt, npc[k]])
|
| 872 |
+
return net, c, q, npc, halt
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
MEMB = 32
|
| 876 |
+
MEM = 416
|
| 877 |
+
|
| 878 |
+
|
| 879 |
+
def build_subleq_machine(cfg):
|
| 880 |
+
"""A stored microprogram implementing one SUBLEQ instruction per sweep over
|
| 881 |
+
a 32-byte memory held in signals [MEM, MEM+8*32). Data-dependent memory
|
| 882 |
+
access is the interpreter's own indexed addressing: set PTR to a byte
|
| 883 |
+
address, then read/write the eight bit-planes at MEM + b*32 + PTR."""
|
| 884 |
+
WB = cfg.WORK_BASE
|
| 885 |
+
reg = lambda o: [WB + o + k for k in range(8)]
|
| 886 |
+
PC, A_, B_, C_ = reg(0), reg(8), reg(16), reg(24)
|
| 887 |
+
X, Y, Rr = reg(32), reg(40), reg(48)
|
| 888 |
+
P1, P2, P3, NPC = reg(56), reg(64), reg(72), reg(80)
|
| 889 |
+
leqs = WB + 88
|
| 890 |
+
workc = WB + 96
|
| 891 |
+
|
| 892 |
+
def zero_hi(): # PTR high bits = 0 (once)
|
| 893 |
+
return [([], -1, (cfg.PTR_BASE + (cfg.A - 1 - k), 0)) for k in range(5, cfg.A)]
|
| 894 |
+
|
| 895 |
+
def ptr_set(r8): # PTR low 5 = addr; high stays 0
|
| 896 |
+
return [([(r8[k], 1, 0)], -1, (cfg.PTR_BASE + (cfg.A - 1 - k), 0)) for k in range(5)]
|
| 897 |
+
|
| 898 |
+
def read_byte(dst):
|
| 899 |
+
return [([(MEM + b * MEMB, 1, 1)], -1, (dst[b], 0)) for b in range(8)]
|
| 900 |
+
|
| 901 |
+
def write_byte(src):
|
| 902 |
+
return [([(src[b], 1, 0)], -1, (MEM + b * MEMB, 1)) for b in range(8)]
|
| 903 |
+
|
| 904 |
+
inet, pin, p1, p2, p3 = _incr_net()
|
| 905 |
+
irec, _, workc = compile_to_reflect(cfg, inet, {pin[k]: PC[k] for k in range(5)}, [],
|
| 906 |
+
workc, fixed={**{p1[k]: P1[k] for k in range(5)},
|
| 907 |
+
**{p2[k]: P2[k] for k in range(5)},
|
| 908 |
+
**{p3[k]: P3[k] for k in range(5)}})
|
| 909 |
+
snet, sx, sy, sr, sleq = _subtract_net()
|
| 910 |
+
srec, _, workc = compile_to_reflect(
|
| 911 |
+
cfg, snet, {**{sx[k]: X[k] for k in range(8)}, **{sy[k]: Y[k] for k in range(8)}},
|
| 912 |
+
[], workc, fixed={**{sr[k]: Rr[k] for k in range(8)}, sleq: leqs})
|
| 913 |
+
bnet, bc, bq, bnpc, bhalt = _branch_net()
|
| 914 |
+
brec, _, workc = compile_to_reflect(
|
| 915 |
+
cfg, bnet, {**{bc[k]: C_[k] for k in range(5)}, **{bq[k]: P3[k] for k in range(5)}, "leq": leqs},
|
| 916 |
+
[], workc, fixed={**{bnpc[k]: NPC[k] for k in range(5)}, bhalt: cfg.HALT_SIG})
|
| 917 |
+
assert workc < MEM, "compiled internals collide with memory"
|
| 918 |
+
|
| 919 |
+
prog = zero_hi() + list(irec)
|
| 920 |
+
prog += ptr_set(PC) + read_byte(A_)
|
| 921 |
+
prog += ptr_set(P1) + read_byte(B_)
|
| 922 |
+
prog += ptr_set(P2) + read_byte(C_)
|
| 923 |
+
prog += ptr_set(A_) + read_byte(X)
|
| 924 |
+
prog += ptr_set(B_) + read_byte(Y)
|
| 925 |
+
prog += srec
|
| 926 |
+
prog += ptr_set(B_) + write_byte(Rr)
|
| 927 |
+
prog += brec
|
| 928 |
+
prog += [([(NPC[k], 1, 0)], -1, (PC[k], 0)) for k in range(5)] # PC = NPC (low 5)
|
| 929 |
+
return prog, {"PC": PC}
|
| 930 |
+
|
| 931 |
+
|
| 932 |
+
def subleq32_step(mem, pc):
|
| 933 |
+
A, B, C = mem[pc], mem[(pc + 1) & 31], mem[(pc + 2) & 31]
|
| 934 |
+
x, y = mem[A & 31], mem[B & 31]
|
| 935 |
+
r = (y - x) & 0xFF
|
| 936 |
+
m2 = list(mem); m2[B & 31] = r
|
| 937 |
+
npc = (C & 31) if (r == 0 or r >= 128) else (pc + 3) & 31
|
| 938 |
+
return m2, npc
|
| 939 |
+
|
| 940 |
+
|
| 941 |
+
def verify_hosted(device):
|
| 942 |
+
cfg = Cfg(A=14, G=296, banks=1, self_mod=False)
|
| 943 |
+
prog, lay = build_subleq_machine(cfg)
|
| 944 |
+
print(f"\nHosted machine: SUBLEQ with a 32-byte memory, one instruction per "
|
| 945 |
+
f"sweep\n microprogram: {len(prog)} gates; interpreter U:", end=" ")
|
| 946 |
+
inet, ii, io = build_net(cfg)
|
| 947 |
+
print(f"{len(inet.gates):,} gates (addressed span {cfg.span})")
|
| 948 |
+
lev = Leveled(inet, ii, io, device=device)
|
| 949 |
+
nl = encode_netlist(cfg, prog)
|
| 950 |
+
sig0 = [0] * cfg.S
|
| 951 |
+
sig0[cfg.NET0:cfg.NET0 + len(nl)] = nl
|
| 952 |
+
base = state_to_vec(cfg, {"sig": sig0, "gp": 0, "halt": 0}).to(device)
|
| 953 |
+
PC = lay["PC"]
|
| 954 |
+
|
| 955 |
+
def set_mem(V, mems):
|
| 956 |
+
for i in range(32):
|
| 957 |
+
for b in range(8):
|
| 958 |
+
V[:, MEM + b * MEMB + i] = ((mems[:, i] >> b) & 1).float().to(device)
|
| 959 |
+
|
| 960 |
+
def get_mem(Vc):
|
| 961 |
+
return sum(Vc[:, MEM + b * MEMB: MEM + b * MEMB + 32].long() << b for b in range(8))
|
| 962 |
+
|
| 963 |
+
# one instruction vs reference over random (memory, PC) states, batched
|
| 964 |
+
gen = torch.Generator().manual_seed(1)
|
| 965 |
+
B = 4096
|
| 966 |
+
mems = torch.randint(0, 256, (B, 32), generator=gen)
|
| 967 |
+
pcs = torch.randint(0, 30, (B,), generator=gen) # avoid the halt cell
|
| 968 |
+
V = base.unsqueeze(0).repeat(B, 1)
|
| 969 |
+
set_mem(V, mems)
|
| 970 |
+
for k in range(8):
|
| 971 |
+
V[:, PC[k]] = ((pcs >> k) & 1).float().to(device)
|
| 972 |
+
for _ in range(cfg.G):
|
| 973 |
+
V = lev.step(V)
|
| 974 |
+
Vc = V.cpu()
|
| 975 |
+
got_mem = get_mem(Vc)
|
| 976 |
+
got_pc = sum(Vc[:, PC[k]].long() << k for k in range(8)) & 31
|
| 977 |
+
exp_mem = torch.zeros_like(got_mem); exp_pc = torch.zeros_like(got_pc)
|
| 978 |
+
for i in range(B):
|
| 979 |
+
m2, np_ = subleq32_step(mems[i].tolist(), int(pcs[i]))
|
| 980 |
+
exp_mem[i] = torch.tensor(m2); exp_pc[i] = np_
|
| 981 |
+
mask = exp_pc != 31 # halting instr freezes PC
|
| 982 |
+
ok1 = bool((got_mem == exp_mem).all()) and bool((got_pc[mask] == exp_pc[mask]).all())
|
| 983 |
+
print(f" {'OK ' if ok1 else 'FAIL'} one instruction exact for {B} random "
|
| 984 |
+
f"(memory, PC) states (result, writeback, and branch)")
|
| 985 |
+
|
| 986 |
+
# run a stored program to a halt: count M[21] (=3) down to 0 through a loop
|
| 987 |
+
prog_mem = [0] * 32
|
| 988 |
+
prog_mem[0:3] = [20, 21, 6] # loop: subleq one, n, done
|
| 989 |
+
prog_mem[3:6] = [22, 22, 0] # subleq z, z, 0 (jump back to loop)
|
| 990 |
+
prog_mem[6:9] = [31, 31, 31] # done: branch to the halt cell
|
| 991 |
+
prog_mem[20], prog_mem[21], prog_mem[22] = 1, 3, 0
|
| 992 |
+
V = base.unsqueeze(0).repeat(1, 1)
|
| 993 |
+
set_mem(V, torch.tensor(prog_mem).unsqueeze(0))
|
| 994 |
+
halted = False
|
| 995 |
+
steps = 0
|
| 996 |
+
for _ in range(24):
|
| 997 |
+
for _ in range(cfg.G):
|
| 998 |
+
V = lev.step(V)
|
| 999 |
+
steps += 1
|
| 1000 |
+
if int(V[0, cfg.S + cfg.GPW]) == 1: # interpreter halt latched
|
| 1001 |
+
halted = True
|
| 1002 |
+
break
|
| 1003 |
+
got = get_mem(V.cpu())[0].tolist()
|
| 1004 |
+
ref_mem, ref_pc = list(prog_mem), 0
|
| 1005 |
+
while ref_pc != 31:
|
| 1006 |
+
ref_mem, ref_pc = subleq32_step(ref_mem, ref_pc)
|
| 1007 |
+
ok2 = halted and got == ref_mem and got[21] == 0
|
| 1008 |
+
print(f" {'OK ' if ok2 else 'FAIL'} stored countdown program ran to a halt in "
|
| 1009 |
+
f"{steps} instructions; final memory matches the emulator (M[21] = {got[21]})")
|
| 1010 |
+
|
| 1011 |
+
good = ok1 and ok2
|
| 1012 |
+
print("HOSTED MACHINE:", "PASS" if good else "FAIL")
|
| 1013 |
+
return good
|
| 1014 |
+
|
| 1015 |
+
|
| 1016 |
# =============================================================================
|
| 1017 |
# analog: matrix == gate + noise / conductance-mismatch margins
|
| 1018 |
# =============================================================================
|
|
|
|
| 1102 |
rc |= 0 if verify(cfg, args.device) else 1
|
| 1103 |
rc |= 0 if verify_scale(args.device) else 1
|
| 1104 |
rc |= 0 if verify_subleq(args.device) else 1
|
| 1105 |
+
rc |= 0 if verify_hosted(args.device) else 1
|
| 1106 |
if args.cmd in ("analog", "all"):
|
| 1107 |
rc |= 0 if analog(cfg, args.device) else 1
|
| 1108 |
return rc
|