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

float FMA: fused multiply-add with a single rounding (F extension). A composed gate netlist (pre-normalize a/b/c, full product, dual sticky align shifters into a max-exponent field, cancellation-correct signed-magnitude add, normalize, one round-to-nearest-even, gradual underflow, full specials) for float16 and float32, validated bit-exact against the single-rounding oracle. FMADD/FMSUB/FNMADD/FNMSUB.S wired into the RV32 assembler, reference, and threshold CPU (gate-computed, lockstep-verified); composed FMA test in the eval suite; all variants and neural_rv32 rebuilt.

Browse files
README.md CHANGED
@@ -378,7 +378,7 @@ The most capable member: a RISC-V CPU whose entire datapath is ternary threshold
378
 
379
  - **RV32I base**: LUI, AUIPC, JAL, JALR, all six branches, LB/LH/LW/LBU/LHU, SB/SH/SW, and the full OP-IMM/OP groups. 32 × 32-bit registers (x0 zero), little-endian memory through the packed threshold circuits. ECALL halts.
380
  - **M extension**: MUL, MULH, MULHSU, MULHU (full 64-bit product through a shift-add array with gate-level sign correction), DIV, DIVU, REM, REMU (32 restoring stages, spec-exact divide-by-zero and overflow).
381
- - **F subset**: FLW, FSW, FMV.W.X, FMV.X.W, FADD.S, FSUB.S, FMUL.S, FDIV.S, FEQ.S, FLT.S, FLE.S, FSGNJ[N/X].S, FCVT.W.S (honors the instruction's rounding-mode field), FCVT.S.W — the arithmetic executed by the composed float32 pipelines (round-to-nearest-even, bit-exact to hardware; specials and subnormals as above), the int/float conversions gate-routed through the priority encoder and barrel shifter and cross-checked bit-exact against native conversion.
382
  - **NEUR (custom-0)**: `neur rd, rs1, rs2` evaluates one threshold neuron — `rd = H(popcount(rs1[7:0] & rs2[7:0]) - popcount(rs1[7:0] & rs2[15:8]) + sext(rs2[20:16]))`. Networks of NEUR instructions are neural networks running as software on the neural network; the test suite computes XOR with a two-layer NEUR net.
383
  - **Dual issue**: two adjacent OP/OP-IMM/LUI/AUIPC instructions retire in one cycle when the gate-level hazard comparators (`rv32.hazard.*`) clear RAW and WAW dependences.
384
  - **MMIO**: stores to `0xFF00` append a character to the console.
@@ -392,7 +392,7 @@ python src/machines.py rv32 # eight-program lock
392
  python src/machines.py rv32-c # stock-compiler C, end to end
393
  ```
394
 
395
- **Running compiled C.** `machines.py rv32-c` compiles a freestanding C program (gcd, Fibonacci, insertion sort; `rv32im`, so real `mul`/`rem`) with an unmodified clang rv32im toolchain, loads the relocatable object with an in-repo loader (no external linker — it resolves the R_RISCV relocations of one translation unit and lays the sections out flat), executes it on the threshold CPU, and checks the return value against the value computed natively. The program retires in ~300 instructions and matches exactly. Stock `rv32im` toolchains (gcc, clang, rustc) emit this ISA. The remaining float item, the fused multiply-add, is tracked in `todo.md`.
396
 
397
  The composed circuits evaluate in a leveled mode (`NetlistEvaluator`, one padded tensor op per topological level instead of one Python step per gate), ~18× faster on the FPU-scale netlists.
398
 
 
378
 
379
  - **RV32I base**: LUI, AUIPC, JAL, JALR, all six branches, LB/LH/LW/LBU/LHU, SB/SH/SW, and the full OP-IMM/OP groups. 32 × 32-bit registers (x0 zero), little-endian memory through the packed threshold circuits. ECALL halts.
380
  - **M extension**: MUL, MULH, MULHSU, MULHU (full 64-bit product through a shift-add array with gate-level sign correction), DIV, DIVU, REM, REMU (32 restoring stages, spec-exact divide-by-zero and overflow).
381
+ - **F subset**: FLW, FSW, FMV.W.X, FMV.X.W, FADD.S, FSUB.S, FMUL.S, FDIV.S, FMADD.S, FMSUB.S, FNMADD.S, FNMSUB.S, FEQ.S, FLT.S, FLE.S, FSGNJ[N/X].S, FCVT.W.S (honors the instruction's rounding-mode field), FCVT.S.W — the arithmetic executed by the composed float32 pipelines (round-to-nearest-even, bit-exact to hardware; specials and subnormals as above; the fused multiply-add rounds `a*b+c` with a single rounding), the int/float conversions gate-routed through the priority encoder and barrel shifter and cross-checked bit-exact against native conversion.
382
  - **NEUR (custom-0)**: `neur rd, rs1, rs2` evaluates one threshold neuron — `rd = H(popcount(rs1[7:0] & rs2[7:0]) - popcount(rs1[7:0] & rs2[15:8]) + sext(rs2[20:16]))`. Networks of NEUR instructions are neural networks running as software on the neural network; the test suite computes XOR with a two-layer NEUR net.
383
  - **Dual issue**: two adjacent OP/OP-IMM/LUI/AUIPC instructions retire in one cycle when the gate-level hazard comparators (`rv32.hazard.*`) clear RAW and WAW dependences.
384
  - **MMIO**: stores to `0xFF00` append a character to the console.
 
392
  python src/machines.py rv32-c # stock-compiler C, end to end
393
  ```
394
 
395
+ **Running compiled C.** `machines.py rv32-c` compiles a freestanding C program (gcd, Fibonacci, insertion sort; `rv32im`, so real `mul`/`rem`) with an unmodified clang rv32im toolchain, loads the relocatable object with an in-repo loader (no external linker — it resolves the R_RISCV relocations of one translation unit and lays the sections out flat), executes it on the threshold CPU, and checks the return value against the value computed natively. The program retires in ~300 instructions and matches exactly. Stock `rv32im` toolchains (gcc, clang, rustc) emit this ISA.
396
 
397
  The composed circuits evaluate in a leveled mode (`NetlistEvaluator`, one padded tensor op per topological level instead of one Python step per gate), ~18× faster on the FPU-scale netlists.
398
 
neural_computer.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c358cad0b5e5fd8cf83720eeb42a399047409d374932a9dc6ec3ebda754933bf
3
- size 34601074
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9257c5519edac4fb5f76724389d6950590c83b8fa6e5a5e48bb2eda41e208c08
3
+ size 45921927
src/build.py CHANGED
@@ -1651,6 +1651,302 @@ def add_float16_div(tensors: Dict[str, torch.Tensor]) -> None:
1651
  """Add the float16 division pipeline."""
1652
  add_float_div(tensors, "float16", 5, 10)
1653
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1654
  def add_float_cmp(tensors: Dict[str, torch.Tensor], family: str,
1655
  exp_bits: int, frac_bits: int) -> None:
1656
  """Add a complete IEEE 754 comparison network (EQ, LT, LE, GT, GE).
@@ -3672,6 +3968,585 @@ def infer_float_div_inputs(gate: str, reg: SignalRegistry, family: str,
3672
  return None
3673
 
3674
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3675
  def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
3676
  exp_bits: int, frac_bits: int) -> Optional[List[int]]:
3677
  """Complete wiring for the self-contained float addition pipeline."""
@@ -4488,14 +5363,15 @@ def infer_inputs_for_gate(gate: str, reg: SignalRegistry, tensors: Dict[str, tor
4488
  if rv is not None:
4489
  return rv
4490
  # Self-contained float networks (complete wiring, raw-word inputs).
4491
- m = re.match(r"^(float16|float32)\.(cmp|mul|div|add)(\.|$)", gate)
4492
  if m:
4493
  family = m.group(1)
4494
  e_bits, f_bits = (5, 10) if family == "float16" else (8, 23)
4495
  infer_fn = {"cmp": infer_float_cmp_inputs,
4496
  "mul": infer_float_mul_inputs,
4497
  "div": infer_float_div_inputs,
4498
- "add": infer_float_add_inputs}[m.group(2)]
 
4499
  fc = infer_fn(gate, reg, family, e_bits, f_bits)
4500
  if fc is not None:
4501
  return fc
@@ -5040,6 +5916,13 @@ def cmd_alu(args) -> None:
5040
  except ValueError as e:
5041
  print(f" float16 DIV already exists: {e}")
5042
 
 
 
 
 
 
 
 
5043
  print("\nGenerating float16 CMP circuits...")
5044
  try:
5045
  add_float16_cmp(tensors)
@@ -5075,6 +5958,13 @@ def cmd_alu(args) -> None:
5075
  except ValueError as e:
5076
  print(f" float32 DIV already exists: {e}")
5077
 
 
 
 
 
 
 
 
5078
  print("\nGenerating float32 CMP circuits...")
5079
  try:
5080
  add_float32_cmp(tensors)
@@ -5190,6 +6080,7 @@ def cmd_rv32(args) -> None:
5190
  add_float32_add(tensors)
5191
  add_float32_mul(tensors)
5192
  add_float32_div(tensors)
 
5193
  add_float32_cmp(tensors)
5194
  add_rv32_extras(tensors)
5195
  tensors["manifest.data_bits"] = torch.tensor([32.0], dtype=torch.float32)
 
1651
  """Add the float16 division pipeline."""
1652
  add_float_div(tensors, "float16", 5, 10)
1653
 
1654
+
1655
+ def add_float_fma(tensors: Dict[str, torch.Tensor], family: str,
1656
+ exp_bits: int, frac_bits: int) -> None:
1657
+ """Fused multiply-add: round(a*b + c) with a SINGLE rounding (F-extension).
1658
+
1659
+ Self-contained; external inputs are the raw operand words $a/$b/$c
1660
+ (MSB-first). Contract: exact IEEE specials (NaN, infinities, inf*0 and
1661
+ inf-inf -> NaN, signed zeros), subnormal operands and gradual-underflow
1662
+ subnormal results, round-to-nearest-even, single rounding of a*b+c.
1663
+
1664
+ Datapath: each mantissa is pre-normalized (leading 1 at bit F, na=eexp-lz);
1665
+ the (F+1)x(F+1) product Mp (2F+2 bits) and the addend Mc are right-shifted by
1666
+ sticky barrel shifters into a common 3(F+1)+4-bit field anchored at
1667
+ max(product,c) exponent; a signed magnitude add (with a borrow when the
1668
+ shifted-out subtrahend leaves a tail) forms the sum; a leading-zero count and
1669
+ normalize give the significand, one round-to-nearest-even rounds it, and a
1670
+ one-hot specials network packs the result.
1671
+ """
1672
+ E, F = exp_bits, frac_bits
1673
+ W = 1 + E + F
1674
+ p = F + 1
1675
+ PW = 2 * p # product width
1676
+ WF = 3 * p + 4 # alignment field width
1677
+ prefix = f"{family}.fma"
1678
+ bias = (1 << (E - 1)) - 1
1679
+
1680
+ ML = F + 1
1681
+ mlzb = 0
1682
+ while (1 << mlzb) <= ML:
1683
+ mlzb += 1
1684
+ SB = 0 # bits to hold a shift amount up to WF
1685
+ while (1 << SB) <= WF:
1686
+ SB += 1
1687
+ NB = 0 # bits for the field leading-zero count
1688
+ while (1 << NB) <= WF:
1689
+ NB += 1
1690
+
1691
+ # sign of the product sp = sa ^ sb (c's sign is c(0) directly).
1692
+ add_gate(tensors, f"{prefix}.sign_p.layer1.or", [1.0, 1.0], [-1.0])
1693
+ add_gate(tensors, f"{prefix}.sign_p.layer1.nand", [-1.0, -1.0], [1.0])
1694
+ add_gate(tensors, f"{prefix}.sign_p.layer2", [1.0, 1.0], [-2.0])
1695
+
1696
+ # operand classification.
1697
+ for op in ("a", "b", "c"):
1698
+ add_gate(tensors, f"{prefix}.{op}.exp_zero", [-1.0] * E, [0.0])
1699
+ add_gate(tensors, f"{prefix}.{op}.exp_nzero", [1.0] * E, [-1.0])
1700
+ add_gate(tensors, f"{prefix}.{op}.exp_max", [1.0] * E, [-float(E)])
1701
+ add_gate(tensors, f"{prefix}.{op}.frac_nz", [1.0] * F, [-1.0])
1702
+ add_gate(tensors, f"{prefix}.{op}.frac_zero", [-1.0] * F, [0.0])
1703
+ add_gate(tensors, f"{prefix}.{op}.is_nan", [1.0, 1.0], [-2.0])
1704
+ add_gate(tensors, f"{prefix}.{op}.is_inf", [1.0, 1.0], [-2.0])
1705
+ add_gate(tensors, f"{prefix}.{op}.is_zero", [1.0, 1.0], [-2.0])
1706
+ add_gate(tensors, f"{prefix}.{op}.nonzero", [-1.0], [0.0])
1707
+ add_gate(tensors, f"{prefix}.{op}.eexp_lsb", [1.0, 1.0], [-1.0])
1708
+
1709
+ # pre-normalize each mantissa: leading 1 to bit F, na = eexp - lz.
1710
+ for op in ("a", "b", "c"):
1711
+ for pos in range(1, ML):
1712
+ add_gate(tensors, f"{prefix}.{op}.pn.any_higher{pos}", [1.0] * pos, [-1.0])
1713
+ add_gate(tensors, f"{prefix}.{op}.pn.is_highest{pos}.not_higher", [-1.0], [0.0])
1714
+ add_gate(tensors, f"{prefix}.{op}.pn.is_highest{pos}.and", [1.0, 1.0], [-2.0])
1715
+ for bbit in range(mlzb):
1716
+ fan = sum(1 for pos in range(1, ML) if (pos >> bbit) & 1)
1717
+ add_gate(tensors, f"{prefix}.{op}.pn.lz.bit{bbit}", [1.0] * max(1, fan), [-1.0])
1718
+ for bbit in range(mlzb):
1719
+ for k in range(ML):
1720
+ add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.not_sel", [-1.0], [0.0])
1721
+ add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.and_a", [1.0, 1.0], [-2.0])
1722
+ add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.and_b", [1.0, 1.0], [-2.0])
1723
+ add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.or", [1.0, 1.0], [-1.0])
1724
+ for bbit in range(mlzb):
1725
+ add_gate(tensors, f"{prefix}.{op}.nlz.bit{bbit}", [-1.0], [0.0])
1726
+ for bit in range(E + 2):
1727
+ add_full_adder(tensors, f"{prefix}.{op}.neff.fa{bit}")
1728
+
1729
+ # product Mp = Ma * Mb (PW bits) by shift-add over the pre-normalized mantissas.
1730
+ for i in range(p):
1731
+ for j in range(p):
1732
+ add_gate(tensors, f"{prefix}.pp.a{i}b{j}", [1.0, 1.0], [-2.0])
1733
+ for stage in range(F):
1734
+ for bit in range(PW):
1735
+ add_full_adder(tensors, f"{prefix}.acc.s{stage}.fa{bit}")
1736
+
1737
+ # exponent alignment (E+3-bit two's complement). q1 = na+nb - bias + 1 (product
1738
+ # MSB exponent); q2 = nc; d = q1 - q2; neg_d = sign(d); |d|; sbig = |d| + 1.
1739
+ # rp = neg_d ? sbig : 1, rc = neg_d ? 1 : sbig; A = max(q1,q2)+1 = neg_d ? q2+1 : q1+1.
1740
+ for bit in range(E + 3):
1741
+ add_full_adder(tensors, f"{prefix}.nab.fa{bit}") # na + nb
1742
+ for bit in range(E + 3):
1743
+ add_full_adder(tensors, f"{prefix}.q1.fa{bit}") # nab + (1 - bias)
1744
+ # zero operands carry no exponent; force their q to a sentinel negative value
1745
+ # (sign bit set, rest 0) so max(q1,q2) picks the nonzero operand.
1746
+ add_gate(tensors, f"{prefix}.prod_zero", [1.0, 1.0], [-1.0]) # a_zero | b_zero
1747
+ add_gate(tensors, f"{prefix}.not_prod_zero", [-1.0], [0.0])
1748
+ add_gate(tensors, f"{prefix}.not_c_zero", [-1.0], [0.0])
1749
+ for bit in range(E + 3):
1750
+ if bit in (E + 1, E + 2): # sentinel -2^(E+1)
1751
+ add_gate(tensors, f"{prefix}.q1e.bit{bit}", [1.0, 1.0], [-1.0]) # prod_zero | q1
1752
+ add_gate(tensors, f"{prefix}.q2e.bit{bit}", [1.0, 1.0], [-1.0]) # c_zero | q2
1753
+ else:
1754
+ add_gate(tensors, f"{prefix}.q1e.bit{bit}", [1.0, 1.0], [-2.0]) # ~prod_zero & q1
1755
+ add_gate(tensors, f"{prefix}.q2e.bit{bit}", [1.0, 1.0], [-2.0]) # ~c_zero & q2
1756
+ for bit in range(E + 3):
1757
+ add_gate(tensors, f"{prefix}.ncnot.bit{bit}", [-1.0], [0.0]) # ~q2e
1758
+ add_full_adder(tensors, f"{prefix}.d.fa{bit}") # q1e + ~q2e + 1 = q1e - q2e
1759
+ add_gate(tensors, f"{prefix}.not_neg_d", [-1.0], [0.0]) # ~sign(d)
1760
+ for bit in range(E + 3):
1761
+ add_gate(tensors, f"{prefix}.dxor.bit{bit}.a", [1.0, 1.0], [-1.0])
1762
+ add_gate(tensors, f"{prefix}.dxor.bit{bit}.b", [-1.0, -1.0], [1.0])
1763
+ add_gate(tensors, f"{prefix}.dxor.bit{bit}", [1.0, 1.0], [-2.0]) # d ^ neg_d
1764
+ add_full_adder(tensors, f"{prefix}.dabs.fa{bit}") # dxor + neg_d = |d|
1765
+ for bit in range(E + 3):
1766
+ add_full_adder(tensors, f"{prefix}.sbig.fa{bit}") # |d| + 1
1767
+ for bit in range(SB):
1768
+ add_gate(tensors, f"{prefix}.rp.bit{bit}.and_big", [1.0, 1.0], [-2.0])
1769
+ add_gate(tensors, f"{prefix}.rp.bit{bit}.and_one", [1.0, 1.0], [-2.0])
1770
+ add_gate(tensors, f"{prefix}.rp.bit{bit}.or", [1.0, 1.0], [-1.0])
1771
+ add_gate(tensors, f"{prefix}.rc.bit{bit}.and_big", [1.0, 1.0], [-2.0])
1772
+ add_gate(tensors, f"{prefix}.rc.bit{bit}.and_one", [1.0, 1.0], [-2.0])
1773
+ add_gate(tensors, f"{prefix}.rc.bit{bit}.or", [1.0, 1.0], [-1.0])
1774
+ add_gate(tensors, f"{prefix}.sbig.high", [1.0] * (E + 3 - SB), [-1.0]) # sbig >= 2^SB
1775
+ add_gate(tensors, f"{prefix}.rp.deep", [1.0, 1.0], [-2.0]) # neg_d & sbig.high
1776
+ add_gate(tensors, f"{prefix}.rc.deep", [1.0, 1.0], [-2.0]) # ~neg_d & sbig.high
1777
+ for bit in range(E + 3):
1778
+ add_full_adder(tensors, f"{prefix}.q1p.fa{bit}") # q1 + 1
1779
+ add_full_adder(tensors, f"{prefix}.q2p.fa{bit}") # nc + 1
1780
+ add_gate(tensors, f"{prefix}.atop.bit{bit}.and_p", [1.0, 1.0], [-2.0])
1781
+ add_gate(tensors, f"{prefix}.atop.bit{bit}.and_c", [1.0, 1.0], [-2.0])
1782
+ add_gate(tensors, f"{prefix}.atop.bit{bit}.or", [1.0, 1.0], [-1.0])
1783
+
1784
+ # two sticky right-barrel shifters into the WF-bit field.
1785
+ for tag in ("pf", "cf"):
1786
+ for j in range(SB):
1787
+ add_gate(tensors, f"{prefix}.{tag}.s{j}.drop", [1.0] * (1 << j), [-1.0])
1788
+ add_gate(tensors, f"{prefix}.{tag}.s{j}.st_and", [1.0, 1.0], [-2.0])
1789
+ add_gate(tensors, f"{prefix}.{tag}.s{j}.sticky", [1.0, 1.0], [-1.0])
1790
+ for k in range(WF):
1791
+ add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.not_sel", [-1.0], [0.0])
1792
+ add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0])
1793
+ add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0])
1794
+ add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.or", [1.0, 1.0], [-1.0])
1795
+ add_gate(tensors, f"{prefix}.{tag}.not_deep", [-1.0], [0.0])
1796
+ for k in range(WF): # field masked to 0 when deep
1797
+ add_gate(tensors, f"{prefix}.{tag}.field.bit{k}", [1.0, 1.0], [-2.0])
1798
+ add_gate(tensors, f"{prefix}.{tag}.nz", [1.0] * WF, [-1.0])
1799
+ add_gate(tensors, f"{prefix}.{tag}.deep_nz", [1.0, 1.0], [-2.0]) # deep & operand nonzero
1800
+ add_gate(tensors, f"{prefix}.{tag}.sticky", [1.0, 1.0], [-1.0]) # barrel | deep_nz
1801
+
1802
+ # signed magnitude combine. eff_sub = sp ^ sc.
1803
+ add_gate(tensors, f"{prefix}.eff_sub.layer1.or", [1.0, 1.0], [-1.0])
1804
+ add_gate(tensors, f"{prefix}.eff_sub.layer1.nand", [-1.0, -1.0], [1.0])
1805
+ add_gate(tensors, f"{prefix}.eff_sub", [1.0, 1.0], [-2.0])
1806
+ add_gate(tensors, f"{prefix}.eff_add", [-1.0], [0.0])
1807
+ add_gate(tensors, f"{prefix}.s_tail", [1.0, 1.0], [-1.0]) # pf.sticky | cf.sticky (shifted-out)
1808
+ add_gate(tensors, f"{prefix}.not_s_tail", [-1.0], [0.0])
1809
+ # sub path: D = PF - CF (WF+1 bits); ge = PF >= CF = ~sign(D); borrow for a tail.
1810
+ for bit in range(WF):
1811
+ add_gate(tensors, f"{prefix}.cfnot.bit{bit}", [-1.0], [0.0])
1812
+ for bit in range(WF + 1):
1813
+ add_full_adder(tensors, f"{prefix}.dsum.fa{bit}") # PF + ~CF + cin
1814
+ add_gate(tensors, f"{prefix}.ge", [-1.0], [0.0]) # ~sign(D)
1815
+ add_gate(tensors, f"{prefix}.not_ge", [-1.0], [0.0])
1816
+ add_gate(tensors, f"{prefix}.borrow_in", [1.0, 1.0], [-2.0]) # eff_sub & ~s_tail -> cin=1
1817
+ for bit in range(WF + 1): # dmagx = D ^ sign(D)
1818
+ add_gate(tensors, f"{prefix}.dmagx.bit{bit}.a", [1.0, 1.0], [-1.0]) # OR
1819
+ add_gate(tensors, f"{prefix}.dmagx.bit{bit}.b", [-1.0, -1.0], [1.0]) # NAND
1820
+ add_gate(tensors, f"{prefix}.dmagx.bit{bit}", [1.0, 1.0], [-2.0]) # AND(or, nand) = XOR
1821
+ for bit in range(WF + 1):
1822
+ add_full_adder(tensors, f"{prefix}.dmag.fa{bit}") # + (~ge as +1 for complement)
1823
+ # add path: A = PF + CF (WF+1 bits).
1824
+ for bit in range(WF + 1):
1825
+ add_full_adder(tensors, f"{prefix}.asum.fa{bit}")
1826
+ # mag = eff_sub ? dmag : asum (WF+1 bits)
1827
+ for bit in range(WF + 1):
1828
+ add_gate(tensors, f"{prefix}.mag.bit{bit}.and_sub", [1.0, 1.0], [-2.0])
1829
+ add_gate(tensors, f"{prefix}.mag.bit{bit}.and_add", [1.0, 1.0], [-2.0])
1830
+ add_gate(tensors, f"{prefix}.mag.bit{bit}.or", [1.0, 1.0], [-1.0])
1831
+
1832
+ # leading-zero count over mag (WF+1 bits) and normalize.
1833
+ MG = WF + 1
1834
+ for pos in range(1, MG):
1835
+ add_gate(tensors, f"{prefix}.lz.any_higher{pos}", [1.0] * pos, [-1.0])
1836
+ add_gate(tensors, f"{prefix}.lz.is_highest{pos}.not_higher", [-1.0], [0.0])
1837
+ add_gate(tensors, f"{prefix}.lz.is_highest{pos}.and", [1.0, 1.0], [-2.0])
1838
+ add_gate(tensors, f"{prefix}.mag.nz", [1.0] * MG, [-1.0])
1839
+ for bbit in range(NB):
1840
+ fan = sum(1 for pos in range(1, MG) if (pos >> bbit) & 1)
1841
+ add_gate(tensors, f"{prefix}.lz.bit{bbit}", [1.0] * max(1, fan), [-1.0])
1842
+ for j in range(NB): # left-normalize barrel
1843
+ for k in range(MG):
1844
+ add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.not_sel", [-1.0], [0.0])
1845
+ add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0])
1846
+ add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0])
1847
+ add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.or", [1.0, 1.0], [-1.0])
1848
+
1849
+ # er = A - lz (biased result exponent), E+3-bit.
1850
+ for bit in range(E + 3):
1851
+ add_gate(tensors, f"{prefix}.nlzf.bit{bit}", [-1.0], [0.0])
1852
+ add_full_adder(tensors, f"{prefix}.er.fa{bit}")
1853
+ add_gate(tensors, f"{prefix}.er.underflow", [1.0, 1.0], [-1.0]) # er <= 0
1854
+ add_gate(tensors, f"{prefix}.er.zero", [-1.0] * (E + 3), [0.0])
1855
+ add_gate(tensors, f"{prefix}.er.not_underflow", [-1.0], [0.0])
1856
+ for bit in range(E + 3):
1857
+ add_gate(tensors, f"{prefix}.exprc.bit{bit}", [1.0, 1.0], [-2.0])
1858
+
1859
+ # subnormal-aware round. Build the mantissa frame from the normalized
1860
+ # significand (bit 0 guard, bits 1..F frac, bit F+1 implicit), right-shift it
1861
+ # by max(0,1-er) for gradual underflow, then round-to-nearest-even once.
1862
+ add_gate(tensors, f"{prefix}.round.sticky_lo", [1.0] * (MG - (F + 2)), [-1.0])
1863
+ add_gate(tensors, f"{prefix}.round.sticky_pre", [1.0, 1.0], [-1.0]) # sticky_lo | s_tail
1864
+ RBd = 0
1865
+ while (1 << RBd) < (F + 2):
1866
+ RBd += 1
1867
+ for bit in range(E + 3):
1868
+ add_gate(tensors, f"{prefix}.ernot.bit{bit}", [-1.0], [0.0])
1869
+ add_full_adder(tensors, f"{prefix}.subv.fa{bit}") # 1 - er
1870
+ add_gate(tensors, f"{prefix}.rsh.bit{bit}", [1.0, 1.0], [-2.0])
1871
+ add_gate(tensors, f"{prefix}.too_deep", [1.0] * (E + 3 - RBd), [-1.0])
1872
+ add_gate(tensors, f"{prefix}.not_deep", [-1.0], [0.0])
1873
+ for j in range(RBd):
1874
+ add_gate(tensors, f"{prefix}.dsh.s{j}.drop", [1.0] * (1 << j), [-1.0])
1875
+ add_gate(tensors, f"{prefix}.dsh.s{j}.st_and", [1.0, 1.0], [-2.0])
1876
+ add_gate(tensors, f"{prefix}.dsh.s{j}.sticky", [1.0, 1.0], [-1.0])
1877
+ for k in range(F + 2):
1878
+ add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.not_sel", [-1.0], [0.0])
1879
+ add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0])
1880
+ add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0])
1881
+ add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.or", [1.0, 1.0], [-1.0])
1882
+ for k in range(F):
1883
+ add_gate(tensors, f"{prefix}.sig.bit{k}", [1.0, 1.0], [-2.0]) # shifted(k+1) & not_deep
1884
+ add_gate(tensors, f"{prefix}.round.guard", [1.0, 1.0], [-2.0]) # shifted(0) & not_deep
1885
+ add_gate(tensors, f"{prefix}.round.sticky", [1.0, 1.0, 1.0], [-1.0]) # pre | barrel | too_deep
1886
+ add_gate(tensors, f"{prefix}.round.rsl", [1.0, 1.0], [-1.0])
1887
+ add_gate(tensors, f"{prefix}.round.up", [1.0, 1.0], [-2.0])
1888
+ for k in range(F):
1889
+ add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.or", [1.0, 1.0], [-1.0])
1890
+ add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.nand", [-1.0, -1.0], [1.0])
1891
+ add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer2", [1.0, 1.0], [-2.0])
1892
+ add_gate(tensors, f"{prefix}.rnd.bit{k}.carry", [1.0, 1.0], [-2.0])
1893
+ for bit in range(E + 3):
1894
+ add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0])
1895
+ add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0])
1896
+ add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0])
1897
+ add_gate(tensors, f"{prefix}.exp_round.bit{bit}.carry", [1.0, 1.0], [-2.0])
1898
+
1899
+ # specials + range detectors + output pack (priority NaN > Inf > Zero > Normal).
1900
+ add_gate(tensors, f"{prefix}.p_inf", [1.0, 1.0], [-1.0]) # a_inf | b_inf
1901
+ add_gate(tensors, f"{prefix}.not_p_inf", [-1.0], [0.0])
1902
+ add_gate(tensors, f"{prefix}.inf0a", [1.0, 1.0], [-2.0]) # a_inf & b_zero
1903
+ add_gate(tensors, f"{prefix}.inf0b", [1.0, 1.0], [-2.0]) # b_inf & a_zero
1904
+ add_gate(tensors, f"{prefix}.inf0", [1.0, 1.0], [-1.0])
1905
+ add_gate(tensors, f"{prefix}.pc_sdiff.or", [1.0, 1.0], [-1.0])
1906
+ add_gate(tensors, f"{prefix}.pc_sdiff.nand", [-1.0, -1.0], [1.0])
1907
+ add_gate(tensors, f"{prefix}.pc_sdiff", [1.0, 1.0], [-2.0]) # sp ^ sc
1908
+ add_gate(tensors, f"{prefix}.infinf_nan", [1.0, 1.0, 1.0], [-3.0]) # p_inf & c_inf & sdiff
1909
+ add_gate(tensors, f"{prefix}.in_nan", [1.0, 1.0, 1.0], [-1.0]) # a/b/c nan
1910
+ add_gate(tensors, f"{prefix}.nan", [1.0, 1.0, 1.0], [-1.0]) # in_nan | inf0 | infinf_nan
1911
+ add_gate(tensors, f"{prefix}.not_nan", [-1.0], [0.0])
1912
+ add_gate(tensors, f"{prefix}.any_inf", [1.0, 1.0], [-1.0]) # p_inf | c_inf
1913
+ add_gate(tensors, f"{prefix}.not_any_inf", [-1.0], [0.0])
1914
+ add_gate(tensors, f"{prefix}.er.and_low", [1.0] * E, [-float(E)]) # exp_round low E bits all 1
1915
+ add_gate(tensors, f"{prefix}.er.hi", [1.0, 1.0, 1.0], [-1.0]) # exp_round bit E/E+1/E+2 set
1916
+ add_gate(tensors, f"{prefix}.er.ge_emax", [1.0, 1.0], [-1.0])
1917
+ add_gate(tensors, f"{prefix}.overflow", [1.0, 1.0], [-2.0]) # ge_emax & not_underflow
1918
+ add_gate(tensors, f"{prefix}.inf_or_ovf", [1.0, 1.0], [-1.0]) # any_inf | overflow
1919
+ add_gate(tensors, f"{prefix}.not_inf_path", [-1.0], [0.0])
1920
+ add_gate(tensors, f"{prefix}.inf", [1.0, 1.0], [-2.0]) # not_nan & inf_or_ovf
1921
+ add_gate(tensors, f"{prefix}.res_zero", [-1.0] * MG, [0.0]) # mag == 0
1922
+ add_gate(tensors, f"{prefix}.not_res_zero", [-1.0], [0.0])
1923
+ add_gate(tensors, f"{prefix}.zero", [1.0, 1.0, 1.0], [-3.0]) # not_nan & not_inf_path & res_zero
1924
+ add_gate(tensors, f"{prefix}.norm", [1.0, 1.0, 1.0], [-3.0]) # not_nan & not_inf_path & ~res_zero
1925
+ # finite-path magnitude sign: eff_sub ? (ge?sp:sc) : sp.
1926
+ add_gate(tensors, f"{prefix}.fin_sign.sub.and_ge", [1.0, 1.0], [-2.0])
1927
+ add_gate(tensors, f"{prefix}.fin_sign.sub.and_lt", [1.0, 1.0], [-2.0])
1928
+ add_gate(tensors, f"{prefix}.fin_sign.sub", [1.0, 1.0], [-1.0])
1929
+ add_gate(tensors, f"{prefix}.fin_sign.and_sub", [1.0, 1.0], [-2.0])
1930
+ add_gate(tensors, f"{prefix}.fin_sign.and_add", [1.0, 1.0], [-2.0])
1931
+ add_gate(tensors, f"{prefix}.fin_sign", [1.0, 1.0], [-1.0])
1932
+ add_gate(tensors, f"{prefix}.p_zero", [1.0, 1.0], [-1.0]) # a_zero | b_zero
1933
+ add_gate(tensors, f"{prefix}.bothzero", [1.0, 1.0], [-2.0]) # p_zero & c_zero
1934
+ add_gate(tensors, f"{prefix}.negzero", [1.0, 1.0, 1.0], [-3.0]) # bothzero & sp & sc
1935
+ add_gate(tensors, f"{prefix}.sign_z.fin", [1.0, 1.0], [-2.0]) # fin_sign & ~res_zero
1936
+ add_gate(tensors, f"{prefix}.sign_z", [1.0, 1.0], [-1.0]) # sign_z.fin | negzero
1937
+ add_gate(tensors, f"{prefix}.sign.term_p", [1.0, 1.0], [-2.0]) # p_inf & sp
1938
+ add_gate(tensors, f"{prefix}.sign.term_c", [1.0, 1.0, 1.0], [-3.0]) # ~p_inf & c_inf & sc
1939
+ add_gate(tensors, f"{prefix}.sign.term_z", [1.0, 1.0], [-2.0]) # ~any_inf & sign_z
1940
+ add_gate(tensors, f"{prefix}.sign_pre", [1.0, 1.0, 1.0], [-1.0])
1941
+ add_gate(tensors, f"{prefix}.sign_out", [1.0, 1.0], [-2.0]) # sign_pre & not_nan
1942
+ for k in range(E):
1943
+ add_gate(tensors, f"{prefix}.exp_out.bit{k}.norm_and", [1.0, 1.0], [-2.0])
1944
+ add_gate(tensors, f"{prefix}.exp_out.bit{k}", [1.0, 1.0, 1.0], [-1.0])
1945
+ for k in range(F):
1946
+ add_gate(tensors, f"{prefix}.frac_out.bit{k}.norm_and", [1.0, 1.0], [-2.0])
1947
+ add_gate(tensors, f"{prefix}.frac_out.bit{k}", [1.0, 1.0], [-1.0])
1948
+
1949
+
1950
  def add_float_cmp(tensors: Dict[str, torch.Tensor], family: str,
1951
  exp_bits: int, frac_bits: int) -> None:
1952
  """Add a complete IEEE 754 comparison network (EQ, LT, LE, GT, GE).
 
3968
  return None
3969
 
3970
 
3971
+ def infer_float_fma_inputs(gate: str, reg: SignalRegistry, family: str,
3972
+ exp_bits: int, frac_bits: int) -> Optional[List[int]]:
3973
+ """Complete wiring for the self-contained fused-multiply-add pipeline."""
3974
+ E, F = exp_bits, frac_bits
3975
+ W = 1 + E + F
3976
+ p = F + 1
3977
+ PW = 2 * p
3978
+ WF = 3 * p + 4
3979
+ MG = WF + 1
3980
+ prefix = f"{family}.fma"
3981
+ if gate != prefix and not gate.startswith(prefix + "."):
3982
+ return None
3983
+ bias = (1 << (E - 1)) - 1
3984
+ for i in range(W):
3985
+ reg.register(f"$a[{i}]")
3986
+ reg.register(f"$b[{i}]")
3987
+ reg.register(f"$c[{i}]")
3988
+
3989
+ def a(i: int) -> int:
3990
+ return reg.get_id(f"$a[{i}]")
3991
+
3992
+ def b(i: int) -> int:
3993
+ return reg.get_id(f"$b[{i}]")
3994
+
3995
+ def c(i: int) -> int:
3996
+ return reg.get_id(f"$c[{i}]")
3997
+
3998
+ def R(name: str) -> int:
3999
+ return reg.register(f"{prefix}.{name}")
4000
+
4001
+ zero = reg.get_id("#0")
4002
+ one = reg.get_id("#1")
4003
+ WOP = {"a": a, "b": b, "c": c}
4004
+ ML = F + 1
4005
+ mlzb = 0
4006
+ while (1 << mlzb) <= ML:
4007
+ mlzb += 1
4008
+ SB = 0
4009
+ while (1 << SB) <= WF:
4010
+ SB += 1
4011
+ NB = 0
4012
+ while (1 << NB) <= WF:
4013
+ NB += 1
4014
+ RBd = 0
4015
+ while (1 << RBd) < (F + 2):
4016
+ RBd += 1
4017
+
4018
+ def m_raw(op: str, k: int) -> int:
4019
+ return R(f"{op}.exp_nzero") if k == F else WOP[op](W - 1 - k)
4020
+
4021
+ def Mn(op: str, k: int) -> int:
4022
+ return R(f"{op}.pn.s{mlzb - 1}.bit{k}.or")
4023
+
4024
+ def eexp(op: str, k: int) -> int:
4025
+ w = WOP[op]
4026
+ return R(f"{op}.eexp_lsb") if k == 0 else (w(E - k) if k < E else zero)
4027
+
4028
+ def na(op: str, k: int) -> int: # signed, sign-extended from bit E+1
4029
+ return R(f"{op}.neff.fa{min(k, E + 1)}.ha2.sum.layer2")
4030
+
4031
+ def Pbit(k: int) -> int:
4032
+ return R(f"acc.s{F - 1}.fa{k}.ha2.sum.layer2")
4033
+
4034
+ def Pt(m: int) -> int: # top-aligned product field bit
4035
+ k = m - (WF - PW)
4036
+ return Pbit(k) if 0 <= k < PW else zero
4037
+
4038
+ def Ct(m: int) -> int: # top-aligned addend field bit
4039
+ k = m - (WF - p)
4040
+ return Mn("c", k) if 0 <= k < p else zero
4041
+
4042
+ sp = R("sign_p.layer2")
4043
+ sc = c(0)
4044
+ q1c = (1 - bias) & ((1 << (E + 3)) - 1) # constant nab + (1-bias)
4045
+ suffix = gate[len(prefix) + 1:] if gate != prefix else ""
4046
+
4047
+ table = {
4048
+ "sign_p.layer1.or": [a(0), b(0)],
4049
+ "sign_p.layer1.nand": [a(0), b(0)],
4050
+ "sign_p.layer2": [R("sign_p.layer1.or"), R("sign_p.layer1.nand")],
4051
+ }
4052
+ for op in ("a", "b", "c"):
4053
+ w = WOP[op]
4054
+ expf = [w(1 + i) for i in range(E)]
4055
+ fracf = [w(1 + E + i) for i in range(F)]
4056
+ table[f"{op}.exp_zero"] = expf
4057
+ table[f"{op}.exp_nzero"] = expf
4058
+ table[f"{op}.exp_max"] = expf
4059
+ table[f"{op}.frac_nz"] = fracf
4060
+ table[f"{op}.frac_zero"] = fracf
4061
+ table[f"{op}.is_nan"] = [R(f"{op}.exp_max"), R(f"{op}.frac_nz")]
4062
+ table[f"{op}.is_inf"] = [R(f"{op}.exp_max"), R(f"{op}.frac_zero")]
4063
+ table[f"{op}.is_zero"] = [R(f"{op}.exp_zero"), R(f"{op}.frac_zero")]
4064
+ table[f"{op}.nonzero"] = [R(f"{op}.is_zero")]
4065
+ table[f"{op}.eexp_lsb"] = [w(E), R(f"{op}.exp_zero")]
4066
+ if suffix in table:
4067
+ return table[suffix]
4068
+
4069
+ # --- pre-normalizers (a, b, c) ---
4070
+ m = re.match(r"^([abc])\.pn\.any_higher(\d+)$", suffix)
4071
+ if m:
4072
+ op, pos = m.group(1), int(m.group(2))
4073
+ return [m_raw(op, ML - 1 - t) for t in range(pos)]
4074
+ m = re.match(r"^([abc])\.pn\.is_highest(\d+)\.(not_higher|and)$", suffix)
4075
+ if m:
4076
+ op, pos = m.group(1), int(m.group(2))
4077
+ if m.group(3) == "not_higher":
4078
+ return [R(f"{op}.pn.any_higher{pos}")]
4079
+ return [m_raw(op, ML - 1 - pos), R(f"{op}.pn.is_highest{pos}.not_higher")]
4080
+ m = re.match(r"^([abc])\.pn\.lz\.bit(\d+)$", suffix)
4081
+ if m:
4082
+ op, bbit = m.group(1), int(m.group(2))
4083
+ srcs = [R(f"{op}.pn.is_highest{pos}.and") for pos in range(1, ML) if (pos >> bbit) & 1]
4084
+ return srcs if srcs else [zero]
4085
+ m = re.match(r"^([abc])\.pn\.s(\d+)\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix)
4086
+ if m:
4087
+ op, bbit, k, kind = m.group(1), int(m.group(2)), int(m.group(3)), m.group(4)
4088
+ sel = R(f"{op}.pn.lz.bit{bbit}")
4089
+
4090
+ def lin(kk: int) -> int:
4091
+ if kk < 0 or kk >= ML:
4092
+ return zero
4093
+ return m_raw(op, kk) if bbit == 0 else R(f"{op}.pn.s{bbit - 1}.bit{kk}.or")
4094
+
4095
+ if kind == "not_sel":
4096
+ return [sel]
4097
+ if kind == "and_a":
4098
+ return [lin(k), R(f"{op}.pn.s{bbit}.bit{k}.not_sel")]
4099
+ if kind == "and_b":
4100
+ return [lin(k - (1 << bbit)), sel]
4101
+ return [R(f"{op}.pn.s{bbit}.bit{k}.and_a"), R(f"{op}.pn.s{bbit}.bit{k}.and_b")]
4102
+ m = re.match(r"^([abc])\.nlz\.bit(\d+)$", suffix)
4103
+ if m:
4104
+ return [R(f"{m.group(1)}.pn.lz.bit{int(m.group(2))}")]
4105
+ m = re.match(r"^([abc])\.neff\.fa(\d+)\.(.+)$", suffix)
4106
+ if m:
4107
+ op, k = m.group(1), int(m.group(2))
4108
+ b_sig = R(f"{op}.nlz.bit{k}") if k < mlzb else one
4109
+ cin = one if k == 0 else R(f"{op}.neff.fa{k - 1}.carry_or")
4110
+ return _fa_member_inputs(m.group(3), f"{prefix}.{op}.neff.fa{k}", eexp(op, k), b_sig, cin, reg)
4111
+
4112
+ # --- multiplier ---
4113
+ m = re.match(r"^pp\.a(\d+)b(\d+)$", suffix)
4114
+ if m:
4115
+ return [Mn("a", int(m.group(1))), Mn("b", int(m.group(2)))]
4116
+ m = re.match(r"^acc\.s(\d+)\.fa(\d+)\.(.+)$", suffix)
4117
+ if m:
4118
+ t, k = int(m.group(1)), int(m.group(2))
4119
+ row = t + 1
4120
+ if t == 0:
4121
+ a_sig = R(f"pp.a{k}b0") if k <= F else zero
4122
+ else:
4123
+ a_sig = R(f"acc.s{t - 1}.fa{k}.ha2.sum.layer2")
4124
+ b_sig = R(f"pp.a{k - row}b{row}") if row <= k <= row + F else zero
4125
+ cin = zero if k == 0 else R(f"acc.s{t}.fa{k - 1}.carry_or")
4126
+ return _fa_member_inputs(m.group(3), f"{prefix}.acc.s{t}.fa{k}", a_sig, b_sig, cin, reg)
4127
+
4128
+ # --- exponent alignment ---
4129
+ m = re.match(r"^nab\.fa(\d+)\.(.+)$", suffix)
4130
+ if m:
4131
+ k = int(m.group(1))
4132
+ cin = zero if k == 0 else R(f"nab.fa{k - 1}.carry_or")
4133
+ return _fa_member_inputs(m.group(2), f"{prefix}.nab.fa{k}", na("a", k), na("b", k), cin, reg)
4134
+ m = re.match(r"^q1\.fa(\d+)\.(.+)$", suffix)
4135
+ if m:
4136
+ k = int(m.group(1))
4137
+ a_sig = R(f"nab.fa{k}.ha2.sum.layer2")
4138
+ b_sig = one if (q1c >> k) & 1 else zero
4139
+ cin = zero if k == 0 else R(f"q1.fa{k - 1}.carry_or")
4140
+ return _fa_member_inputs(m.group(2), f"{prefix}.q1.fa{k}", a_sig, b_sig, cin, reg)
4141
+ if suffix == "prod_zero":
4142
+ return [R("a.is_zero"), R("b.is_zero")]
4143
+ if suffix == "not_prod_zero":
4144
+ return [R("prod_zero")]
4145
+ if suffix == "not_c_zero":
4146
+ return [R("c.is_zero")]
4147
+ m = re.match(r"^q1e\.bit(\d+)$", suffix)
4148
+ if m:
4149
+ k = int(m.group(1))
4150
+ q1b = R(f"q1.fa{k}.ha2.sum.layer2")
4151
+ return [R("prod_zero"), q1b] if k in (E + 1, E + 2) else [R("not_prod_zero"), q1b]
4152
+ m = re.match(r"^q2e\.bit(\d+)$", suffix)
4153
+ if m:
4154
+ k = int(m.group(1))
4155
+ return [R("c.is_zero"), na("c", k)] if k in (E + 1, E + 2) else [R("not_c_zero"), na("c", k)]
4156
+ m = re.match(r"^ncnot\.bit(\d+)$", suffix)
4157
+ if m:
4158
+ return [R(f"q2e.bit{int(m.group(1))}")]
4159
+ m = re.match(r"^d\.fa(\d+)\.(.+)$", suffix)
4160
+ if m:
4161
+ k = int(m.group(1))
4162
+ a_sig = R(f"q1e.bit{k}")
4163
+ cin = one if k == 0 else R(f"d.fa{k - 1}.carry_or")
4164
+ return _fa_member_inputs(m.group(2), f"{prefix}.d.fa{k}", a_sig, R(f"ncnot.bit{k}"), cin, reg)
4165
+
4166
+ neg_d = R(f"d.fa{E + 2}.ha2.sum.layer2")
4167
+ if suffix == "not_neg_d":
4168
+ return [neg_d]
4169
+ m = re.match(r"^dxor\.bit(\d+)\.(a|b)$", suffix)
4170
+ if m:
4171
+ return [R(f"d.fa{int(m.group(1))}.ha2.sum.layer2"), neg_d]
4172
+ m = re.match(r"^dxor\.bit(\d+)$", suffix)
4173
+ if m:
4174
+ k = int(m.group(1))
4175
+ return [R(f"dxor.bit{k}.a"), R(f"dxor.bit{k}.b")]
4176
+ m = re.match(r"^dabs\.fa(\d+)\.(.+)$", suffix)
4177
+ if m:
4178
+ k = int(m.group(1))
4179
+ cin = neg_d if k == 0 else R(f"dabs.fa{k - 1}.carry_or")
4180
+ return _fa_member_inputs(m.group(2), f"{prefix}.dabs.fa{k}", R(f"dxor.bit{k}"), zero, cin, reg)
4181
+ m = re.match(r"^sbig\.fa(\d+)\.(.+)$", suffix)
4182
+ if m:
4183
+ k = int(m.group(1))
4184
+ a_sig = R(f"dabs.fa{k}.ha2.sum.layer2")
4185
+ cin = one if k == 0 else R(f"sbig.fa{k - 1}.carry_or")
4186
+ return _fa_member_inputs(m.group(2), f"{prefix}.sbig.fa{k}", a_sig, zero, cin, reg)
4187
+ if suffix == "sbig.high":
4188
+ return [R(f"sbig.fa{k}.ha2.sum.layer2") for k in range(SB, E + 3)]
4189
+ if suffix == "rp.deep":
4190
+ return [neg_d, R("sbig.high")]
4191
+ if suffix == "rc.deep":
4192
+ return [R("not_neg_d"), R("sbig.high")]
4193
+ m = re.match(r"^r([pc])\.bit(\d+)\.(and_big|and_one|or)$", suffix)
4194
+ if m:
4195
+ which, k, kind = m.group(1), int(m.group(2)), m.group(3)
4196
+ big = R(f"sbig.fa{k}.ha2.sum.layer2")
4197
+ one_bit = one if k == 0 else zero
4198
+ sel_big = neg_d if which == "p" else R("not_neg_d") # rp big when neg_d; rc big when ~neg_d
4199
+ sel_one = R("not_neg_d") if which == "p" else neg_d
4200
+ if kind == "and_big":
4201
+ return [big, sel_big]
4202
+ if kind == "and_one":
4203
+ return [one_bit, sel_one]
4204
+ return [R(f"r{which}.bit{k}.and_big"), R(f"r{which}.bit{k}.and_one")]
4205
+ m = re.match(r"^q1p\.fa(\d+)\.(.+)$", suffix)
4206
+ if m:
4207
+ k = int(m.group(1))
4208
+ b_sig = one if k == 1 else zero # + 2
4209
+ cin = zero if k == 0 else R(f"q1p.fa{k - 1}.carry_or")
4210
+ return _fa_member_inputs(m.group(2), f"{prefix}.q1p.fa{k}", R(f"q1e.bit{k}"), b_sig, cin, reg)
4211
+ m = re.match(r"^q2p\.fa(\d+)\.(.+)$", suffix)
4212
+ if m:
4213
+ k = int(m.group(1))
4214
+ b_sig = one if k == 1 else zero # + 2
4215
+ cin = zero if k == 0 else R(f"q2p.fa{k - 1}.carry_or")
4216
+ return _fa_member_inputs(m.group(2), f"{prefix}.q2p.fa{k}", R(f"q2e.bit{k}"), b_sig, cin, reg)
4217
+ m = re.match(r"^atop\.bit(\d+)\.(and_p|and_c|or)$", suffix)
4218
+ if m:
4219
+ k, kind = int(m.group(1)), m.group(2)
4220
+ if kind == "and_p":
4221
+ return [R(f"q1p.fa{k}.ha2.sum.layer2"), R("not_neg_d")]
4222
+ if kind == "and_c":
4223
+ return [R(f"q2p.fa{k}.ha2.sum.layer2"), neg_d]
4224
+ return [R(f"atop.bit{k}.and_p"), R(f"atop.bit{k}.and_c")]
4225
+
4226
+ # --- align shifters (pf: product, cf: addend) ---
4227
+ m = re.match(r"^(pf|cf)\.s(\d+)\.(.+)$", suffix)
4228
+ if m:
4229
+ tag, j, rest = m.group(1), int(m.group(2)), m.group(3)
4230
+ rsel = R(f"r{'p' if tag == 'pf' else 'c'}.bit{j}.or")
4231
+ topfn = Pt if tag == "pf" else Ct
4232
+
4233
+ def sin(k: int) -> int:
4234
+ if k < 0 or k >= WF:
4235
+ return zero
4236
+ return topfn(k) if j == 0 else R(f"{tag}.s{j - 1}.bit{k}.or")
4237
+
4238
+ if rest == "drop":
4239
+ return [sin(k) for k in range(1 << j)]
4240
+ if rest == "st_and":
4241
+ return [rsel, R(f"{tag}.s{j}.drop")]
4242
+ if rest == "sticky":
4243
+ prev = zero if j == 0 else R(f"{tag}.s{j - 1}.sticky")
4244
+ return [prev, R(f"{tag}.s{j}.st_and")]
4245
+ mm = re.match(r"^bit(\d+)\.(not_sel|and_a|and_b|or)$", rest)
4246
+ if mm:
4247
+ k, kind = int(mm.group(1)), mm.group(2)
4248
+ if kind == "not_sel":
4249
+ return [rsel]
4250
+ if kind == "and_a":
4251
+ return [sin(k), R(f"{tag}.s{j}.bit{k}.not_sel")]
4252
+ if kind == "and_b":
4253
+ return [sin(k + (1 << j)), rsel]
4254
+ return [R(f"{tag}.s{j}.bit{k}.and_a"), R(f"{tag}.s{j}.bit{k}.and_b")]
4255
+ m = re.match(r"^(pf|cf)\.not_deep$", suffix)
4256
+ if m:
4257
+ tag = m.group(1)
4258
+ return [R(f"r{'p' if tag == 'pf' else 'c'}.deep")]
4259
+ m = re.match(r"^(pf|cf)\.field\.bit(\d+)$", suffix)
4260
+ if m:
4261
+ tag, k = m.group(1), int(m.group(2))
4262
+ return [R(f"{tag}.s{SB - 1}.bit{k}.or"), R(f"{tag}.not_deep")]
4263
+ m = re.match(r"^(pf|cf)\.nz$", suffix)
4264
+ if m:
4265
+ tag = m.group(1)
4266
+ topfn = Pt if tag == "pf" else Ct
4267
+ return [topfn(k) for k in range(WF)]
4268
+ m = re.match(r"^(pf|cf)\.deep_nz$", suffix)
4269
+ if m:
4270
+ tag = m.group(1)
4271
+ return [R(f"r{'p' if tag == 'pf' else 'c'}.deep"), R(f"{tag}.nz")]
4272
+ m = re.match(r"^(pf|cf)\.sticky$", suffix)
4273
+ if m:
4274
+ tag = m.group(1)
4275
+ return [R(f"{tag}.s{SB - 1}.sticky"), R(f"{tag}.deep_nz")]
4276
+
4277
+ # --- signed magnitude combine ---
4278
+ def PF(k: int) -> int:
4279
+ return R(f"pf.field.bit{k}") if k < WF else zero
4280
+
4281
+ def CF(k: int) -> int:
4282
+ return R(f"cf.field.bit{k}") if k < WF else zero
4283
+
4284
+ def Dbit(k: int) -> int:
4285
+ return R(f"dsum.fa{k}.ha2.sum.layer2")
4286
+
4287
+ sign_D = Dbit(WF)
4288
+ combine = {
4289
+ "eff_sub.layer1.or": [sp, sc],
4290
+ "eff_sub.layer1.nand": [sp, sc],
4291
+ "eff_sub": [R("eff_sub.layer1.or"), R("eff_sub.layer1.nand")],
4292
+ "eff_add": [R("eff_sub")],
4293
+ "s_tail": [R("pf.sticky"), R("cf.sticky")],
4294
+ "not_s_tail": [R("s_tail")],
4295
+ "borrow_in": [R("eff_sub"), R("not_s_tail")],
4296
+ "ge": [sign_D],
4297
+ "not_ge": [R("ge")],
4298
+ "mag.nz": [R(f"mag.bit{k}.or") for k in range(MG)],
4299
+ "er.zero": [R(f"er.fa{k}.ha2.sum.layer2") for k in range(E + 3)],
4300
+ "er.underflow": [R(f"er.fa{E + 2}.ha2.sum.layer2"), R("er.zero")],
4301
+ "er.not_underflow": [R("er.underflow")],
4302
+ }
4303
+ if suffix in combine:
4304
+ return combine[suffix]
4305
+
4306
+ m = re.match(r"^cfnot\.bit(\d+)$", suffix)
4307
+ if m:
4308
+ return [CF(int(m.group(1)))]
4309
+ m = re.match(r"^dsum\.fa(\d+)\.(.+)$", suffix)
4310
+ if m:
4311
+ k = int(m.group(1))
4312
+ a_sig = PF(k)
4313
+ b_sig = R(f"cfnot.bit{k}") if k < WF else one
4314
+ cin = R("borrow_in") if k == 0 else R(f"dsum.fa{k - 1}.carry_or")
4315
+ return _fa_member_inputs(m.group(2), f"{prefix}.dsum.fa{k}", a_sig, b_sig, cin, reg)
4316
+ m = re.match(r"^dmagx\.bit(\d+)\.(a|b)$", suffix)
4317
+ if m:
4318
+ return [Dbit(int(m.group(1))), sign_D]
4319
+ m = re.match(r"^dmagx\.bit(\d+)$", suffix)
4320
+ if m:
4321
+ k = int(m.group(1))
4322
+ return [R(f"dmagx.bit{k}.a"), R(f"dmagx.bit{k}.b")]
4323
+ m = re.match(r"^dmag\.fa(\d+)\.(.+)$", suffix)
4324
+ if m:
4325
+ k = int(m.group(1))
4326
+ cin = sign_D if k == 0 else R(f"dmag.fa{k - 1}.carry_or")
4327
+ return _fa_member_inputs(m.group(2), f"{prefix}.dmag.fa{k}", R(f"dmagx.bit{k}"), zero, cin, reg)
4328
+ m = re.match(r"^asum\.fa(\d+)\.(.+)$", suffix)
4329
+ if m:
4330
+ k = int(m.group(1))
4331
+ cin = zero if k == 0 else R(f"asum.fa{k - 1}.carry_or")
4332
+ return _fa_member_inputs(m.group(2), f"{prefix}.asum.fa{k}", PF(k), CF(k), cin, reg)
4333
+ m = re.match(r"^mag\.bit(\d+)\.(and_sub|and_add|or)$", suffix)
4334
+ if m:
4335
+ k, kind = int(m.group(1)), m.group(2)
4336
+ if kind == "and_sub":
4337
+ return [R(f"dmag.fa{k}.ha2.sum.layer2"), R("eff_sub")]
4338
+ if kind == "and_add":
4339
+ return [R(f"asum.fa{k}.ha2.sum.layer2"), R("eff_add")]
4340
+ return [R(f"mag.bit{k}.and_sub"), R(f"mag.bit{k}.and_add")]
4341
+
4342
+ # --- leading-zero count + normalize (MG bits, MSB at MG-1) ---
4343
+ def magbit(k: int) -> int:
4344
+ return R(f"mag.bit{k}.or")
4345
+
4346
+ m = re.match(r"^lz\.any_higher(\d+)$", suffix)
4347
+ if m:
4348
+ pos = int(m.group(1))
4349
+ return [magbit(MG - 1 - t) for t in range(pos)]
4350
+ m = re.match(r"^lz\.is_highest(\d+)\.(not_higher|and)$", suffix)
4351
+ if m:
4352
+ pos = int(m.group(1))
4353
+ if m.group(2) == "not_higher":
4354
+ return [R(f"lz.any_higher{pos}")]
4355
+ return [magbit(MG - 1 - pos), R(f"lz.is_highest{pos}.not_higher")]
4356
+ m = re.match(r"^lz\.bit(\d+)$", suffix)
4357
+ if m:
4358
+ bbit = int(m.group(1))
4359
+ srcs = [R(f"lz.is_highest{pos}.and") for pos in range(1, MG) if (pos >> bbit) & 1]
4360
+ return srcs if srcs else [zero]
4361
+ m = re.match(r"^nrm\.s(\d+)\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix)
4362
+ if m:
4363
+ j, k, kind = int(m.group(1)), int(m.group(2)), m.group(3)
4364
+ sel = R(f"lz.bit{j}")
4365
+
4366
+ def nin(kk: int) -> int:
4367
+ if kk < 0 or kk >= MG:
4368
+ return zero
4369
+ return magbit(kk) if j == 0 else R(f"nrm.s{j - 1}.bit{kk}.or")
4370
+
4371
+ if kind == "not_sel":
4372
+ return [sel]
4373
+ if kind == "and_a":
4374
+ return [nin(k), R(f"nrm.s{j}.bit{k}.not_sel")]
4375
+ if kind == "and_b":
4376
+ return [nin(k - (1 << j)), sel]
4377
+ return [R(f"nrm.s{j}.bit{k}.and_a"), R(f"nrm.s{j}.bit{k}.and_b")]
4378
+
4379
+ # --- er = atop - lz ---
4380
+ def atopbit(k: int) -> int:
4381
+ return R(f"atop.bit{k}.or")
4382
+
4383
+ m = re.match(r"^nlzf\.bit(\d+)$", suffix)
4384
+ if m:
4385
+ k = int(m.group(1))
4386
+ return [R(f"lz.bit{k}")] if k < NB else [zero]
4387
+ m = re.match(r"^er\.fa(\d+)\.(.+)$", suffix)
4388
+ if m:
4389
+ k = int(m.group(1))
4390
+ cin = one if k == 0 else R(f"er.fa{k - 1}.carry_or")
4391
+ return _fa_member_inputs(m.group(2), f"{prefix}.er.fa{k}", atopbit(k), R(f"nlzf.bit{k}"), cin, reg)
4392
+ m = re.match(r"^exprc\.bit(\d+)$", suffix)
4393
+ if m:
4394
+ k = int(m.group(1))
4395
+ return [R(f"er.fa{k}.ha2.sum.layer2"), R("er.not_underflow")]
4396
+
4397
+ # --- subnormal-aware round on the normalized significand ---
4398
+ def nrmbit(k: int) -> int:
4399
+ return R(f"nrm.s{NB - 1}.bit{k}.or")
4400
+
4401
+ def MF(k: int) -> int: # mantissa frame
4402
+ if k == 0:
4403
+ return nrmbit(MG - 2 - F) # guard
4404
+ if k <= F:
4405
+ return nrmbit(MG - 2 - F + k) # frac[k-1]
4406
+ return nrmbit(MG - 1) # implicit
4407
+
4408
+ def shifted(k: int) -> int:
4409
+ return R(f"dsh.s{RBd - 1}.bit{k}.or")
4410
+
4411
+ rnd_round = {
4412
+ "round.sticky_lo": [nrmbit(k) for k in range(MG - F - 2)],
4413
+ "round.sticky_pre": [R("round.sticky_lo"), R("s_tail")],
4414
+ "too_deep": [R(f"rsh.bit{k}") for k in range(RBd, E + 3)],
4415
+ "not_deep": [R("too_deep")],
4416
+ "round.guard": [shifted(0), R("not_deep")],
4417
+ "round.sticky": [R("round.sticky_pre"), R(f"dsh.s{RBd - 1}.sticky"), R("too_deep")],
4418
+ "round.rsl": [R("round.sticky"), R("sig.bit0")],
4419
+ "round.up": [R("round.guard"), R("round.rsl")],
4420
+ }
4421
+ if suffix in rnd_round:
4422
+ return rnd_round[suffix]
4423
+
4424
+ m = re.match(r"^ernot\.bit(\d+)$", suffix)
4425
+ if m:
4426
+ return [R(f"er.fa{int(m.group(1))}.ha2.sum.layer2")]
4427
+ m = re.match(r"^subv\.fa(\d+)\.(.+)$", suffix)
4428
+ if m:
4429
+ k = int(m.group(1))
4430
+ a_sig = one if k == 0 else zero
4431
+ cin = one if k == 0 else R(f"subv.fa{k - 1}.carry_or")
4432
+ return _fa_member_inputs(m.group(2), f"{prefix}.subv.fa{k}", a_sig, R(f"ernot.bit{k}"), cin, reg)
4433
+ m = re.match(r"^rsh\.bit(\d+)$", suffix)
4434
+ if m:
4435
+ k = int(m.group(1))
4436
+ return [R(f"subv.fa{k}.ha2.sum.layer2"), R("er.underflow")]
4437
+ m = re.match(r"^dsh\.s(\d+)\.(.+)$", suffix)
4438
+ if m:
4439
+ j, rest = int(m.group(1)), m.group(2)
4440
+ sel = R(f"rsh.bit{j}")
4441
+
4442
+ def din(k: int) -> int:
4443
+ if k < 0 or k > F + 1:
4444
+ return zero
4445
+ return MF(k) if j == 0 else R(f"dsh.s{j - 1}.bit{k}.or")
4446
+
4447
+ if rest == "drop":
4448
+ return [din(k) for k in range(1 << j)]
4449
+ if rest == "st_and":
4450
+ return [sel, R(f"dsh.s{j}.drop")]
4451
+ if rest == "sticky":
4452
+ prev = zero if j == 0 else R(f"dsh.s{j - 1}.sticky")
4453
+ return [prev, R(f"dsh.s{j}.st_and")]
4454
+ mm = re.match(r"^bit(\d+)\.(not_sel|and_a|and_b|or)$", rest)
4455
+ if mm:
4456
+ k, kind = int(mm.group(1)), mm.group(2)
4457
+ if kind == "not_sel":
4458
+ return [sel]
4459
+ if kind == "and_a":
4460
+ return [din(k), R(f"dsh.s{j}.bit{k}.not_sel")]
4461
+ if kind == "and_b":
4462
+ return [din(k + (1 << j)), sel]
4463
+ return [R(f"dsh.s{j}.bit{k}.and_a"), R(f"dsh.s{j}.bit{k}.and_b")]
4464
+ m = re.match(r"^sig\.bit(\d+)$", suffix)
4465
+ if m:
4466
+ return [shifted(int(m.group(1)) + 1), R("not_deep")]
4467
+ m = re.match(r"^rnd\.bit(\d+)\.(.+)$", suffix)
4468
+ if m:
4469
+ k, rest = int(m.group(1)), m.group(2)
4470
+ cin = R("round.up") if k == 0 else R(f"rnd.bit{k - 1}.carry")
4471
+ f_bit = R(f"sig.bit{k}")
4472
+ if rest in ("xor.layer1.or", "xor.layer1.nand", "carry"):
4473
+ return [f_bit, cin]
4474
+ if rest == "xor.layer2":
4475
+ return [R(f"rnd.bit{k}.xor.layer1.or"), R(f"rnd.bit{k}.xor.layer1.nand")]
4476
+ m = re.match(r"^exp_round\.bit(\d+)\.(.+)$", suffix)
4477
+ if m:
4478
+ k, rest = int(m.group(1)), m.group(2)
4479
+ cin = R(f"rnd.bit{F - 1}.carry") if k == 0 else R(f"exp_round.bit{k - 1}.carry")
4480
+ e_bit = R(f"exprc.bit{k}")
4481
+ if rest in ("xor.layer1.or", "xor.layer1.nand", "carry"):
4482
+ return [e_bit, cin]
4483
+ if rest == "xor.layer2":
4484
+ return [R(f"exp_round.bit{k}.xor.layer1.or"), R(f"exp_round.bit{k}.xor.layer1.nand")]
4485
+
4486
+ # --- specials, sign selection, output pack ---
4487
+ specials = {
4488
+ "p_inf": [R("a.is_inf"), R("b.is_inf")],
4489
+ "not_p_inf": [R("p_inf")],
4490
+ "inf0a": [R("a.is_inf"), R("b.is_zero")],
4491
+ "inf0b": [R("b.is_inf"), R("a.is_zero")],
4492
+ "inf0": [R("inf0a"), R("inf0b")],
4493
+ "pc_sdiff.or": [sp, sc],
4494
+ "pc_sdiff.nand": [sp, sc],
4495
+ "pc_sdiff": [R("pc_sdiff.or"), R("pc_sdiff.nand")],
4496
+ "infinf_nan": [R("p_inf"), R("c.is_inf"), R("pc_sdiff")],
4497
+ "in_nan": [R("a.is_nan"), R("b.is_nan"), R("c.is_nan")],
4498
+ "nan": [R("in_nan"), R("inf0"), R("infinf_nan")],
4499
+ "not_nan": [R("nan")],
4500
+ "any_inf": [R("p_inf"), R("c.is_inf")],
4501
+ "not_any_inf": [R("any_inf")],
4502
+ "er.and_low": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E)],
4503
+ "er.hi": [R(f"exp_round.bit{E}.xor.layer2"), R(f"exp_round.bit{E + 1}.xor.layer2"),
4504
+ R(f"exp_round.bit{E + 2}.xor.layer2")],
4505
+ "er.ge_emax": [R("er.and_low"), R("er.hi")],
4506
+ "overflow": [R("er.ge_emax"), R("er.not_underflow")],
4507
+ "inf_or_ovf": [R("any_inf"), R("overflow")],
4508
+ "not_inf_path": [R("inf_or_ovf")],
4509
+ "inf": [R("not_nan"), R("inf_or_ovf")],
4510
+ "res_zero": [R(f"mag.bit{k}.or") for k in range(MG)],
4511
+ "not_res_zero": [R("res_zero")],
4512
+ "zero": [R("not_nan"), R("not_inf_path"), R("res_zero")],
4513
+ "norm": [R("not_nan"), R("not_inf_path"), R("not_res_zero")],
4514
+ "fin_sign.sub.and_ge": [R("ge"), sp],
4515
+ "fin_sign.sub.and_lt": [R("not_ge"), sc],
4516
+ "fin_sign.sub": [R("fin_sign.sub.and_ge"), R("fin_sign.sub.and_lt")],
4517
+ "fin_sign.and_sub": [R("fin_sign.sub"), R("eff_sub")],
4518
+ "fin_sign.and_add": [sp, R("eff_add")],
4519
+ "fin_sign": [R("fin_sign.and_sub"), R("fin_sign.and_add")],
4520
+ "p_zero": [R("a.is_zero"), R("b.is_zero")],
4521
+ "bothzero": [R("p_zero"), R("c.is_zero")],
4522
+ "negzero": [R("bothzero"), sp, sc],
4523
+ "sign_z.fin": [R("fin_sign"), R("not_res_zero")],
4524
+ "sign_z": [R("sign_z.fin"), R("negzero")],
4525
+ "sign.term_p": [R("p_inf"), sp],
4526
+ "sign.term_c": [R("not_p_inf"), R("c.is_inf"), sc],
4527
+ "sign.term_z": [R("not_any_inf"), R("sign_z")],
4528
+ "sign_pre": [R("sign.term_p"), R("sign.term_c"), R("sign.term_z")],
4529
+ "sign_out": [R("sign_pre"), R("not_nan")],
4530
+ }
4531
+ if suffix in specials:
4532
+ return specials[suffix]
4533
+ m = re.match(r"^exp_out\.bit(\d+)(\.norm_and)?$", suffix)
4534
+ if m:
4535
+ k = int(m.group(1))
4536
+ if m.group(2):
4537
+ return [R("norm"), R(f"exp_round.bit{k}.xor.layer2")]
4538
+ return [R("nan"), R("inf"), R(f"exp_out.bit{k}.norm_and")]
4539
+ m = re.match(r"^frac_out\.bit(\d+)(\.norm_and)?$", suffix)
4540
+ if m:
4541
+ k = int(m.group(1))
4542
+ if m.group(2):
4543
+ return [R("norm"), R(f"rnd.bit{k}.xor.layer2")]
4544
+ nan_sig = R("nan") if k == F - 1 else zero
4545
+ return [nan_sig, R(f"frac_out.bit{k}.norm_and")]
4546
+
4547
+ return None
4548
+
4549
+
4550
  def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
4551
  exp_bits: int, frac_bits: int) -> Optional[List[int]]:
4552
  """Complete wiring for the self-contained float addition pipeline."""
 
5363
  if rv is not None:
5364
  return rv
5365
  # Self-contained float networks (complete wiring, raw-word inputs).
5366
+ m = re.match(r"^(float16|float32)\.(cmp|mul|div|add|fma)(\.|$)", gate)
5367
  if m:
5368
  family = m.group(1)
5369
  e_bits, f_bits = (5, 10) if family == "float16" else (8, 23)
5370
  infer_fn = {"cmp": infer_float_cmp_inputs,
5371
  "mul": infer_float_mul_inputs,
5372
  "div": infer_float_div_inputs,
5373
+ "add": infer_float_add_inputs,
5374
+ "fma": infer_float_fma_inputs}[m.group(2)]
5375
  fc = infer_fn(gate, reg, family, e_bits, f_bits)
5376
  if fc is not None:
5377
  return fc
 
5916
  except ValueError as e:
5917
  print(f" float16 DIV already exists: {e}")
5918
 
5919
+ print("\nGenerating float16 FMA circuit...")
5920
+ try:
5921
+ add_float_fma(tensors, "float16", 5, 10)
5922
+ print(" Added float16 fused multiply-add (single rounding)")
5923
+ except ValueError as e:
5924
+ print(f" float16 FMA already exists: {e}")
5925
+
5926
  print("\nGenerating float16 CMP circuits...")
5927
  try:
5928
  add_float16_cmp(tensors)
 
5958
  except ValueError as e:
5959
  print(f" float32 DIV already exists: {e}")
5960
 
5961
+ print("\nGenerating float32 FMA circuit...")
5962
+ try:
5963
+ add_float_fma(tensors, "float32", 8, 23)
5964
+ print(" Added float32 fused multiply-add (single rounding)")
5965
+ except ValueError as e:
5966
+ print(f" float32 FMA already exists: {e}")
5967
+
5968
  print("\nGenerating float32 CMP circuits...")
5969
  try:
5970
  add_float32_cmp(tensors)
 
6080
  add_float32_add(tensors)
6081
  add_float32_mul(tensors)
6082
  add_float32_div(tensors)
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)
src/eval.py CHANGED
@@ -816,6 +816,99 @@ def float_div_oracle(aw: int, bw: int, exp_bits: int, frac_bits: int) -> int:
816
  return (s << (E + F)) | sig
817
 
818
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
  def float_add_oracle(aw: int, bw: int, exp_bits: int, frac_bits: int) -> int:
820
  """Expected sum word under the documented contract: exact IEEE specials
821
  (NaN, infinities, opposite-sign infinities -> NaN, signed zeros, exact
@@ -4192,6 +4285,71 @@ class BatchedFitnessEvaluator:
4192
 
4193
  return scores, total
4194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4195
  # =========================================================================
4196
  # FLOAT16 STRUCTURE CHECKS
4197
  # =========================================================================
@@ -5105,6 +5263,12 @@ class BatchedFitnessEvaluator:
5105
  total_tests += t
5106
  self.category_scores['float16_add_composed'] = (s[0].item() if pop_size == 1 else s.mean().item(), t)
5107
 
 
 
 
 
 
 
5108
  s, t = self._test_float16_core(population, debug)
5109
  scores += s
5110
  total_tests += t
@@ -5174,6 +5338,12 @@ class BatchedFitnessEvaluator:
5174
  total_tests += t
5175
  self.category_scores['float32_add_composed'] = (s[0].item() if pop_size == 1 else s.mean().item(), t)
5176
 
 
 
 
 
 
 
5177
  s, t = self._test_float32_core(population, debug)
5178
  scores += s
5179
  total_tests += t
 
816
  return (s << (E + F)) | sig
817
 
818
 
819
+ def float_fma_oracle(aw: int, bw: int, cw: int, exp_bits: int, frac_bits: int) -> int:
820
+ """Expected word for round(a*b + c) with a SINGLE rounding: exact IEEE
821
+ specials (NaN, infinities, inf*0 and inf-inf -> NaN, signed zeros, exact
822
+ cancellation -> +0), subnormal operands and gradual-underflow results,
823
+ round-to-nearest-even. Pure integer arithmetic, so exact."""
824
+ E, F = exp_bits, frac_bits
825
+ emax = (1 << E) - 1
826
+ fmask = (1 << F) - 1
827
+ bias = (1 << (E - 1)) - 1
828
+ qnan = (emax << F) | (1 << (F - 1))
829
+
830
+ def dec(w):
831
+ return (w >> (E + F)) & 1, (w >> F) & emax, w & fmask
832
+ sa, ea, fa = dec(aw)
833
+ sb, eb, fb = dec(bw)
834
+ sc, ec, fc = dec(cw)
835
+ sp = sa ^ sb
836
+ a_nan = ea == emax and fa != 0
837
+ b_nan = eb == emax and fb != 0
838
+ c_nan = ec == emax and fc != 0
839
+ a_inf = ea == emax and fa == 0
840
+ b_inf = eb == emax and fb == 0
841
+ c_inf = ec == emax and fc == 0
842
+ a_zero = ea == 0 and fa == 0
843
+ b_zero = eb == 0 and fb == 0
844
+ c_zero = ec == 0 and fc == 0
845
+ if a_nan or b_nan or c_nan:
846
+ return qnan
847
+ if (a_inf and b_zero) or (b_inf and a_zero):
848
+ return qnan
849
+ if a_inf or b_inf:
850
+ if c_inf and sc != sp:
851
+ return qnan
852
+ return (sp << (E + F)) | (emax << F)
853
+ if c_inf:
854
+ return (sc << (E + F)) | (emax << F)
855
+
856
+ Ma = ((1 << F) if ea else 0) | fa
857
+ Mb = ((1 << F) if eb else 0) | fb
858
+ Mc = ((1 << F) if ec else 0) | fc
859
+ eea = ea if ea else 1
860
+ eeb = eb if eb else 1
861
+ eec = ec if ec else 1
862
+ P = Ma * Mb
863
+ pe = eea + eeb - 2 * bias - 2 * F
864
+ ce = eec - bias - F
865
+ if P == 0 and c_zero:
866
+ return ((1 if (sp and sc) else 0) << (E + F))
867
+ g = min(pe, ce)
868
+ Pi = P << (pe - g)
869
+ Ci = Mc << (ce - g)
870
+ S = (-Pi if sp else Pi) + (-Ci if sc else Ci)
871
+ if S == 0:
872
+ return 0
873
+ s_out = 1 if S < 0 else 0
874
+ mag = -S if S < 0 else S
875
+ t = mag.bit_length() - 1
876
+ er = t + g + bias
877
+ if er >= 1:
878
+ drop = t - F
879
+ if drop <= 0:
880
+ m = mag << (-drop)
881
+ guard = sticky = 0
882
+ else:
883
+ m = mag >> drop
884
+ guard = (mag >> (drop - 1)) & 1
885
+ sticky = 1 if (mag & ((1 << (drop - 1)) - 1)) else 0
886
+ frac = m & fmask
887
+ if guard and ((frac & 1) or sticky):
888
+ frac += 1
889
+ if frac > fmask:
890
+ frac = 0
891
+ er += 1
892
+ if er >= emax:
893
+ return (s_out << (E + F)) | (emax << F)
894
+ return (s_out << (E + F)) | (er << F) | frac
895
+ sh = (1 - bias - F) - g
896
+ if sh <= 0:
897
+ sig = mag << (-sh)
898
+ guard = sticky = 0
899
+ else:
900
+ sig = mag >> sh
901
+ guard = (mag >> (sh - 1)) & 1
902
+ sticky = 1 if (mag & ((1 << (sh - 1)) - 1)) else 0
903
+ if guard and ((sig & 1) or sticky):
904
+ sig += 1
905
+ if sig == 0:
906
+ return s_out << (E + F)
907
+ if sig >= (1 << F):
908
+ return (s_out << (E + F)) | (1 << F)
909
+ return (s_out << (E + F)) | sig
910
+
911
+
912
  def float_add_oracle(aw: int, bw: int, exp_bits: int, frac_bits: int) -> int:
913
  """Expected sum word under the documented contract: exact IEEE specials
914
  (NaN, infinities, opposite-sign infinities -> NaN, signed zeros, exact
 
4285
 
4286
  return scores, total
4287
 
4288
+ def _test_float_fma_composed(self, pop: Dict, family: str, exp_bits: int,
4289
+ frac_bits: int, debug: bool) -> Tuple[torch.Tensor, int]:
4290
+ """Composed fused-multiply-add test: rebuild the netlist from .inputs,
4291
+ evaluate round(a*b+c) end to end over edge and random triples, compare
4292
+ to the single-rounding oracle."""
4293
+ pop_size = next(iter(pop.values())).shape[0]
4294
+ scores = torch.zeros(pop_size, device=self.device)
4295
+ prefix = f"{family}.fma"
4296
+ try:
4297
+ ne = NetlistEvaluator(pop, self.signal_registry, prefix, pop_size=pop_size)
4298
+ except (KeyError, ValueError) as e:
4299
+ if debug:
4300
+ print(f" {prefix} composed: SKIP ({e})")
4301
+ return scores, 0
4302
+ if f"{prefix}.norm" not in ne.gates:
4303
+ if debug:
4304
+ print(f" {prefix} composed: SKIP (pre-composition wiring)")
4305
+ return scores, 0
4306
+
4307
+ E, F = exp_bits, frac_bits
4308
+ W = 1 + E + F
4309
+ directed, randoms = float_test_words(E, F)
4310
+ base = directed + randoms[:24]
4311
+ triples = [(base[i], base[(i * 7 + 3) % len(base)], base[(i * 13 + 5) % len(base)])
4312
+ for i in range(len(base))]
4313
+ triples += list(zip(randoms, randoms[1:], randoms[2:]))
4314
+ a_words = torch.tensor([t[0] for t in triples], dtype=torch.long)
4315
+ b_words = torch.tensor([t[1] for t in triples], dtype=torch.long)
4316
+ c_words = torch.tensor([t[2] for t in triples], dtype=torch.long)
4317
+ ext = {}
4318
+ for i in range(W):
4319
+ ext[f"$a[{i}]"] = ((a_words >> (W - 1 - i)) & 1).float()
4320
+ ext[f"$b[{i}]"] = ((b_words >> (W - 1 - i)) & 1).float()
4321
+ ext[f"$c[{i}]"] = ((c_words >> (W - 1 - i)) & 1).float()
4322
+ try:
4323
+ out = ne.run(ext)
4324
+ except KeyError as e:
4325
+ if debug:
4326
+ print(f" {prefix} composed: SKIP (unbound signal {e})")
4327
+ return scores, 0
4328
+
4329
+ got = out[f"{prefix}.sign_out"].double() * float(1 << (E + F))
4330
+ for k in range(E):
4331
+ got = got + out[f"{prefix}.exp_out.bit{k}"].double() * float(1 << (F + k))
4332
+ for k in range(F):
4333
+ got = got + out[f"{prefix}.frac_out.bit{k}"].double() * float(1 << k)
4334
+ expected = torch.tensor(
4335
+ [float(float_fma_oracle(t[0], t[1], t[2], E, F)) for t in triples],
4336
+ dtype=torch.float64).unsqueeze(1)
4337
+ correct = (got == expected).float().sum(0)
4338
+ scores += correct
4339
+ failures = []
4340
+ if pop_size == 1:
4341
+ for i in range(len(triples)):
4342
+ if got[i, 0].item() != expected[i, 0].item():
4343
+ failures.append((list(triples[i]), int(expected[i, 0].item()),
4344
+ int(got[i, 0].item())))
4345
+ self._record(f"{prefix}.composed", int(correct[0].item()), len(triples), failures[:10])
4346
+ if debug:
4347
+ r = self.results[-1]
4348
+ print(f" {r.name}: {r.passed}/{r.total} {'PASS' if r.success else 'FAIL'}")
4349
+ for inp, exp_v, got_v in (failures or [])[:4]:
4350
+ print(f" FAIL: a={inp[0]:#x} b={inp[1]:#x} c={inp[2]:#x} expected {exp_v:#x}, got {got_v:#x}")
4351
+ return scores, len(triples)
4352
+
4353
  # =========================================================================
4354
  # FLOAT16 STRUCTURE CHECKS
4355
  # =========================================================================
 
5263
  total_tests += t
5264
  self.category_scores['float16_add_composed'] = (s[0].item() if pop_size == 1 else s.mean().item(), t)
5265
 
5266
+ if 'float16.fma.norm.weight' in population:
5267
+ s, t = self._test_float_fma_composed(population, 'float16', 5, 10, debug)
5268
+ scores += s
5269
+ total_tests += t
5270
+ self.category_scores['float16_fma_composed'] = (s[0].item() if pop_size == 1 else s.mean().item(), t)
5271
+
5272
  s, t = self._test_float16_core(population, debug)
5273
  scores += s
5274
  total_tests += t
 
5338
  total_tests += t
5339
  self.category_scores['float32_add_composed'] = (s[0].item() if pop_size == 1 else s.mean().item(), t)
5340
 
5341
+ if 'float32.fma.norm.weight' in population:
5342
+ s, t = self._test_float_fma_composed(population, 'float32', 8, 23, debug)
5343
+ scores += s
5344
+ total_tests += t
5345
+ self.category_scores['float32_fma_composed'] = (s[0].item() if pop_size == 1 else s.mean().item(), t)
5346
+
5347
  s, t = self._test_float32_core(population, debug)
5348
  scores += s
5349
  total_tests += t
src/machines.py CHANGED
@@ -361,6 +361,9 @@ class Asm:
361
  if mn in ("fadd.s", "fsub.s", "fmul.s", "fdiv.s"):
362
  f7 = {"fadd.s": 0, "fsub.s": 4, "fmul.s": 8, "fdiv.s": 12}[mn]
363
  return (f7 << 25) | (a[2] << 20) | (a[1] << 15) | (a[0] << 7) | 0x53
 
 
 
364
  if mn in ("fle.s", "flt.s", "feq.s"):
365
  f3 = {"fle.s": 0, "flt.s": 1, "feq.s": 2}[mn]
366
  return (0x50 << 25) | (a[2] << 20) | (a[1] << 15) | (f3 << 12) | (a[0] << 7) | 0x53
@@ -529,6 +532,13 @@ class RefRV32:
529
  elif f7 == 0x68: FR[rd] = fcvt_s_w_ref(R[rs1]) # FCVT.S.W
530
  elif f7 == 0x78: FR[rd] = a
531
  elif f7 == 0x70: wr(FR[rs1])
 
 
 
 
 
 
 
532
  elif op == 0x0B: # NEUR (custom-0)
533
  x = a & 0xFF
534
  pos, neg = b & 0xFF, (b >> 8) & 0xFF
@@ -574,7 +584,7 @@ class Rv32ThresholdCPU:
574
  self.registry = load_metadata(path)["signal_registry"]
575
  self.alu = GenericThresholdALU(self.T, 32)
576
  self.fpu = {op: NetlistEvaluator(self.T, self.registry, f"float32.{op}")
577
- for op in ("add", "mul", "div", "cmp")}
578
  self.pairs_issued = 0
579
 
580
  # ---- gate helpers -------------------------------------------------
@@ -1080,6 +1090,14 @@ class Rv32ThresholdCPU:
1080
  FR[rd] = a
1081
  elif f7 == 0x70:
1082
  wr(FR[rs1])
 
 
 
 
 
 
 
 
1083
  elif op == 0x0B:
1084
  wr(self.neur(a, b))
1085
  elif op == 0x73:
@@ -1108,6 +1126,24 @@ class Rv32ThresholdCPU:
1108
  v |= int(out[f"{p}.frac_out.bit{k}"][0, 0].item()) << k
1109
  return v
1110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1111
  def run(self, state, max_cycles=400):
1112
  s = dict(state)
1113
  s["_mem_bits"] = torch.tensor(
@@ -1527,6 +1563,32 @@ def prog_float():
1527
  and s["regs"][12] == 1 and s["regs"][13] == f4)
1528
 
1529
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1530
  def prog_neur():
1531
  # XOR of two bits computed by a two-layer network of NEUR instructions:
1532
  # h1 = OR(a,b) = H(a + b - 1); h2 = NAND(a,b) = H(-a - b + 1);
@@ -1756,6 +1818,7 @@ def rv32_test() -> int:
1756
  ("mem_call", prog_mem_call),
1757
  ("muldiv", prog_muldiv),
1758
  ("float_subset", prog_float),
 
1759
  ("fcvt_roundtrip", prog_fcvt),
1760
  ("fcvt_rounding_modes", prog_fcvt_rm),
1761
  ("neur_xor_net", prog_neur),
 
361
  if mn in ("fadd.s", "fsub.s", "fmul.s", "fdiv.s"):
362
  f7 = {"fadd.s": 0, "fsub.s": 4, "fmul.s": 8, "fdiv.s": 12}[mn]
363
  return (f7 << 25) | (a[2] << 20) | (a[1] << 15) | (a[0] << 7) | 0x53
364
+ if mn in ("fmadd.s", "fmsub.s", "fnmsub.s", "fnmadd.s"): # R4-type: rd,rs1,rs2,rs3
365
+ op = {"fmadd.s": 0x43, "fmsub.s": 0x47, "fnmsub.s": 0x4b, "fnmadd.s": 0x4f}[mn]
366
+ return (a[3] << 27) | (a[2] << 20) | (a[1] << 15) | (a[0] << 7) | op
367
  if mn in ("fle.s", "flt.s", "feq.s"):
368
  f3 = {"fle.s": 0, "flt.s": 1, "feq.s": 2}[mn]
369
  return (0x50 << 25) | (a[2] << 20) | (a[1] << 15) | (f3 << 12) | (a[0] << 7) | 0x53
 
532
  elif f7 == 0x68: FR[rd] = fcvt_s_w_ref(R[rs1]) # FCVT.S.W
533
  elif f7 == 0x78: FR[rd] = a
534
  elif f7 == 0x70: wr(FR[rs1])
535
+ elif op in (0x43, 0x47, 0x4b, 0x4f): # FMADD/FMSUB/FNMSUB/FNMADD.S
536
+ from eval import float_fma_oracle
537
+ rs3 = (w >> 27) & 0x1F
538
+ fa, fb, fc = FR[rs1], FR[rs2], FR[rs3]
539
+ if op in (0x4b, 0x4f): fa ^= 0x80000000 # negate product
540
+ if op in (0x47, 0x4f): fc ^= 0x80000000 # negate addend
541
+ FR[rd] = float_fma_oracle(fa, fb, fc, 8, 23)
542
  elif op == 0x0B: # NEUR (custom-0)
543
  x = a & 0xFF
544
  pos, neg = b & 0xFF, (b >> 8) & 0xFF
 
584
  self.registry = load_metadata(path)["signal_registry"]
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 -------------------------------------------------
 
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
1097
+ fa ^= 0x80000000
1098
+ if op in (0x47, 0x4f): # negate the addend
1099
+ fc ^= 0x80000000
1100
+ FR[rd] = self._fpu_word3(fa, fb, fc)
1101
  elif op == 0x0B:
1102
  wr(self.neur(a, b))
1103
  elif op == 0x73:
 
1126
  v |= int(out[f"{p}.frac_out.bit{k}"][0, 0].item()) << k
1127
  return v
1128
 
1129
+ def _fpu_ext3(self, fa, fb, fc):
1130
+ ext = {}
1131
+ for i in range(32):
1132
+ ext[f"$a[{i}]"] = (fa >> (31 - i)) & 1
1133
+ ext[f"$b[{i}]"] = (fb >> (31 - i)) & 1
1134
+ ext[f"$c[{i}]"] = (fc >> (31 - i)) & 1
1135
+ return ext
1136
+
1137
+ def _fpu_word3(self, fa, fb, fc):
1138
+ out = self.fpu["fma"].run(self._fpu_ext3(fa, fb, fc))
1139
+ p = "float32.fma"
1140
+ v = int(out[f"{p}.sign_out"][0, 0].item()) << 31
1141
+ for k in range(8):
1142
+ v |= int(out[f"{p}.exp_out.bit{k}"][0, 0].item()) << (23 + k)
1143
+ for k in range(23):
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(
 
1563
  and s["regs"][12] == 1 and s["regs"][13] == f4)
1564
 
1565
 
1566
+ def prog_fma():
1567
+ a = Asm()
1568
+ a.lui(1, 0x1)
1569
+ a.flw(1, 1, 0) # f1 = 1.5
1570
+ a.flw(2, 1, 4) # f2 = 2.5
1571
+ a.flw(3, 1, 8) # f3 = 3.0
1572
+ a.fmadd_s(4, 1, 2, 3) # 1.5*2.5 + 3.0 = 6.75
1573
+ a.fmsub_s(5, 1, 2, 3) # 1.5*2.5 - 3.0 = 0.75
1574
+ a.fnmadd_s(6, 1, 2, 3) # -(1.5*2.5) - 3.0 = -6.75
1575
+ a.fnmsub_s(7, 1, 2, 3) # -(1.5*2.5) + 3.0 = -0.75
1576
+ a.fmv_x_w(10, 4)
1577
+ a.fmv_x_w(11, 5)
1578
+ a.fmv_x_w(12, 6)
1579
+ a.fmv_x_w(13, 7)
1580
+ a.ecall()
1581
+ mem = a.assemble()
1582
+ f3_0 = struct.unpack("<I", struct.pack("<f", 3.0))[0]
1583
+ for i, wv in ((0, F1_0), (4, F2_5), (8, f3_0)):
1584
+ for b in range(4):
1585
+ mem[0x1000 + i + b] = (wv >> (8 * b)) & 0xFF
1586
+ def word(x):
1587
+ return struct.unpack("<I", struct.pack("<f", x))[0]
1588
+ return mem, 30, lambda s: (s["regs"][10] == word(6.75) and s["regs"][11] == word(0.75)
1589
+ and s["regs"][12] == word(-6.75) and s["regs"][13] == word(-0.75))
1590
+
1591
+
1592
  def prog_neur():
1593
  # XOR of two bits computed by a two-layer network of NEUR instructions:
1594
  # h1 = OR(a,b) = H(a + b - 1); h2 = NAND(a,b) = H(-a - b + 1);
 
1818
  ("mem_call", prog_mem_call),
1819
  ("muldiv", prog_muldiv),
1820
  ("float_subset", prog_float),
1821
+ ("fma", prog_fma),
1822
  ("fcvt_roundtrip", prog_fcvt),
1823
  ("fcvt_rounding_modes", prog_fcvt_rm),
1824
  ("neur_xor_net", prog_neur),
todo.md CHANGED
@@ -2,6 +2,5 @@
2
 
3
  Unfinished work.
4
 
5
- - The F extension includes a fused multiply-add instruction that rounds once.
6
- - Program-counter sequencing and instruction decode are computed by threshold gates rather than by fixed wiring in the runtimes.
7
- - A machine-checked correctness proof relates the shipped weights to the ISA.
 
2
 
3
  Unfinished work.
4
 
5
+ 1. Program-counter sequencing and instruction decode are computed by threshold gates rather than by fixed wiring in the runtimes.
6
+ 2. A machine-checked correctness proof relates the shipped weights to the ISA.
 
variants/neural_alu16.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:5f446db437a0de918305a96edfaa5e43871ccf04d4ff01af6dc4a0b0f5d9b299
3
- size 27677671
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:96fca43edeec72d0e59327fd3f39f1c4a8da0690c819345a7002e581013d4737
3
+ size 39046996
variants/neural_alu32.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d35d7c3801b45c4a34af02097f8e10389cb6eadb88df2962de913db0b455e2f5
3
- size 26064500
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1dc11ac5b06736a2d16a2904d1ed64dcc719960033f49c5caa50d033f22a7710
3
+ size 37387833
variants/neural_alu8.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ffcddf4c5081404b7d3010e705c40b13e4058973ea97b57f734e36f522a656ed
3
- size 26064500
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ad56db6b70e3c49a5b6f475f990190a30505ac2658d61a5bb2e5d14ed03f943c
3
+ size 37387833
variants/neural_computer16.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:7b945aabc345ec0313643493b2667b5aac4b034ab4aa878ddb89da8d39c1e198
3
- size 36194449
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3879d0b35eba828e26eba8195984c36220467411955a0256b2254459b30c1714
3
+ size 47563034
variants/neural_computer16_reduced.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:58f883b503cb40a6e9cd3fb0629fcce99de438fa4b52720fcdba12a024dc2ce5
3
- size 28382973
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:40ac54ccac39c0c5beb9ed5d2f56a3caa70cd5dc6632505d7e42e2b9f5191370
3
+ size 39749446
variants/neural_computer16_registers.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:b9f7ad572ba0136d068bab7edfafe9e2aa383e1a6fe1e1e291fb972cff589c20
3
- size 27764633
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ace072b14bd6724d564e7411043aa009ca739dde4c78f17b9728d3fbe0f7c09a
3
+ size 39136706
variants/neural_computer16_scratchpad.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:aa7c9630864d48014acc971035034943d377f959839a889b871263d8af3636eb
3
- size 27848613
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc65c083aa1f822bc7493e2c695919db716589570f602e8d099b6213a37bcf05
3
+ size 39224462
variants/neural_computer16_small.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:71ede38e63607b4cda4e366db1bef813a48eb473f13aa0b4c563a72c1b244d94
3
- size 27967749
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:204dc61509b20cbfd5ff4f7eaa722df30b6d3cc90a60bfa6a210dc9b0e39edaf
3
+ size 39344766
variants/neural_computer32.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c358cad0b5e5fd8cf83720eeb42a399047409d374932a9dc6ec3ebda754933bf
3
- size 34601074
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9257c5519edac4fb5f76724389d6950590c83b8fa6e5a5e48bb2eda41e208c08
3
+ size 45921927
variants/neural_computer32_reduced.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:7e72369c1d4839e176a28b1cac8d89082698254af54c81af82bc6391f50b1c68
3
- size 26789662
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:612b7246735ac6c27d7b39b49a8c884230d5824219995e03712009fa546c6f1f
3
+ size 38108343
variants/neural_computer32_registers.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:065f85b710beb30882dc8dc76e0f5b00a50c0cbb7c98bbf786d236be56216678
3
- size 26170374
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf7e8aa2593a4dd87ba61bba28ef88d5f587188882d61a75bc143a2a8f54ce88
3
+ size 37497223
variants/neural_computer32_scratchpad.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ce74d1fb55c4e43902664788da52d612722fef4f434b5c7391a41a469c9106bc
3
- size 26254306
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b6bb2bea3bde7ac5912c81d4ca32acd82b42bf146aac7089b0d9816bda7ae3a3
3
+ size 37583499
variants/neural_computer32_small.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:74614887741b0aab81644a23aec52b4315cc4cd8661030467b834d9d554c862f
3
- size 26374348
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b188500ed25064a13079b09a2430c75863eb5283ea31cdea17933422e1b83228
3
+ size 37703667
variants/neural_computer8.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:93aa28025be1610259c6605c0c0b645b26e4d9e4a3ff7482183e7b71c1825585
3
- size 34572926
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b7aad3aa9c64dbebd8022299c3b64ed561cfb00321ce586a30c40df4ac84aa4
3
+ size 45892907
variants/neural_computer8_reduced.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:63c11df1459c3ae2f87437528f4af6a1e2b1a39cc4474fa6e800e83922beb8cf
3
- size 26761486
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1387f4646dc73723e3fed45810901242e5c61fb8eea52b10995e037b0fcad11c
3
+ size 38079415
variants/neural_computer8_registers.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:1b315ef217223e48cd1967b723ac417da290c27fb86f450496f1fb77eb8270b8
3
- size 26142278
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35e4667440cd56046f1709bda0ed4ddfa24e8df6f0df448fc3ddb0ec18c280e3
3
+ size 37468231
variants/neural_computer8_scratchpad.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c493262272216ee4207c76e37dc11a96320828918c352e60f8dedda4513fe9a2
3
- size 26226042
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:426fe58eeb1022aebcac43203ee8e5484ae4a3dba9ed820faacd1a23a608bf37
3
+ size 37554631
variants/neural_computer8_small.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:1ebf7e275a87aa80033af987cede32e85aa35b4bb926d50df334a5e972149812
3
- size 26345338
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:631606d3d79f09abf7fad511c68b55bfeeb908348da7309680ef4a593c07c9a3
3
+ size 37674799
variants/neural_rv32.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:3367ac4fd179dc05f6ed17822e327206e0ff2ab6d64c2d796448d79ca805b52b
3
- size 33274820
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d329a8aedbf8e993be78df458f26044ace482b4ee146e0dee86612c12d5a345a
3
+ size 43277092