CharlesCNorton commited on
Commit
c3e47db
·
1 Parent(s): 886dfed

rv32 decode and PC sequencing as threshold gates: an opcode-class detector network (exact 7-bit match per class), sign-extended immediate generation muxed over the I/S/B/U/J formats, and a next-PC mux (PC+4 / PC+imm / (rs1+imm)&~1). The threshold CPU reads these gate outputs for dispatch, immediates, and the next PC instead of slicing the instruction word in Python; verified in isolation and by the full RV32 lockstep suite plus randomized programs.

Browse files
Files changed (3) hide show
  1. src/build.py +124 -0
  2. src/machines.py +60 -35
  3. variants/neural_rv32.safetensors +2 -2
src/build.py CHANGED
@@ -5166,6 +5166,48 @@ def infer_rv32_inputs(gate: str, reg: SignalRegistry) -> Optional[List[int]]:
5166
  pair = m.group(1)
5167
  return [R(f"rv32.hazard.{pair}.bit{k}.eq") for k in range(5)]
5168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5169
  return None
5170
 
5171
 
@@ -6056,6 +6098,86 @@ def add_rv32_extras(tensors: Dict[str, torch.Tensor]) -> None:
6056
  add_gate(tensors, f"rv32.hazard.{pair}.all", [1.0] * 5, [-5.0])
6057
 
6058
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6059
  def cmd_rv32(args) -> None:
6060
  """Build the standalone RV32 threshold processor file from scratch:
6061
  64 KB packed memory, the shared 32-bit circuit families, the composed
@@ -6083,6 +6205,8 @@ def cmd_rv32(args) -> None:
6083
  add_float_fma(tensors, "float32", 8, 23)
6084
  add_float32_cmp(tensors)
6085
  add_rv32_extras(tensors)
 
 
6086
  tensors["manifest.data_bits"] = torch.tensor([32.0], dtype=torch.float32)
6087
  tensors["manifest.addr_bits"] = torch.tensor([16.0], dtype=torch.float32)
6088
  tensors["manifest.memory_bytes"] = torch.tensor([65536.0], dtype=torch.float32)
 
5166
  pair = m.group(1)
5167
  return [R(f"rv32.hazard.{pair}.bit{k}.eq") for k in range(5)]
5168
 
5169
+ # Instruction decode: opcode-class detectors, format-select, active immediate.
5170
+ m = re.match(r"^rv32\.decode\.is_(\w+)$", gate)
5171
+ if m:
5172
+ name = m.group(1)
5173
+ if name == "fma":
5174
+ return [R(f"$rv32_instr[{j}]") for j in (0, 1, 4, 5, 6)]
5175
+ return [R(f"$rv32_instr[{j}]") for j in range(7)]
5176
+ m = re.match(r"^rv32\.decode\.use_([ISBUJ])$", gate)
5177
+ if m:
5178
+ return [R(f"rv32.decode.is_{cls}") for cls in RV32_USE[m.group(1)]]
5179
+ m = re.match(r"^rv32\.decode\.imm\.bit(\d+)\.and_([ISBUJ])$", gate)
5180
+ if m:
5181
+ k, fmt = int(m.group(1)), m.group(2)
5182
+ return [R(f"rv32.decode.use_{fmt}"), R(f"$rv32_instr[{_rv32_imm_src(fmt, k)}]")]
5183
+ m = re.match(r"^rv32\.decode\.imm\.bit(\d+)$", gate)
5184
+ if m:
5185
+ k = int(m.group(1))
5186
+ terms = [fmt for fmt in RV32_IMM_FMTS if _rv32_imm_src(fmt, k) is not None]
5187
+ return [R(f"rv32.decode.imm.bit{k}.and_{fmt}") for fmt in terms] if terms else [zero]
5188
+
5189
+ # PC sequencing: next-PC mux (pc4 / pc+imm / (rs1+imm)&~1).
5190
+ m = re.match(r"^rv32\.pcnext\.m1\.bit(\d+)\.(not_sel|and_a|and_b|or)$", gate)
5191
+ if m:
5192
+ k, kind = int(m.group(1)), m.group(2)
5193
+ if kind == "not_sel":
5194
+ return [R("$rv32_sel_target")]
5195
+ if kind == "and_a":
5196
+ return [R(f"$rv32_pc4[{k}]"), R(f"rv32.pcnext.m1.bit{k}.not_sel")]
5197
+ if kind == "and_b":
5198
+ return [R(f"$rv32_pcimm[{k}]"), R("$rv32_sel_target")]
5199
+ return [R(f"rv32.pcnext.m1.bit{k}.and_a"), R(f"rv32.pcnext.m1.bit{k}.and_b")]
5200
+ m = re.match(r"^rv32\.pcnext\.bit(\d+)\.(not_sel|and_a|and_b|or)$", gate)
5201
+ if m:
5202
+ k, kind = int(m.group(1)), m.group(2)
5203
+ if kind == "not_sel":
5204
+ return [R("$rv32_sel_jalr")]
5205
+ if kind == "and_a":
5206
+ return [R(f"rv32.pcnext.m1.bit{k}.or"), R(f"rv32.pcnext.bit{k}.not_sel")]
5207
+ if kind == "and_b":
5208
+ return [R(f"$rv32_jalr[{k}]"), R("$rv32_sel_jalr")]
5209
+ return [R(f"rv32.pcnext.bit{k}.and_a"), R(f"rv32.pcnext.bit{k}.and_b")]
5210
+
5211
  return None
5212
 
5213
 
 
6098
  add_gate(tensors, f"rv32.hazard.{pair}.all", [1.0] * 5, [-5.0])
6099
 
6100
 
6101
+ # Opcode -> class name for the instruction-decode network.
6102
+ RV32_OPCLASS = {0x33: "op", 0x13: "op_imm", 0x03: "load", 0x23: "store",
6103
+ 0x63: "branch", 0x37: "lui", 0x17: "auipc", 0x6F: "jal",
6104
+ 0x67: "jalr", 0x73: "system", 0x53: "fp", 0x07: "flw", 0x27: "fsw"}
6105
+ # Which opcode classes select each immediate format.
6106
+ RV32_USE = {"I": ["op_imm", "load", "jalr", "flw"], "S": ["store", "fsw"],
6107
+ "B": ["branch"], "U": ["lui", "auipc"], "J": ["jal"]}
6108
+ RV32_IMM_FMTS = ("I", "S", "B", "U", "J")
6109
+
6110
+
6111
+ def _rv32_imm_src(fmt: str, k: int):
6112
+ """Instruction bit that supplies immediate bit k for a format, or None (0)."""
6113
+ if fmt == "I":
6114
+ return (20 + k) if k <= 11 else 31
6115
+ if fmt == "S":
6116
+ if k <= 4:
6117
+ return 7 + k
6118
+ return (20 + k) if k <= 11 else 31
6119
+ if fmt == "B":
6120
+ if k == 0:
6121
+ return None
6122
+ if k <= 4:
6123
+ return 7 + k
6124
+ if k <= 10:
6125
+ return 20 + k
6126
+ if k == 11:
6127
+ return 7
6128
+ return 31
6129
+ if fmt == "U":
6130
+ return None if k < 12 else k
6131
+ if fmt == "J":
6132
+ if k == 0:
6133
+ return None
6134
+ if k <= 10:
6135
+ return 20 + k
6136
+ if k == 11:
6137
+ return 20
6138
+ if k <= 19:
6139
+ return k
6140
+ return 31
6141
+ return None
6142
+
6143
+
6144
+ def add_rv32_decode(tensors: Dict[str, torch.Tensor]) -> None:
6145
+ """Instruction decode as threshold gates: opcode-class one-hots (an exact
6146
+ 7-bit match on the instruction's opcode field) and the sign-extended
6147
+ immediate for the active format, muxed from the raw instruction word. The
6148
+ runtime reads these gate outputs instead of slicing the word in Python."""
6149
+ for opc, name in RV32_OPCLASS.items():
6150
+ w = [1.0 if (opc >> j) & 1 else -1.0 for j in range(7)]
6151
+ add_gate(tensors, f"rv32.decode.is_{name}", w, [-float(bin(opc).count("1"))])
6152
+ # FMADD/FMSUB/FNMADD/FNMSUB share the mask 100xx11 (instr bits 6,1,0 set; 5,4 clear).
6153
+ add_gate(tensors, "rv32.decode.is_fma", [1.0, 1.0, -1.0, -1.0, 1.0], [-3.0])
6154
+ for u, cls in RV32_USE.items():
6155
+ add_gate(tensors, f"rv32.decode.use_{u}", [1.0] * len(cls), [-1.0])
6156
+ for k in range(32):
6157
+ terms = [fmt for fmt in RV32_IMM_FMTS if _rv32_imm_src(fmt, k) is not None]
6158
+ for fmt in terms:
6159
+ add_gate(tensors, f"rv32.decode.imm.bit{k}.and_{fmt}", [1.0, 1.0], [-2.0])
6160
+ add_gate(tensors, f"rv32.decode.imm.bit{k}", [1.0] * max(1, len(terms)), [-1.0])
6161
+
6162
+
6163
+ def add_rv32_pcnext(tensors: Dict[str, torch.Tensor]) -> None:
6164
+ """PC sequencing as threshold gates: a two-level mux selecting the next PC
6165
+ among PC+4, PC+imm (branch/jal), and (rs1+imm)&~1 (jalr). The three
6166
+ candidates are the gate adder's outputs; this network chooses between them
6167
+ from the decode's control signals rather than a Python if/elif."""
6168
+ for k in range(32):
6169
+ # m1 = sel_target ? pcimm : pc4
6170
+ add_gate(tensors, f"rv32.pcnext.m1.bit{k}.not_sel", [-1.0], [0.0])
6171
+ add_gate(tensors, f"rv32.pcnext.m1.bit{k}.and_a", [1.0, 1.0], [-2.0])
6172
+ add_gate(tensors, f"rv32.pcnext.m1.bit{k}.and_b", [1.0, 1.0], [-2.0])
6173
+ add_gate(tensors, f"rv32.pcnext.m1.bit{k}.or", [1.0, 1.0], [-1.0])
6174
+ # pcnext = sel_jalr ? jalr : m1
6175
+ add_gate(tensors, f"rv32.pcnext.bit{k}.not_sel", [-1.0], [0.0])
6176
+ add_gate(tensors, f"rv32.pcnext.bit{k}.and_a", [1.0, 1.0], [-2.0])
6177
+ add_gate(tensors, f"rv32.pcnext.bit{k}.and_b", [1.0, 1.0], [-2.0])
6178
+ add_gate(tensors, f"rv32.pcnext.bit{k}.or", [1.0, 1.0], [-1.0])
6179
+
6180
+
6181
  def cmd_rv32(args) -> None:
6182
  """Build the standalone RV32 threshold processor file from scratch:
6183
  64 KB packed memory, the shared 32-bit circuit families, the composed
 
6205
  add_float_fma(tensors, "float32", 8, 23)
6206
  add_float32_cmp(tensors)
6207
  add_rv32_extras(tensors)
6208
+ add_rv32_decode(tensors)
6209
+ add_rv32_pcnext(tensors)
6210
  tensors["manifest.data_bits"] = torch.tensor([32.0], dtype=torch.float32)
6211
  tensors["manifest.addr_bits"] = torch.tensor([16.0], dtype=torch.float32)
6212
  tensors["manifest.memory_bytes"] = torch.tensor([65536.0], dtype=torch.float32)
src/machines.py CHANGED
@@ -573,6 +573,8 @@ def rv_state(mem):
573
  # =============================================================================
574
 
575
  PAIRABLE = (0x33, 0x13, 0x37, 0x17)
 
 
576
 
577
 
578
  class Rv32ThresholdCPU:
@@ -585,6 +587,8 @@ class Rv32ThresholdCPU:
585
  self.alu = GenericThresholdALU(self.T, 32)
586
  self.fpu = {op: NetlistEvaluator(self.T, self.registry, f"float32.{op}")
587
  for op in ("add", "mul", "div", "cmp", "fma")}
 
 
588
  self.pairs_issued = 0
589
 
590
  # ---- gate helpers -------------------------------------------------
@@ -926,13 +930,15 @@ class Rv32ThresholdCPU:
926
  f7 = w >> 25
927
  R = s["regs"]
928
  a = R[rs1]
 
 
929
  if op == 0x37:
930
- return rd, w & 0xFFFFF000
931
  if op == 0x17:
932
- return rd, self.add32(pc, w & 0xFFFFF000)
933
  if op == 0x13:
934
- b = u32(sext(w >> 20, 12))
935
- sh = (w >> 20) & 31
936
  imm_mode = True
937
  else:
938
  b = R[rs2]
@@ -1001,7 +1007,9 @@ class Rv32ThresholdCPU:
1001
  R = s["regs"]
1002
  FR = s["fregs"]
1003
  a, b = R[rs1], R[rs2]
1004
- nxt = (pc + 4) & MASK32
 
 
1005
 
1006
  def wr(v):
1007
  if rd:
@@ -1011,7 +1019,7 @@ class Rv32ThresholdCPU:
1011
  d, v = self._exec_alu_class(s, pc, w)
1012
  if d:
1013
  R[d] = u32(v)
1014
- if op == 0x33 and f7 == 1:
1015
  if f3 == 0:
1016
  wr(self.alu.mul_n(a, b, 32))
1017
  elif f3 == 1:
@@ -1028,40 +1036,29 @@ class Rv32ThresholdCPU:
1028
  wr(self.divs(a, b)[1])
1029
  elif f3 == 7:
1030
  wr(self.divu(a, b)[1])
1031
- elif op == 0x6F:
1032
- imm = sext((((w >> 31) & 1) << 20) | (((w >> 12) & 0xFF) << 12)
1033
- | (((w >> 20) & 1) << 11) | (((w >> 21) & 0x3FF) << 1), 21)
1034
- wr(nxt)
1035
- nxt = self.add32(pc, u32(imm))
1036
- elif op == 0x67:
1037
- t = self.add32(a, u32(sext(w >> 20, 12))) & ~1
1038
- wr(nxt)
1039
- nxt = t
1040
- elif op == 0x63:
1041
- imm = sext((((w >> 31) & 1) << 12) | (((w >> 7) & 1) << 11)
1042
- | (((w >> 25) & 0x3F) << 5) | (((w >> 8) & 0xF) << 1), 13)
1043
  eq = self.ucmp(a, b, "eq")
1044
  sl = self.scmp_lt(a, b)
1045
  ul = self.ucmp(a, b, "lessthan")
1046
- take = [eq, 1 - eq, 0, 0, sl, 1 - sl, ul, 1 - ul][f3]
1047
- if take:
1048
- nxt = self.add32(pc, u32(imm))
1049
- elif op == 0x03:
1050
- addr = self.add32(a, u32(sext(w >> 20, 12))) & 0xFFFF
1051
  width = [1, 2, 4, 0, 1, 2][f3]
1052
  wr(self._load(s, addr, width, f3 < 3))
1053
- elif op == 0x23:
1054
- imm = sext(((w >> 25) << 5) | ((w >> 7) & 31), 12)
1055
- addr = self.add32(a, u32(imm)) & 0xFFFF
1056
  self._store(s, addr, b, [1, 2, 4][f3])
1057
- elif op == 0x07:
1058
- addr = self.add32(a, u32(sext(w >> 20, 12))) & 0xFFFF
1059
  FR[rd] = self._load(s, addr, 4, False)
1060
- elif op == 0x27:
1061
- imm = sext(((w >> 25) << 5) | ((w >> 7) & 31), 12)
1062
- addr = self.add32(a, u32(imm)) & 0xFFFF
1063
  self._store(s, addr, FR[rs2], 4)
1064
- elif op == 0x53:
1065
  fa, fb = FR[rs1], FR[rs2]
1066
  if f7 in (0x00, 0x04, 0x08, 0x0C):
1067
  circ = {0x00: "add", 0x04: "add", 0x08: "mul", 0x0C: "div"}[f7]
@@ -1090,7 +1087,7 @@ class Rv32ThresholdCPU:
1090
  FR[rd] = a
1091
  elif f7 == 0x70:
1092
  wr(FR[rs1])
1093
- elif op in (0x43, 0x47, 0x4b, 0x4f): # FMADD/FMSUB/FNMSUB/FNMADD.S
1094
  rs3 = (w >> 27) & 0x1F
1095
  fa, fb, fc = FR[rs1], FR[rs2], FR[rs3]
1096
  if op in (0x4b, 0x4f): # negate the product
@@ -1100,13 +1097,17 @@ class Rv32ThresholdCPU:
1100
  FR[rd] = self._fpu_word3(fa, fb, fc)
1101
  elif op == 0x0B:
1102
  wr(self.neur(a, b))
1103
- elif op == 0x73:
1104
  s["halted"] = True
1105
  elif op == 0x0F:
1106
  pass
1107
  elif op not in PAIRABLE:
1108
  raise ValueError(f"illegal instruction {w:#010x} at {pc:#06x}")
1109
- s["pc"] = nxt
 
 
 
 
1110
  return s
1111
 
1112
  def _fpu_ext(self, fa, fb):
@@ -1144,6 +1145,30 @@ class Rv32ThresholdCPU:
1144
  v |= int(out[f"{p}.frac_out.bit{k}"][0, 0].item()) << k
1145
  return v
1146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
  def run(self, state, max_cycles=400):
1148
  s = dict(state)
1149
  s["_mem_bits"] = torch.tensor(
 
573
  # =============================================================================
574
 
575
  PAIRABLE = (0x33, 0x13, 0x37, 0x17)
576
+ DECODE_CLASSES = ("op", "op_imm", "load", "store", "branch", "lui", "auipc",
577
+ "jal", "jalr", "system", "fp", "flw", "fsw", "fma")
578
 
579
 
580
  class Rv32ThresholdCPU:
 
587
  self.alu = GenericThresholdALU(self.T, 32)
588
  self.fpu = {op: NetlistEvaluator(self.T, self.registry, f"float32.{op}")
589
  for op in ("add", "mul", "div", "cmp", "fma")}
590
+ self.decode = NetlistEvaluator(self.T, self.registry, "rv32.decode")
591
+ self.pcseq = NetlistEvaluator(self.T, self.registry, "rv32.pcnext")
592
  self.pairs_issued = 0
593
 
594
  # ---- gate helpers -------------------------------------------------
 
930
  f7 = w >> 25
931
  R = s["regs"]
932
  a = R[rs1]
933
+ if op in (0x37, 0x17, 0x13):
934
+ _, imm_g = self._decode(w) # gate-decoded immediate
935
  if op == 0x37:
936
+ return rd, imm_g
937
  if op == 0x17:
938
+ return rd, self.add32(pc, imm_g)
939
  if op == 0x13:
940
+ b = imm_g
941
+ sh = imm_g & 31
942
  imm_mode = True
943
  else:
944
  b = R[rs2]
 
1007
  R = s["regs"]
1008
  FR = s["fregs"]
1009
  a, b = R[rs1], R[rs2]
1010
+ cls, imm_g = self._decode(w) # gate decode: classes + immediate
1011
+ pc4 = self.add32(pc, 4) # PC+4 through the gate adder
1012
+ branch_taken = 0
1013
 
1014
  def wr(v):
1015
  if rd:
 
1019
  d, v = self._exec_alu_class(s, pc, w)
1020
  if d:
1021
  R[d] = u32(v)
1022
+ if cls["op"] and f7 == 1:
1023
  if f3 == 0:
1024
  wr(self.alu.mul_n(a, b, 32))
1025
  elif f3 == 1:
 
1036
  wr(self.divs(a, b)[1])
1037
  elif f3 == 7:
1038
  wr(self.divu(a, b)[1])
1039
+ elif cls["jal"]:
1040
+ wr(pc4) # return address
1041
+ elif cls["jalr"]:
1042
+ wr(pc4)
1043
+ elif cls["branch"]:
 
 
 
 
 
 
 
1044
  eq = self.ucmp(a, b, "eq")
1045
  sl = self.scmp_lt(a, b)
1046
  ul = self.ucmp(a, b, "lessthan")
1047
+ branch_taken = [eq, 1 - eq, 0, 0, sl, 1 - sl, ul, 1 - ul][f3]
1048
+ elif cls["load"]:
1049
+ addr = self.add32(a, imm_g) & 0xFFFF
 
 
1050
  width = [1, 2, 4, 0, 1, 2][f3]
1051
  wr(self._load(s, addr, width, f3 < 3))
1052
+ elif cls["store"]:
1053
+ addr = self.add32(a, imm_g) & 0xFFFF
 
1054
  self._store(s, addr, b, [1, 2, 4][f3])
1055
+ elif cls["flw"]:
1056
+ addr = self.add32(a, imm_g) & 0xFFFF
1057
  FR[rd] = self._load(s, addr, 4, False)
1058
+ elif cls["fsw"]:
1059
+ addr = self.add32(a, imm_g) & 0xFFFF
 
1060
  self._store(s, addr, FR[rs2], 4)
1061
+ elif cls["fp"]:
1062
  fa, fb = FR[rs1], FR[rs2]
1063
  if f7 in (0x00, 0x04, 0x08, 0x0C):
1064
  circ = {0x00: "add", 0x04: "add", 0x08: "mul", 0x0C: "div"}[f7]
 
1087
  FR[rd] = a
1088
  elif f7 == 0x70:
1089
  wr(FR[rs1])
1090
+ elif cls["fma"]: # FMADD/FMSUB/FNMSUB/FNMADD.S
1091
  rs3 = (w >> 27) & 0x1F
1092
  fa, fb, fc = FR[rs1], FR[rs2], FR[rs3]
1093
  if op in (0x4b, 0x4f): # negate the product
 
1097
  FR[rd] = self._fpu_word3(fa, fb, fc)
1098
  elif op == 0x0B:
1099
  wr(self.neur(a, b))
1100
+ elif cls["system"]:
1101
  s["halted"] = True
1102
  elif op == 0x0F:
1103
  pass
1104
  elif op not in PAIRABLE:
1105
  raise ValueError(f"illegal instruction {w:#010x} at {pc:#06x}")
1106
+ # PC sequencing through the gate mux: pc4 / pc+imm (branch,jal) / (rs1+imm)&~1 (jalr).
1107
+ pcimm = self.add32(pc, imm_g)
1108
+ jalr_t = self.add32(a, imm_g) & ~1
1109
+ sel_target = 1 if (branch_taken or cls["jal"]) else 0
1110
+ s["pc"] = self._pcnext(pc4, pcimm, jalr_t, sel_target, cls["jalr"])
1111
  return s
1112
 
1113
  def _fpu_ext(self, fa, fb):
 
1145
  v |= int(out[f"{p}.frac_out.bit{k}"][0, 0].item()) << k
1146
  return v
1147
 
1148
+ def _decode(self, w):
1149
+ """Instruction decode through the gate network: opcode-class one-hots
1150
+ and the sign-extended immediate for the active format."""
1151
+ ext = {f"$rv32_instr[{k}]": (w >> k) & 1 for k in range(32)}
1152
+ out = self.decode.run(ext)
1153
+ cls = {n: int(out[f"rv32.decode.is_{n}"][0, 0].item()) for n in DECODE_CLASSES}
1154
+ imm = 0
1155
+ for k in range(32):
1156
+ imm |= int(out[f"rv32.decode.imm.bit{k}"][0, 0].item()) << k
1157
+ return cls, imm
1158
+
1159
+ def _pcnext(self, pc4, pcimm, jalr, sel_target, sel_jalr):
1160
+ """Next-PC selection through the gate mux (pc4 / pc+imm / (rs1+imm)&~1)."""
1161
+ ext = {"$rv32_sel_target": sel_target, "$rv32_sel_jalr": sel_jalr}
1162
+ for k in range(32):
1163
+ ext[f"$rv32_pc4[{k}]"] = (pc4 >> k) & 1
1164
+ ext[f"$rv32_pcimm[{k}]"] = (pcimm >> k) & 1
1165
+ ext[f"$rv32_jalr[{k}]"] = (jalr >> k) & 1
1166
+ out = self.pcseq.run(ext)
1167
+ v = 0
1168
+ for k in range(32):
1169
+ v |= int(out[f"rv32.pcnext.bit{k}.or"][0, 0].item()) << k
1170
+ return v
1171
+
1172
  def run(self, state, max_cycles=400):
1173
  s = dict(state)
1174
  s["_mem_bits"] = torch.tensor(
variants/neural_rv32.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d329a8aedbf8e993be78df458f26044ace482b4ee146e0dee86612c12d5a345a
3
- size 43277092
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1947dab2cf586de70c3f799538eda4f129106d77ead404944df060349ff1380e
3
+ size 43429657