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 +2 -2
- neural_computer.safetensors +2 -2
- src/build.py +893 -2
- src/eval.py +170 -0
- src/machines.py +64 -1
- todo.md +2 -3
- variants/neural_alu16.safetensors +2 -2
- variants/neural_alu32.safetensors +2 -2
- variants/neural_alu8.safetensors +2 -2
- variants/neural_computer16.safetensors +2 -2
- variants/neural_computer16_reduced.safetensors +2 -2
- variants/neural_computer16_registers.safetensors +2 -2
- variants/neural_computer16_scratchpad.safetensors +2 -2
- variants/neural_computer16_small.safetensors +2 -2
- variants/neural_computer32.safetensors +2 -2
- variants/neural_computer32_reduced.safetensors +2 -2
- variants/neural_computer32_registers.safetensors +2 -2
- variants/neural_computer32_scratchpad.safetensors +2 -2
- variants/neural_computer32_small.safetensors +2 -2
- variants/neural_computer8.safetensors +2 -2
- variants/neural_computer8_reduced.safetensors +2 -2
- variants/neural_computer8_registers.safetensors +2 -2
- variants/neural_computer8_scratchpad.safetensors +2 -2
- variants/neural_computer8_small.safetensors +2 -2
- variants/neural_rv32.safetensors +2 -2
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.
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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
|
|
|
|
| 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 |
-
-
|
| 6 |
-
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d329a8aedbf8e993be78df458f26044ace482b4ee146e0dee86612c12d5a345a
|
| 3 |
+
size 43277092
|