CharlesCNorton
8-bit computer decode and PC sequencing as threshold gates: a 4-to-16 opcode one-hot decoder and a next-PC network (PC+2/PC+4 increment chains plus a priority mux over PC+2, PC+4, the conditional-jump mux, and the direct target). The runtime dispatches on the gate one-hots and takes the next PC from the mux instead of Python slicing and PC+2. Full 10/10 CPU suite passes; all variants rebuilt. Completes decode/PC-as-gates across every runtime (rv32, subleq, 8-bit computer).
b9fb5ce | """ | |
| Build tools for 8-bit Threshold Computer safetensors. | |
| Subcommands: | |
| python build.py memory - Generate 64KB memory circuits | |
| python build.py inputs - Add .inputs metadata tensors | |
| python build.py all - Run both (memory first, then inputs) | |
| ROUTING SCHEMA (formerly routing.json) | |
| ====================================== | |
| Routing info is now embedded in safetensors via .inputs tensors and signal registry metadata. | |
| INPUT SOURCE TYPES | |
| ------------------ | |
| 1. External input: "$input_name" - Named input to the circuit | |
| - Example: "$a", "$b", "$cin" | |
| 2. Gate output: "path.to.gate" - Output of another gate | |
| - Example: "ha1.sum", "layer1.or" | |
| 3. Bit extraction: "$input[i]" - Single bit from multi-bit input | |
| - Example: "$a[0]" (LSB), "$a[7]" (MSB for 8-bit) | |
| 4. Constant: "#0" or "#1" - Fixed value | |
| - Example: "#1" for carry-in in two's complement | |
| CIRCUIT TYPES | |
| ------------- | |
| Single-Layer Gates: .weight and .bias only | |
| "boolean.and": ["$a", "$b"] | |
| Two-Layer Gates (XOR, XNOR): layer1 + layer2 | |
| "boolean.xor.layer1.or": ["$a", "$b"] | |
| "boolean.xor.layer1.nand": ["$a", "$b"] | |
| "boolean.xor.layer2": ["layer1.or", "layer1.nand"] | |
| Hierarchical Circuits: nested sub-components | |
| "arithmetic.fulladder": { | |
| "ha1.sum.layer1.or": ["$a", "$b"], | |
| "ha1.carry": ["$a", "$b"], | |
| "ha2.sum.layer1.or": ["ha1.sum", "$cin"], | |
| "carry_or": ["ha1.carry", "ha2.carry"] | |
| } | |
| Bit-Indexed Circuits: multi-bit operations | |
| "arithmetic.ripplecarry8bit.fa0": ["$a[0]", "$b[0]", "#0"] | |
| "arithmetic.ripplecarry8bit.fa1": ["$a[1]", "$b[1]", "fa0.cout"] | |
| PACKED MEMORY CIRCUITS | |
| ---------------------- | |
| 64KB memory uses packed tensors (shapes for 16-bit address, 8-bit data): | |
| memory.addr_decode.weight: [65536, 16] | |
| memory.addr_decode.bias: [65536] | |
| memory.read.and.weight: [8, 65536, 2] | |
| memory.read.and.bias: [8, 65536] | |
| memory.read.or.weight: [8, 65536] | |
| memory.read.or.bias: [8] | |
| memory.write.sel.weight: [65536, 2] | |
| memory.write.sel.bias: [65536] | |
| memory.write.nsel.weight: [65536, 1] | |
| memory.write.nsel.bias: [65536] | |
| memory.write.and_old.weight: [65536, 8, 2] | |
| memory.write.and_old.bias: [65536, 8] | |
| memory.write.and_new.weight: [65536, 8, 2] | |
| memory.write.and_new.bias: [65536, 8] | |
| memory.write.or.weight: [65536, 8, 2] | |
| memory.write.or.bias: [65536, 8] | |
| Semantics: | |
| decode: sel[i] = H(sum(addr_bits * weight[i]) + bias[i]) | |
| read: bit[b] = H(sum(H([mem_bit, sel] * and_w) + and_b) * or_w + or_b) | |
| write: new = H(H([old, nsel] * and_old) + H([data, sel] * and_new) - 1) | |
| SIGNAL REGISTRY | |
| --------------- | |
| Signal IDs are stored in safetensors metadata as JSON: | |
| {"0": "#0", "1": "#1", "2": "$a", "3": "$b", ...} | |
| Each gate's .inputs tensor contains integer IDs referencing this registry. | |
| NAMING CONVENTIONS | |
| ------------------ | |
| - External inputs: $name or $name[bit] | |
| - Constants: #0, #1 | |
| - Internal gates: relative path from circuit root | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import re | |
| from pathlib import Path | |
| from typing import Any, Dict, Iterable, List, Optional, Set | |
| import torch | |
| from safetensors import safe_open | |
| from safetensors.torch import save_file | |
| MODEL_DIR = Path(__file__).resolve().parent.parent # repo root; this module lives in src/ | |
| def get_model_path(bits: int = 8, memory_profile: str = None, addr_bits: int = None) -> Path: | |
| """Generate model filename based on configuration.""" | |
| if addr_bits is not None: | |
| if addr_bits == 0: | |
| has_memory = False | |
| mem_suffix = "" | |
| else: | |
| has_memory = True | |
| mem_suffix = f"_addr{addr_bits}" | |
| elif memory_profile == "none": | |
| has_memory = False | |
| mem_suffix = "" | |
| elif memory_profile == "full" or memory_profile is None: | |
| has_memory = True | |
| mem_suffix = "" | |
| else: | |
| has_memory = True | |
| mem_suffix = f"_{memory_profile}" | |
| base = "neural_alu" if not has_memory else "neural_computer" | |
| return MODEL_DIR / f"{base}{bits}{mem_suffix}.safetensors" | |
| MODEL_PATH = MODEL_DIR / "neural_computer8.safetensors" | |
| MANIFEST_PATH = Path(__file__).resolve().parent.parent / "tensors.txt" | |
| DEFAULT_ADDR_BITS = 16 | |
| DEFAULT_MEM_BYTES = 1 << DEFAULT_ADDR_BITS | |
| MEMORY_PROFILES = { | |
| "full": 16, # 64KB - full CPU mode | |
| "reduced": 12, # 4KB - reduced CPU | |
| "small": 10, # 1KB - 32-bit arithmetic scratch | |
| "scratchpad": 8, # 256 bytes - LLM scratchpad | |
| "registers": 4, # 16 bytes - LLM register file | |
| "none": 0, # Pure ALU, no memory | |
| } | |
| SUPPORTED_BITS = [8, 16, 32] | |
| def load_tensors(path: Path) -> Dict[str, torch.Tensor]: | |
| tensors: Dict[str, torch.Tensor] = {} | |
| with safe_open(str(path), framework="pt") as f: | |
| for name in f.keys(): | |
| tensors[name] = f.get_tensor(name).clone() | |
| return tensors | |
| def load_file_metadata(path: Path) -> Dict[str, str]: | |
| """Read the safetensors header metadata so every save can carry it | |
| forward (signal_registry, weight_quantization, ...).""" | |
| with safe_open(str(path), framework="pt") as f: | |
| meta = f.metadata() | |
| return dict(meta) if meta else {} | |
| def get_all_gates(tensors: Dict[str, torch.Tensor]) -> Set[str]: | |
| gates = set() | |
| for name in tensors: | |
| if name.endswith('.weight'): | |
| gates.add(name[:-7]) | |
| return gates | |
| class SignalRegistry: | |
| def __init__(self): | |
| self.name_to_id: Dict[str, int] = {} | |
| self.id_to_name: Dict[int, str] = {} | |
| self.next_id = 0 | |
| self.register("#0") | |
| self.register("#1") | |
| def register(self, name: str) -> int: | |
| if name not in self.name_to_id: | |
| self.name_to_id[name] = self.next_id | |
| self.id_to_name[self.next_id] = name | |
| self.next_id += 1 | |
| return self.name_to_id[name] | |
| def get_id(self, name: str) -> int: | |
| return self.name_to_id.get(name, -1) | |
| def to_metadata(self) -> str: | |
| return json.dumps(self.id_to_name) | |
| def add_gate(tensors: Dict[str, torch.Tensor], name: str, weight: Iterable[float], bias: Iterable[float]) -> None: | |
| w_key = f"{name}.weight" | |
| b_key = f"{name}.bias" | |
| if w_key in tensors or b_key in tensors: | |
| raise ValueError(f"Gate already exists: {name}") | |
| tensors[w_key] = torch.tensor(list(weight), dtype=torch.float32) | |
| tensors[b_key] = torch.tensor(list(bias), dtype=torch.float32) | |
| def drop_prefixes(tensors: Dict[str, torch.Tensor], prefixes: List[str]) -> None: | |
| for key in list(tensors.keys()): | |
| if any(key.startswith(prefix) for prefix in prefixes): | |
| del tensors[key] | |
| def add_decoder(tensors: Dict[str, torch.Tensor], addr_bits: int, mem_bytes: int) -> None: | |
| weights = torch.empty((mem_bytes, addr_bits), dtype=torch.float32) | |
| bias = torch.empty((mem_bytes,), dtype=torch.float32) | |
| for addr in range(mem_bytes): | |
| bits = [(addr >> (addr_bits - 1 - i)) & 1 for i in range(addr_bits)] | |
| weights[addr] = torch.tensor([1.0 if bit == 1 else -1.0 for bit in bits], dtype=torch.float32) | |
| bias[addr] = -float(sum(bits)) | |
| tensors["memory.addr_decode.weight"] = weights | |
| tensors["memory.addr_decode.bias"] = bias | |
| def add_memory_read_mux(tensors: Dict[str, torch.Tensor], mem_bytes: int) -> None: | |
| and_weight = torch.ones((8, mem_bytes, 2), dtype=torch.float32) | |
| and_bias = torch.full((8, mem_bytes), -2.0, dtype=torch.float32) | |
| or_weight = torch.ones((8, mem_bytes), dtype=torch.float32) | |
| or_bias = torch.full((8,), -1.0, dtype=torch.float32) | |
| tensors["memory.read.and.weight"] = and_weight | |
| tensors["memory.read.and.bias"] = and_bias | |
| tensors["memory.read.or.weight"] = or_weight | |
| tensors["memory.read.or.bias"] = or_bias | |
| def add_memory_write_cells(tensors: Dict[str, torch.Tensor], mem_bytes: int) -> None: | |
| sel_weight = torch.ones((mem_bytes, 2), dtype=torch.float32) | |
| sel_bias = torch.full((mem_bytes,), -2.0, dtype=torch.float32) | |
| nsel_weight = torch.full((mem_bytes, 1), -1.0, dtype=torch.float32) | |
| nsel_bias = torch.zeros((mem_bytes,), dtype=torch.float32) | |
| and_old_weight = torch.ones((mem_bytes, 8, 2), dtype=torch.float32) | |
| and_old_bias = torch.full((mem_bytes, 8), -2.0, dtype=torch.float32) | |
| and_new_weight = torch.ones((mem_bytes, 8, 2), dtype=torch.float32) | |
| and_new_bias = torch.full((mem_bytes, 8), -2.0, dtype=torch.float32) | |
| or_weight = torch.ones((mem_bytes, 8, 2), dtype=torch.float32) | |
| or_bias = torch.full((mem_bytes, 8), -1.0, dtype=torch.float32) | |
| tensors["memory.write.sel.weight"] = sel_weight | |
| tensors["memory.write.sel.bias"] = sel_bias | |
| tensors["memory.write.nsel.weight"] = nsel_weight | |
| tensors["memory.write.nsel.bias"] = nsel_bias | |
| tensors["memory.write.and_old.weight"] = and_old_weight | |
| tensors["memory.write.and_old.bias"] = and_old_bias | |
| tensors["memory.write.and_new.weight"] = and_new_weight | |
| tensors["memory.write.and_new.bias"] = and_new_bias | |
| tensors["memory.write.or.weight"] = or_weight | |
| tensors["memory.write.or.bias"] = or_bias | |
| def add_fetch_load_store_buffers(tensors: Dict[str, torch.Tensor], data_bits: int, addr_bits: int) -> None: | |
| """Add control buffers for fetch, load, store operations. | |
| Args: | |
| data_bits: Width of data bus (8/16/32) | |
| addr_bits: Width of address bus (determines instruction register width) | |
| """ | |
| # Instruction register width = opcode (8) + operands (depends on arch) | |
| # For simplicity, IR width = max(16, addr_bits) to hold jump targets | |
| ir_bits = max(16, addr_bits) | |
| for bit in range(ir_bits): | |
| add_gate(tensors, f"control.fetch.ir.bit{bit}", [1.0], [-1.0]) | |
| for bit in range(data_bits): | |
| add_gate(tensors, f"control.load.bit{bit}", [1.0], [-1.0]) | |
| add_gate(tensors, f"control.store.bit{bit}", [1.0], [-1.0]) | |
| for bit in range(addr_bits): | |
| add_gate(tensors, f"control.mem_addr.bit{bit}", [1.0], [-1.0]) | |
| def add_full_adder(tensors: Dict[str, torch.Tensor], prefix: str) -> None: | |
| """Add a single full adder at the given prefix. | |
| Full adder structure: | |
| - ha1: first half adder (A XOR B for sum, A AND B for carry) | |
| - ha2: second half adder (ha1.sum XOR Cin for sum, ha1.sum AND Cin for carry) | |
| - carry_or: OR of ha1.carry and ha2.carry for final carry out | |
| """ | |
| # XOR for ha1.sum (2-layer: OR + NAND -> AND) | |
| add_gate(tensors, f"{prefix}.ha1.sum.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.ha1.sum.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.ha1.sum.layer2", [1.0, 1.0], [-2.0]) | |
| # AND for ha1.carry | |
| add_gate(tensors, f"{prefix}.ha1.carry", [1.0, 1.0], [-2.0]) | |
| # XOR for ha2.sum | |
| add_gate(tensors, f"{prefix}.ha2.sum.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.ha2.sum.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.ha2.sum.layer2", [1.0, 1.0], [-2.0]) | |
| # AND for ha2.carry | |
| add_gate(tensors, f"{prefix}.ha2.carry", [1.0, 1.0], [-2.0]) | |
| # OR for final carry | |
| add_gate(tensors, f"{prefix}.carry_or", [1.0, 1.0], [-1.0]) | |
| def add_expr_add_mul(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add expression circuit for A + B × C (order of operations). | |
| Computes A + (B × C) where multiplication has higher precedence. | |
| Structure: | |
| - Stage 1: Multiply B × C using shift-add algorithm | |
| - 8 mask stages: mask[i] = B AND C[i] (8 AND gates each, shifted) | |
| - 7 accumulator adders to sum masked values | |
| - Stage 2: Add A to multiplication result (8-bit ripple carry) | |
| Inputs: $a[0-7], $b[0-7], $c[0-7] (MSB-first, 8-bit each) | |
| Output: 8-bit result of A + (B × C), wrapping on overflow | |
| Total: 64 AND gates + 7×8 full adders (mul) + 8 full adders (add) = ~640 gates | |
| """ | |
| prefix = "arithmetic.expr_add_mul" | |
| # Stage 1: Multiply B × C using shift-add | |
| # For each bit i of C, we AND all bits of B with C[i] | |
| # This creates partial products that are shifted by i positions | |
| # Mask AND gates: mask[stage][bit] = B[bit] AND C[stage] | |
| # These compute B & (C[i] ? 0xFF : 0x00) for each bit of C | |
| for stage in range(8): | |
| for bit in range(8): | |
| add_gate(tensors, f"{prefix}.mul.mask.s{stage}.b{bit}", [1.0, 1.0], [-2.0]) | |
| # Accumulator adders for shift-add multiplication | |
| # Stage 0: acc = mask0 (no adder needed, just the masked value) | |
| # Stage 1-7: acc = acc + (mask[i] << i) | |
| # We need to handle the shifting by connecting different bit positions | |
| # For proper shift-add, we need adders that accumulate partial products | |
| # Each stage adds a shifted partial product to the accumulator | |
| # Using 16-bit internal accumulator, output low 8 bits | |
| # Simplified approach: chain of 8-bit adders with proper bit alignment | |
| # acc_stage[i] = acc_stage[i-1] + (mask[i] << i) | |
| # We keep only low 8 bits at each stage for 8-bit result | |
| for stage in range(1, 8): # 7 accumulator adders | |
| for bit in range(8): | |
| add_full_adder(tensors, f"{prefix}.mul.acc.s{stage}.fa{bit}") | |
| # Stage 2: Add A to multiplication result | |
| for bit in range(8): | |
| add_full_adder(tensors, f"{prefix}.add.fa{bit}") | |
| def add_expr_paren_add_mul(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add expression circuit for (A + B) × C (parenthetical override). | |
| Computes (A + B) × C where parentheses override normal precedence. | |
| Addition happens first, then multiplication. | |
| Structure: | |
| - Stage 1: Add A + B (8-bit ripple carry adder) | |
| - Stage 2: Multiply sum × C using shift-add algorithm | |
| - 8 mask stages: mask[i] = sum AND C[i] (8 AND gates each) | |
| - 7 accumulator adders to sum shifted masked values | |
| Inputs: $a[0-7], $b[0-7], $c[0-7] (MSB-first, 8-bit each) | |
| Output: 8-bit result of (A + B) × C, wrapping on overflow | |
| Total: 8 full adders (add) + 64 AND gates + 56 full adders (mul) = ~640 gates | |
| """ | |
| prefix = "arithmetic.expr_paren_add_mul" | |
| # Stage 1: Add A + B | |
| for bit in range(8): | |
| add_full_adder(tensors, f"{prefix}.add.fa{bit}") | |
| # Stage 2: Multiply sum × C using shift-add | |
| # Mask AND gates: mask[stage][bit] = sum[bit] AND C[stage] | |
| for stage in range(8): | |
| for bit in range(8): | |
| add_gate(tensors, f"{prefix}.mul.mask.s{stage}.b{bit}", [1.0, 1.0], [-2.0]) | |
| # Accumulator adders for shift-add multiplication | |
| for stage in range(1, 8): # 7 accumulator adders | |
| for bit in range(8): | |
| add_full_adder(tensors, f"{prefix}.mul.acc.s{stage}.fa{bit}") | |
| def add_expr_paren(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add expression circuit for (A + B) × C (parenthetical grouping). | |
| Computes (A + B) × C where addition happens first due to parentheses. | |
| Structure: | |
| - Stage 1: Add A + B (8-bit ripple carry) | |
| - Stage 2: Multiply sum × C using shift-add algorithm | |
| - 8 mask stages: mask[i] = sum AND C[i] (8 AND gates each) | |
| - 7 accumulator adders to sum shifted masked values | |
| Inputs: $a[0-7], $b[0-7], $c[0-7] (MSB-first, 8-bit each) | |
| Output: 8-bit result of (A + B) × C, wrapping on overflow | |
| Total: 8 full adders (add) + 64 AND gates + 56 full adders (mul) = ~640 gates | |
| """ | |
| prefix = "arithmetic.expr_paren" | |
| # Stage 1: Add A + B | |
| for bit in range(8): | |
| add_full_adder(tensors, f"{prefix}.add.fa{bit}") | |
| # Stage 2: Multiply sum × C using shift-add | |
| # Mask AND gates: mask[stage][bit] = sum[bit] AND C[stage] | |
| for stage in range(8): | |
| for bit in range(8): | |
| add_gate(tensors, f"{prefix}.mul.mask.s{stage}.b{bit}", [1.0, 1.0], [-2.0]) | |
| # Accumulator adders for shift-add multiplication | |
| for stage in range(1, 8): # 7 accumulator adders | |
| for bit in range(8): | |
| add_full_adder(tensors, f"{prefix}.mul.acc.s{stage}.fa{bit}") | |
| def add_add3(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add 3-operand 8-bit adder circuit. | |
| Computes A + B + C using two chained ripple-carry stages: | |
| - Stage 1: temp = A + B (8 full adders) | |
| - Stage 2: result = temp + C (8 full adders) | |
| Inputs: $a[0-7], $b[0-7], $c[0-7] (MSB-first) | |
| Outputs: stage2.fa0-7.ha2.sum.layer2 (result bits), stage2.fa7.carry_or (overflow) | |
| Total: 16 full adders = 144 gates | |
| """ | |
| # Stage 1: A + B -> temp | |
| for bit in range(8): | |
| add_full_adder(tensors, f"arithmetic.add3_8bit.stage1.fa{bit}") | |
| # Stage 2: temp + C -> result | |
| for bit in range(8): | |
| add_full_adder(tensors, f"arithmetic.add3_8bit.stage2.fa{bit}") | |
| def add_shl_shr(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add SHL (shift left) and SHR (shift right) circuits. | |
| Identity gate: w=1, b=-1 -> H(x - 1) = x for x in {0,1} | |
| Zero gate: w=0, b=-1 -> H(-1) = 0 | |
| SHL (MSB-first): out[i] = in[i+1] for i<7, out[7] = 0 | |
| SHR (MSB-first): out[0] = 0, out[i] = in[i-1] for i>0 | |
| """ | |
| for bit in range(8): | |
| if bit < 7: | |
| add_gate(tensors, f"alu.alu8bit.shl.bit{bit}", [1.0], [-1.0]) | |
| else: | |
| add_gate(tensors, f"alu.alu8bit.shl.bit{bit}", [0.0], [-1.0]) | |
| for bit in range(8): | |
| if bit > 0: | |
| add_gate(tensors, f"alu.alu8bit.shr.bit{bit}", [1.0], [-1.0]) | |
| else: | |
| add_gate(tensors, f"alu.alu8bit.shr.bit{bit}", [0.0], [-1.0]) | |
| def add_mul(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add 8-bit multiplication circuit. | |
| Produces low 8 bits of the 16-bit result. | |
| Structure: | |
| - 64 AND gates for partial products P[i][j] = A[i] AND B[j] | |
| - Uses existing ripple-carry adder components for summation | |
| The multiply method in ThresholdALU computes: | |
| 1. Partial products via these AND gates | |
| 2. Shift-add accumulation via existing 8-bit adder | |
| """ | |
| # AND gates for partial products: P[i][j] = A[i] AND B[j] | |
| # These compute whether bit i of A and bit j of B are both 1 | |
| for i in range(8): | |
| for j in range(8): | |
| add_gate(tensors, f"alu.alu8bit.mul.pp.a{i}b{j}", [1.0, 1.0], [-2.0]) | |
| def add_div(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add 8-bit division circuit. | |
| Produces quotient (8 bits) and remainder (8 bits). | |
| Uses restoring division algorithm: | |
| - 8 iterations, each producing one quotient bit | |
| - Each iteration: compare, conditionally subtract, shift | |
| Structure: | |
| - 8 comparison gates (one per iteration) | |
| - 8 conditional subtraction stages | |
| - Uses existing comparator and subtractor components | |
| """ | |
| # Comparison gates: check if (remainder << 1 | next_bit) >= divisor | |
| # Bit-cascaded ternary GE comparator per stage. | |
| for stage in range(8): | |
| add_bit_cascade_compare( | |
| tensors, | |
| cmp_prefix=f"alu.alu8bit.div.stage{stage}.cmp_bc", | |
| bits=8, | |
| out_gt=f"alu.alu8bit.div.stage{stage}.cmp_bc.gt", | |
| out_lt=f"alu.alu8bit.div.stage{stage}.cmp_bc.lt", | |
| out_ge=f"alu.alu8bit.div.stage{stage}.cmp", | |
| out_le=f"alu.alu8bit.div.stage{stage}.cmp_bc.le", | |
| out_eq=f"alu.alu8bit.div.stage{stage}.cmp_bc.eq", | |
| ) | |
| # Conditional mux gates: select (rem - div) or rem based on comparison | |
| for stage in range(8): | |
| for bit in range(8): | |
| # NOT for inverting comparison result | |
| add_gate(tensors, f"alu.alu8bit.div.stage{stage}.mux.bit{bit}.not_sel", [-1.0], [0.0]) | |
| # AND gates for mux | |
| add_gate(tensors, f"alu.alu8bit.div.stage{stage}.mux.bit{bit}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"alu.alu8bit.div.stage{stage}.mux.bit{bit}.and_b", [1.0, 1.0], [-2.0]) | |
| # OR gate for mux output | |
| add_gate(tensors, f"alu.alu8bit.div.stage{stage}.mux.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| def add_inc_dec(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add INC and DEC circuits. | |
| INC: A + 1 using half adders with carry chain | |
| DEC: A - 1 using borrow chain (A + 255, two's complement of 1) | |
| For INC, we add 1 to the LSB and propagate carry. | |
| For DEC, we add 0xFF (two's complement of 1) or use borrow logic. | |
| """ | |
| # INC: half adder chain starting with carry_in = 1 | |
| # bit 7 (LSB): XOR with 1, carry = bit[7] | |
| # bit 6: XOR with carry, new_carry = bit[6] AND old_carry | |
| # ... | |
| for bit in range(8): | |
| # XOR for sum (two-layer) | |
| add_gate(tensors, f"alu.alu8bit.inc.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"alu.alu8bit.inc.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"alu.alu8bit.inc.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| # AND for carry propagation | |
| add_gate(tensors, f"alu.alu8bit.inc.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| # DEC: similar but with borrow logic | |
| # Equivalent to adding 0xFF with carry_in = 0 | |
| # Or: NOT each bit, propagate borrow | |
| for bit in range(8): | |
| # XOR for difference | |
| add_gate(tensors, f"alu.alu8bit.dec.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"alu.alu8bit.dec.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"alu.alu8bit.dec.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| # Borrow: NOT(A) AND borrow_in, equivalent to (NOT A) when borrow_in=1 | |
| add_gate(tensors, f"alu.alu8bit.dec.bit{bit}.not_a", [-1.0], [0.0]) | |
| add_gate(tensors, f"alu.alu8bit.dec.bit{bit}.borrow", [1.0, 1.0], [-2.0]) | |
| def add_neg(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add NEG circuit (two's complement negation). | |
| NEG(A) = NOT(A) + 1 = ~A + 1 | |
| Structure: NOT gates followed by INC-style adder. | |
| """ | |
| for bit in range(8): | |
| # NOT gate for each bit | |
| add_gate(tensors, f"alu.alu8bit.neg.not.bit{bit}", [-1.0], [0.0]) | |
| # Then add 1 using half adder chain | |
| add_gate(tensors, f"alu.alu8bit.neg.inc.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"alu.alu8bit.neg.inc.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"alu.alu8bit.neg.inc.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"alu.alu8bit.neg.inc.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| def add_rol_ror(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add 8-bit ROL and ROR circuits (legacy wrapper).""" | |
| add_rol_ror_nbits(tensors, 8) | |
| def add_rol_ror_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit ROL and ROR circuits (rotate left/right). | |
| ROL: out[i] = in[i+1] for i<N-1, out[N-1] = in[0] (MSB wraps to LSB) | |
| ROR: out[0] = in[N-1], out[i] = in[i-1] for i>0 (LSB wraps to MSB) | |
| Args: | |
| bits: Data width (8, 16, 32, etc.) | |
| """ | |
| # ROL: rotate left (toward MSB) | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.rol.bit{bit}", [1.0], [-1.0]) | |
| # ROR: rotate right (toward LSB) | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.ror.bit{bit}", [1.0], [-1.0]) | |
| def add_stack_ops(tensors: Dict[str, torch.Tensor], data_bits: int, addr_bits: int) -> None: | |
| """Add RET, PUSH, POP circuit components. | |
| These are higher-level operations that use memory read/write. | |
| We create the control logic gates. | |
| Args: | |
| data_bits: Width of data to push/pop (8/16/32) | |
| addr_bits: Width of stack pointer and return addresses | |
| RET: Pop return address from stack, jump to it | |
| PUSH: Decrement SP, write value to [SP] | |
| POP: Read value from [SP], increment SP | |
| """ | |
| # SP decrement for PUSH (addr_bits wide) | |
| for bit in range(addr_bits): | |
| add_gate(tensors, f"control.push.sp_dec.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"control.push.sp_dec.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"control.push.sp_dec.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"control.push.sp_dec.bit{bit}.borrow", [1.0, 1.0], [-2.0]) | |
| # SP increment for POP (addr_bits wide) | |
| for bit in range(addr_bits): | |
| add_gate(tensors, f"control.pop.sp_inc.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"control.pop.sp_inc.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"control.pop.sp_inc.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"control.pop.sp_inc.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| # Data buffers for PUSH (data_bits wide) | |
| for bit in range(data_bits): | |
| add_gate(tensors, f"control.push.data.bit{bit}", [1.0], [-1.0]) | |
| # Data buffers for POP (data_bits wide) | |
| for bit in range(data_bits): | |
| add_gate(tensors, f"control.pop.data.bit{bit}", [1.0], [-1.0]) | |
| # RET: Buffer gates for return address (addr_bits wide) | |
| for bit in range(addr_bits): | |
| add_gate(tensors, f"control.ret.addr.bit{bit}", [1.0], [-1.0]) | |
| def add_conditional_jumps(tensors: Dict[str, torch.Tensor], addr_bits: int) -> None: | |
| """Add conditional jump circuits (JZ, JNZ, JC, JNC, JP, JN, JV, JNV). | |
| Each conditional jump is a 2:1 MUX per address bit: | |
| - If flag is set: output = target_bit | |
| - If flag is clear: output = pc_bit | |
| Structure per bit: | |
| - not_sel: NOT(flag) | |
| - and_a: pc_bit AND NOT(flag) | |
| - and_b: target_bit AND flag | |
| - or: and_a OR and_b | |
| Args: | |
| addr_bits: Width of program counter / jump target | |
| """ | |
| jump_types = ['jz', 'jnz', 'jc', 'jnc', 'jp', 'jn', 'jv', 'jnv'] | |
| for jmp in jump_types: | |
| for bit in range(addr_bits): | |
| prefix = f"control.{jmp}.bit{bit}" | |
| # NOT sel (invert flag) | |
| add_gate(tensors, f"{prefix}.not_sel", [-1.0], [0.0]) | |
| # AND a: pc_bit AND NOT(flag) | |
| add_gate(tensors, f"{prefix}.and_a", [1.0, 1.0], [-2.0]) | |
| # AND b: target_bit AND flag | |
| add_gate(tensors, f"{prefix}.and_b", [1.0, 1.0], [-2.0]) | |
| # OR: combine | |
| add_gate(tensors, f"{prefix}.or", [1.0, 1.0], [-1.0]) | |
| def add_computer_decode(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Instruction decode for the 8-bit computer as threshold gates: a 4-to-16 | |
| opcode one-hot decoder (exact match on the instruction's opcode nibble). The | |
| runtime reads these one-hots to dispatch instead of slicing the word.""" | |
| for opc in range(16): | |
| w = [1.0 if (opc >> j) & 1 else -1.0 for j in range(4)] | |
| add_gate(tensors, f"control.decode.op{opc}", w, [-float(bin(opc).count("1"))]) | |
| def add_computer_pcnext(tensors: Dict[str, torch.Tensor], addr_bits: int) -> None: | |
| """PC sequencing for the 8-bit computer as threshold gates: PC+2 / PC+4 | |
| increment chains and a priority mux selecting the next PC among PC+2 (ALU), | |
| PC+4 (load/store), the conditional-jump mux, and the direct target | |
| (JMP/CALL). Replaces the runtime's Python PC+2/PC+4 and jump wiring.""" | |
| for name, start in (("pc2", 1), ("pc4", 2)): | |
| for bit in range(addr_bits): | |
| if bit < start: | |
| add_gate(tensors, f"control.pcnext.{name}.bit{bit}", [1.0], [-1.0]) | |
| else: | |
| add_gate(tensors, f"control.pcnext.{name}.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"control.pcnext.{name}.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"control.pcnext.{name}.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"control.pcnext.{name}.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| # priority mux: next = use_addr ? addr : (use_jcc ? jcc : (use_pc4 ? pc4 : pc2)) | |
| for lvl in ("m_pc4", "m_jcc", "m_addr"): | |
| for bit in range(addr_bits): | |
| add_gate(tensors, f"control.pcnext.{lvl}.bit{bit}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"control.pcnext.{lvl}.bit{bit}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"control.pcnext.{lvl}.bit{bit}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"control.pcnext.{lvl}.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| def add_status_flags(tensors: Dict[str, torch.Tensor], data_bits: int) -> None: | |
| """Add status flag computation circuits (Z, N, C, V). | |
| Args: | |
| data_bits: Width of ALU data (8/16/32) | |
| Flags: | |
| - Z (Zero): NOR of all result bits (1 if result == 0) | |
| - N (Negative): Copy of MSB (sign bit) | |
| - C (Carry): Carry out from adder (external input) | |
| - V (Overflow): XOR of carry into and out of MSB (signed overflow) | |
| """ | |
| # Z flag: NOR of all bits (result == 0) | |
| # Single threshold gate: fires if sum of all bits < 1 | |
| add_gate(tensors, "flags.zero", [-1.0] * data_bits, [0.0]) | |
| # N flag: Buffer for MSB (sign bit) | |
| add_gate(tensors, "flags.negative", [1.0], [-1.0]) | |
| # C flag: Buffer for carry out (input from adder) | |
| add_gate(tensors, "flags.carry", [1.0], [-1.0]) | |
| # V flag: XOR of carry_in_msb and carry_out_msb | |
| # Two-layer XOR: (A OR B) AND (A NAND B) | |
| add_gate(tensors, "flags.overflow.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, "flags.overflow.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, "flags.overflow.layer2", [1.0, 1.0], [-2.0]) | |
| def add_barrel_shifter(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add 8-bit barrel shifter circuit (legacy wrapper).""" | |
| add_barrel_shifter_nbits(tensors, 8) | |
| def add_barrel_shifter_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit barrel shifter circuit. | |
| Shifts input by 0 to (bits-1) positions based on ceil(log2(bits))-bit shift amount. | |
| Uses layers of 2:1 muxes controlled by shift amount bits. | |
| Args: | |
| bits: Data width (8, 16, 32, etc.) | |
| """ | |
| import math | |
| num_layers = max(1, math.ceil(math.log2(bits))) | |
| for layer in range(num_layers): | |
| shift_amount = 1 << (num_layers - 1 - layer) | |
| for bit in range(bits): | |
| prefix = f"combinational.barrelshifter{bits}.layer{layer}.bit{bit}" | |
| # 2:1 mux: if sel then shifted else original | |
| add_gate(tensors, f"{prefix}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.or", [1.0, 1.0], [-1.0]) | |
| def add_priority_encoder(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add 8-bit priority encoder circuit (legacy wrapper).""" | |
| add_priority_encoder_nbits(tensors, 8) | |
| def add_priority_encoder_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit priority encoder circuit. | |
| Finds the position of the highest set bit (0 to bits-1). | |
| Position 0 = MSB (highest priority), Position bits-1 = LSB (lowest priority). | |
| Output is ceil(log2(bits))-bit index + valid flag. | |
| Circuit structure: | |
| 1. any_higher{pos}: OR of bits 0 to pos-1 (all higher-priority positions) | |
| 2. is_highest{pos}: bit[pos] AND NOT(any_higher{pos}) | |
| 3. out{bit}: OR of is_highest{pos} for positions where (pos >> bit) & 1 | |
| 4. valid: OR of all input bits | |
| Args: | |
| bits: Input width (8, 16, 32, etc.) | |
| """ | |
| import math | |
| out_bits = max(1, math.ceil(math.log2(bits))) | |
| prefix = f"combinational.priorityencoder{bits}" | |
| # any_higher{pos}: OR of all bits at positions 0 to pos-1 (higher priority) | |
| # any_higher{0} not needed (no higher bits) | |
| # any_higher{1} = bit[0] | |
| # any_higher{N} = bit[0] OR bit[1] OR ... OR bit[N-1] | |
| for pos in range(1, bits): | |
| weights = [1.0] * pos | |
| add_gate(tensors, f"{prefix}.any_higher{pos}", weights, [-1.0]) | |
| # is_highest{pos}: bit[pos] is set AND no higher-priority bit is set | |
| # is_highest{0} = bit[0] (always highest if set) | |
| # is_highest{pos} = bit[pos] AND NOT(any_higher{pos}) for pos > 0 | |
| for pos in range(1, bits): | |
| add_gate(tensors, f"{prefix}.is_highest{pos}.not_higher", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.is_highest{pos}.and", [1.0, 1.0], [-2.0]) | |
| # Encode position to output bits | |
| # out{bit} = OR of is_highest{pos} for all pos where (pos >> bit) & 1 | |
| for out_bit in range(out_bits): | |
| weights = [] | |
| for pos in range(bits): | |
| if (pos >> out_bit) & 1: | |
| weights.append(1.0) | |
| if weights: | |
| add_gate(tensors, f"{prefix}.out{out_bit}", weights, [-1.0]) | |
| # Valid flag: any bit set | |
| add_gate(tensors, f"{prefix}.valid", [1.0] * bits, [-1.0]) | |
| def add_bit_cascade_compare( | |
| tensors: Dict[str, torch.Tensor], | |
| cmp_prefix: str, | |
| bits: int, | |
| out_gt: str, | |
| out_lt: str, | |
| out_ge: str, | |
| out_le: str, | |
| out_eq: str, | |
| ) -> None: | |
| """Generic ternary-only N-bit comparator. | |
| Inputs are two N-bit values A and B in MSB-first order. The structure | |
| produces unsigned-magnitude GT, LT, GE, LE, EQ outputs using only | |
| weights in {-1, 0, 1} and integer biases. | |
| Per-bit primitives (i = 0 is the MSB): | |
| {cmp_prefix}.bit{i}.gt A[i] AND NOT B[i] weights [1, -1], bias -1 | |
| {cmp_prefix}.bit{i}.lt NOT A[i] AND B[i] weights [-1, 1], bias -1 | |
| {cmp_prefix}.bit{i}.eq.layer1.and A[i] AND B[i] | |
| {cmp_prefix}.bit{i}.eq.layer1.nor NOR(A[i], B[i]) | |
| {cmp_prefix}.bit{i}.eq XNOR via OR of layer1 outputs | |
| Cascade (linear chain from MSB to LSB): | |
| {cmp_prefix}.cascade.eq_prefix.bit{i} AND of eq[0..i-1] (i in 1..N-1) | |
| {cmp_prefix}.cascade.gt.bit{i} eq_prefix[i] AND gt[i] | |
| {cmp_prefix}.cascade.lt.bit{i} eq_prefix[i] AND lt[i] | |
| Final outputs: | |
| out_gt = OR of (gt[0], cascade.gt.bit{1..N-1}) | |
| out_lt = OR of (lt[0], cascade.lt.bit{1..N-1}) | |
| out_eq = AND of all eq[i] | |
| out_ge = NOT(out_lt) | |
| out_le = NOT(out_gt) | |
| """ | |
| for i in range(bits): | |
| # per-bit GT: A[i] AND NOT B[i] -> H(A - B - 1) | |
| add_gate(tensors, f"{cmp_prefix}.bit{i}.gt", [1.0, -1.0], [-1.0]) | |
| # per-bit LT: NOT A[i] AND B[i] -> H(-A + B - 1) | |
| add_gate(tensors, f"{cmp_prefix}.bit{i}.lt", [-1.0, 1.0], [-1.0]) | |
| # per-bit EQ via XNOR = (A AND B) OR (NOR A B) | |
| add_gate(tensors, f"{cmp_prefix}.bit{i}.eq.layer1.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{cmp_prefix}.bit{i}.eq.layer1.nor", [-1.0, -1.0], [0.0]) | |
| add_gate(tensors, f"{cmp_prefix}.bit{i}.eq", [1.0, 1.0], [-1.0]) | |
| # eq_prefix[i] = AND of eq[0..i-1], i in 1..N-1 | |
| for i in range(1, bits): | |
| add_gate( | |
| tensors, | |
| f"{cmp_prefix}.cascade.eq_prefix.bit{i}", | |
| [1.0] * i, | |
| [-float(i)], | |
| ) | |
| # cascade.gt[i], cascade.lt[i] for i in 1..N-1 | |
| for i in range(1, bits): | |
| add_gate(tensors, f"{cmp_prefix}.cascade.gt.bit{i}", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{cmp_prefix}.cascade.lt.bit{i}", [1.0, 1.0], [-2.0]) | |
| # Final OR for GT and LT (N inputs each) | |
| add_gate(tensors, out_gt, [1.0] * bits, [-1.0]) | |
| add_gate(tensors, out_lt, [1.0] * bits, [-1.0]) | |
| # AND of all eq's for EQ | |
| add_gate(tensors, out_eq, [1.0] * bits, [-float(bits)]) | |
| # GE = NOT(LT), LE = NOT(GT) -- single-input NOT then identity buffer | |
| add_gate(tensors, f"{out_ge}.not_lt", [-1.0], [0.0]) | |
| add_gate(tensors, out_ge, [1.0], [-1.0]) | |
| add_gate(tensors, f"{out_le}.not_gt", [-1.0], [0.0]) | |
| add_gate(tensors, out_le, [1.0], [-1.0]) | |
| def add_comparators(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add 8-bit comparator circuits (GT, LT, GE, LE, EQ) using bit-cascade. | |
| Inputs are 8 bits of A then 8 bits of B in MSB-first order. The | |
| underlying bit-cascade produces only ternary {-1, 0, 1} weights. | |
| """ | |
| add_bit_cascade_compare( | |
| tensors, | |
| cmp_prefix="arithmetic.cmp8bit", | |
| bits=8, | |
| out_gt="arithmetic.greaterthan8bit", | |
| out_lt="arithmetic.lessthan8bit", | |
| out_ge="arithmetic.greaterorequal8bit", | |
| out_le="arithmetic.lessorequal8bit", | |
| out_eq="arithmetic.equality8bit", | |
| ) | |
| def add_ripple_carry_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit ripple carry adder circuit. | |
| Creates a chain of full adders for N-bit addition. | |
| Works for 8, 16, or 32 bits. | |
| Inputs: $a[0..N-1], $b[0..N-1] (MSB-first) | |
| Outputs: fa0-fa{N-1} sum bits, fa{N-1}.carry_or for overflow | |
| """ | |
| prefix = f"arithmetic.ripplecarry{bits}bit" | |
| for bit in range(bits): | |
| add_full_adder(tensors, f"{prefix}.fa{bit}") | |
| def add_sub_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit subtractor circuit (A - B). | |
| Uses two's complement: A - B = A + (~B) + 1 | |
| Structure: | |
| - NOT gates for each bit of B | |
| - N-bit ripple carry adder with carry_in = 1 | |
| The carry_in=1 is handled by the adder's fa0 having cin=#1 instead of #0. | |
| """ | |
| prefix = f"arithmetic.sub{bits}bit" | |
| for bit in range(bits): | |
| add_gate(tensors, f"{prefix}.not_b.bit{bit}", [-1.0], [0.0]) | |
| for bit in range(bits): | |
| add_full_adder(tensors, f"{prefix}.fa{bit}") | |
| def add_comparators_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit comparator circuits (GT, LT, GE, LE, EQ) via bit-cascade. | |
| All weights are in {-1, 0, 1}. Inputs are A bits then B bits, MSB-first. | |
| """ | |
| add_bit_cascade_compare( | |
| tensors, | |
| cmp_prefix=f"arithmetic.cmp{bits}bit", | |
| bits=bits, | |
| out_gt=f"arithmetic.greaterthan{bits}bit", | |
| out_lt=f"arithmetic.lessthan{bits}bit", | |
| out_ge=f"arithmetic.greaterorequal{bits}bit", | |
| out_le=f"arithmetic.lessorequal{bits}bit", | |
| out_eq=f"arithmetic.equality{bits}bit", | |
| ) | |
| def add_mul_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit multiplication circuit. | |
| Produces low N bits of the 2N-bit result. | |
| Structure: | |
| - N*N AND gates for partial products P[i][j] = A[i] AND B[j] | |
| - Shift-add accumulation using existing adder circuits | |
| For 32-bit: 1024 AND gates for partial products. | |
| """ | |
| for i in range(bits): | |
| for j in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.mul.pp.a{i}b{j}", [1.0, 1.0], [-2.0]) | |
| def add_div_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit division circuit. | |
| Uses restoring division algorithm with N iterations. Each stage's | |
| comparator (rem >= div) is a ternary bit-cascade GE. | |
| """ | |
| for stage in range(bits): | |
| add_bit_cascade_compare( | |
| tensors, | |
| cmp_prefix=f"alu.alu{bits}bit.div.stage{stage}.cmp_bc", | |
| bits=bits, | |
| out_gt=f"alu.alu{bits}bit.div.stage{stage}.cmp_bc.gt", | |
| out_lt=f"alu.alu{bits}bit.div.stage{stage}.cmp_bc.lt", | |
| out_ge=f"alu.alu{bits}bit.div.stage{stage}.cmp", | |
| out_le=f"alu.alu{bits}bit.div.stage{stage}.cmp_bc.le", | |
| out_eq=f"alu.alu{bits}bit.div.stage{stage}.cmp_bc.eq", | |
| ) | |
| for stage in range(bits): | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.div.stage{stage}.mux.bit{bit}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.div.stage{stage}.mux.bit{bit}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.div.stage{stage}.mux.bit{bit}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.div.stage{stage}.mux.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| def add_bitwise_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit bitwise operation circuits (AND, OR, XOR, NOT). | |
| These are simply N copies of the 1-bit gates. | |
| """ | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.and.bit{bit}", [1.0, 1.0], [-2.0]) | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.or.bit{bit}", [1.0, 1.0], [-1.0]) | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.xor.bit{bit}.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.xor.bit{bit}.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.xor.bit{bit}.layer2", [1.0, 1.0], [-2.0]) | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.not.bit{bit}", [-1.0], [0.0]) | |
| def add_shift_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit shift circuits (SHL, SHR by 1 position). | |
| SHL: out[i] = in[i+1] for i<N-1, out[N-1] = 0 | |
| SHR: out[0] = 0, out[i] = in[i-1] for i>0 | |
| """ | |
| for bit in range(bits): | |
| if bit < bits - 1: | |
| add_gate(tensors, f"alu.alu{bits}bit.shl.bit{bit}", [1.0], [-1.0]) | |
| else: | |
| add_gate(tensors, f"alu.alu{bits}bit.shl.bit{bit}", [0.0], [-1.0]) | |
| for bit in range(bits): | |
| if bit > 0: | |
| add_gate(tensors, f"alu.alu{bits}bit.shr.bit{bit}", [1.0], [-1.0]) | |
| else: | |
| add_gate(tensors, f"alu.alu{bits}bit.shr.bit{bit}", [0.0], [-1.0]) | |
| def add_inc_dec_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit INC and DEC circuits.""" | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.inc.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.inc.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.inc.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.inc.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.dec.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.dec.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.dec.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.dec.bit{bit}.not_a", [-1.0], [0.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.dec.bit{bit}.borrow", [1.0, 1.0], [-2.0]) | |
| def add_neg_nbits(tensors: Dict[str, torch.Tensor], bits: int) -> None: | |
| """Add N-bit NEG circuit (two's complement negation).""" | |
| for bit in range(bits): | |
| add_gate(tensors, f"alu.alu{bits}bit.neg.not.bit{bit}", [-1.0], [0.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.neg.inc.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.neg.inc.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.neg.inc.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"alu.alu{bits}bit.neg.inc.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| def add_float16_core(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add float16 core circuits (unpack, pack, classify). | |
| IEEE 754 half-precision format (16 bits): | |
| - Bit 15: Sign (0=positive, 1=negative) | |
| - Bits 14-10: Exponent (5 bits, bias=15) | |
| - Bits 9-0: Mantissa/fraction (10 bits, implicit leading 1 for normalized) | |
| Special values: | |
| - Zero: exp=0, frac=0 | |
| - Subnormal: exp=0, frac≠0 | |
| - Infinity: exp=31, frac=0 | |
| - NaN: exp=31, frac≠0 | |
| """ | |
| prefix = "float16" | |
| # Identity buffers: H(x - 1) reproduces a binary input (bias 0 would | |
| # make the gate constant-1, since H(0) = 1). | |
| for i in range(16): | |
| add_gate(tensors, f"{prefix}.unpack.bit{i}", [1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.classify.exp_zero", [-1.0] * 5, [0.0]) | |
| add_gate(tensors, f"{prefix}.classify.exp_max", [1.0] * 5, [-5.0]) | |
| add_gate(tensors, f"{prefix}.classify.frac_zero", [-1.0] * 10, [0.0]) | |
| add_gate(tensors, f"{prefix}.classify.frac_nonzero", [1.0] * 10, [-1.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_zero.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_subnormal.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_inf.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_nan.and", [1.0, 1.0], [-2.0]) | |
| for i in range(16): | |
| add_gate(tensors, f"{prefix}.pack.bit{i}", [1.0], [-1.0]) | |
| def add_float_add(tensors: Dict[str, torch.Tensor], family: str, | |
| exp_bits: int, frac_bits: int) -> None: | |
| """Add a complete float addition/subtraction pipeline. | |
| Self-contained circuit; external inputs are the raw operand words | |
| $a[0..W-1] / $b[0..W-1] (MSB-first). Contract: exact IEEE specials | |
| (NaN, infinities, signed zeros, opposite-sign infinities -> NaN, exact | |
| cancellation -> +0), subnormal operands and gradual-underflow subnormal | |
| results, round-to-nearest-even for the mantissa. A zero operand passes the | |
| other operand through verbatim. | |
| Datapath: payload comparison picks the larger-magnitude operand, swap | |
| muxes route it to L; the smaller mantissa is aligned right through a | |
| capped barrel shifter over an F+3-bit round+guard frame with a sticky | |
| collector; add and subtract chains run in parallel (the subtract chain | |
| takes the sticky as a borrow-in so its round bit is exact) and the sign | |
| agreement selects; a leading-zero counter drives the left renormalize | |
| barrel; guard/round/sticky then drive a round-to-nearest-even increment | |
| on the fraction with a carry into the exponent; two E+2-bit exponent | |
| chains compute exp_L + 1 - nlz plus the rounding carry; and a one-hot | |
| specials network selects the output word. | |
| Rounding: round-to-nearest-even (RNE), matching multiply and divide. | |
| """ | |
| E, F = exp_bits, frac_bits | |
| P = E + F # payload width | |
| FRAME = F + 3 # mantissa + guard + round | |
| RES = F + 4 # result frame (carry bit) | |
| prefix = f"{family}.add" | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer2", [1.0, 1.0], [-2.0]) | |
| for op in ("a", "b"): | |
| add_gate(tensors, f"{prefix}.{op}.exp_zero", [-1.0] * E, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_nzero", [1.0] * E, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_max", [1.0] * E, [-float(E)]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_nz", [1.0] * F, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_zero", [-1.0] * F, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_nan", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_inf", [1.0, 1.0], [-2.0]) | |
| # True zero is exp==0 AND frac==0; a subnormal (exp==0, frac!=0) is a | |
| # nonzero datapath operand, not a zero. | |
| add_gate(tensors, f"{prefix}.{op}.is_zero", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.nonzero", [-1.0], [0.0]) | |
| # Effective exponent LSB for subnormals: a subnormal (exp==0) acts at | |
| # the smallest normal exponent (biased 1), so force the LSB when exp==0. | |
| add_gate(tensors, f"{prefix}.{op}.eexp_lsb", [1.0, 1.0], [-1.0]) | |
| # Payload magnitude comparison (chooses the larger-magnitude operand). | |
| add_bit_cascade_compare( | |
| tensors, | |
| cmp_prefix=f"{prefix}.pl_bc", | |
| bits=P, | |
| out_gt=f"{prefix}.pl.gt", | |
| out_lt=f"{prefix}.pl.lt", | |
| out_ge=f"{prefix}.pl.ge", | |
| out_le=f"{prefix}.pl.le", | |
| out_eq=f"{prefix}.pl.eq", | |
| ) | |
| # Swap muxes: sel = pl.lt (a smaller in magnitude -> L = b). | |
| add_gate(tensors, f"{prefix}.swap.not_sel", [-1.0], [0.0]) | |
| for name, width in (("sign_l", 1), ("exp_l", E), ("exp_s", E), | |
| ("mant_l", F), ("mant_s", F), ("impl_l", 1), ("impl_s", 1)): | |
| for k in range(width): | |
| g = f"{prefix}.swap.{name}.bit{k}" if width > 1 else f"{prefix}.swap.{name}" | |
| add_gate(tensors, f"{g}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{g}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{g}.or", [1.0, 1.0], [-1.0]) | |
| # exp_diff = exp_L - exp_S (never negative), E+1-bit two's complement. | |
| for k in range(E): | |
| add_gate(tensors, f"{prefix}.exp_nb.bit{k}", [-1.0], [0.0]) | |
| for k in range(E + 1): | |
| add_full_adder(tensors, f"{prefix}.exp_diff.fa{k}") | |
| # Alignment barrel shifter over the guard frame, with sticky collection | |
| # and a saturation flag for shifts past the frame. | |
| num_stages = 0 | |
| while (1 << num_stages) - 1 < FRAME: | |
| num_stages += 1 | |
| for j in range(num_stages): | |
| add_gate(tensors, f"{prefix}.align.s{j}.drop", [1.0] * (1 << j), [-1.0]) | |
| add_gate(tensors, f"{prefix}.align.s{j}.st_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.align.s{j}.sticky", [1.0, 1.0], [-1.0]) | |
| for k in range(FRAME): | |
| add_gate(tensors, f"{prefix}.align.s{j}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.align.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.align.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.align.s{j}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| sat_bits = E - num_stages | |
| if sat_bits == 1: | |
| add_gate(tensors, f"{prefix}.align.sat", [1.0], [-1.0]) | |
| else: | |
| add_gate(tensors, f"{prefix}.align.sat", [1.0] * sat_bits, [-1.0]) | |
| add_gate(tensors, f"{prefix}.align.not_sat", [-1.0], [0.0]) | |
| for k in range(FRAME): | |
| add_gate(tensors, f"{prefix}.align.out.bit{k}", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.align.sticky_final", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.align.not_sticky", [-1.0], [0.0]) | |
| # Parallel add and subtract over the frame, then select by sign parity. | |
| # The subtract chain's carry-in is NOT(sticky), which subtracts one extra | |
| # round-ULP when alignment dropped a nonzero tail, so the round bit is | |
| # exact for effective subtraction. | |
| for k in range(RES): | |
| add_full_adder(tensors, f"{prefix}.addp.fa{k}") | |
| for k in range(FRAME): | |
| add_gate(tensors, f"{prefix}.subp.not_s.bit{k}", [-1.0], [0.0]) | |
| add_full_adder(tensors, f"{prefix}.subp.fa{k}") | |
| add_gate(tensors, f"{prefix}.res.not_sub", [-1.0], [0.0]) | |
| for k in range(RES): | |
| add_gate(tensors, f"{prefix}.res.bit{k}.and_add", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.res.bit{k}.and_sub", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.res.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| # Leading-zero normalization: priority detect + left barrel shifter. | |
| for pos in range(1, RES): | |
| add_gate(tensors, f"{prefix}.lzc.any_higher{pos}", [1.0] * pos, [-1.0]) | |
| for pos in range(1, RES): | |
| add_gate(tensors, f"{prefix}.lzc.is_highest{pos}.not_higher", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.lzc.is_highest{pos}.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.lzc.nz", [1.0] * RES, [-1.0]) | |
| add_gate(tensors, f"{prefix}.lzc.not_nz", [-1.0], [0.0]) | |
| nlz_bits = 0 | |
| while (1 << nlz_bits) <= RES: | |
| nlz_bits += 1 | |
| for bbit in range(nlz_bits): | |
| fan = sum(1 for pos in range(1, RES) if (pos >> bbit) & 1) | |
| add_gate(tensors, f"{prefix}.lzc.nlz.bit{bbit}", [1.0] * max(1, fan), [-1.0]) | |
| for bbit in range(nlz_bits): | |
| for k in range(RES): | |
| add_gate(tensors, f"{prefix}.norm.s{bbit}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.norm.s{bbit}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.norm.s{bbit}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.norm.s{bbit}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| # Round-to-nearest-even: guard = shifted[2], round = shifted[1], and the | |
| # extra low bit shifted[0] plus the alignment sticky form the tail; the | |
| # fraction is incremented on G AND (LSB OR round OR low OR sticky), with | |
| # the carry-out bumping the exponent. | |
| add_gate(tensors, f"{prefix}.round.tail", [1.0, 1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.round.rsl", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.round.up", [1.0, 1.0], [-2.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.carry", [1.0, 1.0], [-2.0]) | |
| # exp_r = exp_L + 1 - nlz via two E+2-bit chains, then a rounding-carry | |
| # incrementer (exp_round). | |
| for bbit in range(nlz_bits): | |
| add_gate(tensors, f"{prefix}.exp_nnlz.bit{bbit}", [-1.0], [0.0]) | |
| for k in range(E + 2): | |
| add_full_adder(tensors, f"{prefix}.exp_s1.fa{k}") | |
| add_full_adder(tensors, f"{prefix}.exp_r.fa{k}") | |
| for k in range(E + 2): | |
| add_gate(tensors, f"{prefix}.exp_round.bit{k}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{k}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{k}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{k}.carry", [1.0, 1.0], [-2.0]) | |
| # Gradual underflow. exp_r <= 0 (pre-round) means the result is subnormal: | |
| # clamp the normalize shift to exp_L so it stays denormalized, and clamp the | |
| # exponent field to 0. The datapath's normal path then emits the subnormal. | |
| add_gate(tensors, f"{prefix}.er.zero", [-1.0] * (E + 2), [0.0]) | |
| add_gate(tensors, f"{prefix}.er.underflow", [1.0, 1.0], [-1.0]) # OR(sign, zero) | |
| add_gate(tensors, f"{prefix}.er.not_underflow", [-1.0], [0.0]) | |
| for k in range(E + 2): | |
| add_gate(tensors, f"{prefix}.exprc.bit{k}", [1.0, 1.0], [-2.0]) # AND(exp_r, not_uf) | |
| for bbit in range(nlz_bits): # nsh = min(nlz, exp_L) | |
| add_gate(tensors, f"{prefix}.nsh.bit{bbit}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.nsh.bit{bbit}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.nsh.bit{bbit}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.nsh.bit{bbit}.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.zero", [-1.0] * (E + 2), [0.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.underflow", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.not_neg", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.and_low", [1.0] * E, [-float(E)]) | |
| add_gate(tensors, f"{prefix}.exp_r.ge_emax", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.overflow", [1.0, 1.0], [-2.0]) | |
| # Specials selection, priority: NaN > operand infinities > both zero > | |
| # zero operand passthrough > datapath (cancel/underflow -> zero, | |
| # overflow -> inf, else normal). | |
| add_gate(tensors, f"{prefix}.sel.inf_opp", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.sel.inputs_nan", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.nan", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_ops", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_inf_ops", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_operand", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.both_zero", [1.0, 1.0, 1.0, 1.0], [-4.0]) | |
| add_gate(tensors, f"{prefix}.sel.a_zero_pass", [1.0, 1.0, 1.0, 1.0], [-4.0]) | |
| add_gate(tensors, f"{prefix}.sel.b_zero_pass", [1.0, 1.0, 1.0, 1.0], [-4.0]) | |
| add_gate(tensors, f"{prefix}.sel.dp_reach", [1.0, 1.0, 1.0, 1.0], [-4.0]) | |
| add_gate(tensors, f"{prefix}.sel.dp_zero_c", [1.0], [-1.0]) # true-zero result only | |
| add_gate(tensors, f"{prefix}.sel.not_dp_zero_c", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_ovf", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.dp_zero", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.dp_inf", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.sel.dp_norm", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_all", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_sign.a_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_sign.b_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_sign", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero_sign.and_signs", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.dp_zero_sign", [1.0, 1.0], [-2.0]) | |
| # Output word. | |
| add_gate(tensors, f"{prefix}.sign_out.inf", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sign_out.bz", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sign_out.az", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sign_out.bzp", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sign_out.dpz", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sign_out.dpi", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sign_out.dpn", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sign_out", [1.0] * 7, [-1.0]) | |
| for k in range(E): | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}.az", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}.bz", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}", [1.0] * 5, [-1.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}.az", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}.bz", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}", [1.0] * 4, [-1.0]) | |
| def add_float16_add(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float16 addition pipeline.""" | |
| add_float_add(tensors, "float16", 5, 10) | |
| def add_float_mul(tensors: Dict[str, torch.Tensor], family: str, | |
| exp_bits: int, frac_bits: int) -> None: | |
| """Add a complete float multiplication pipeline. | |
| Self-contained circuit; external inputs are the raw operand words | |
| $a[0..W-1] / $b[0..W-1] (MSB-first). Contract: exact IEEE specials | |
| (NaN, infinities, signed zeros), subnormal operands and gradual-underflow | |
| subnormal results, round-to-nearest-even for the mantissa. | |
| Datapath: (F+1)x(F+1) partial products accumulated shift-add into the | |
| 2F+2-bit product; a leading-zero count and an E+2-bit exponent chain give | |
| exp_base = exp_a + exp_b - bias and a signed clamp min(nlz, exp_base); the | |
| product is right-shifted by (F+1) - that clamp (one shifter that normalizes | |
| normal results, denormalizes subnormal ones, and drives deeply underflowing | |
| products to zero), then round-to-nearest-even and a one-hot specials network | |
| produce the output word. | |
| """ | |
| E, F = exp_bits, frac_bits | |
| prefix = f"{family}.mul" | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer2", [1.0, 1.0], [-2.0]) | |
| # Operand classification. Subnormals (exp==0, frac!=0) are nonzero datapath | |
| # operands with implicit bit 0 and effective exponent 1. | |
| for op in ("a", "b"): | |
| add_gate(tensors, f"{prefix}.{op}.exp_zero", [-1.0] * E, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_nzero", [1.0] * E, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_max", [1.0] * E, [-float(E)]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_nz", [1.0] * F, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_zero", [-1.0] * F, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_nan", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_inf", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_zero", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.nonzero", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.eexp_lsb", [1.0, 1.0], [-1.0]) | |
| # exp_add: exp_a + exp_b in E+1 bits. | |
| for bit in range(E + 1): | |
| add_full_adder(tensors, f"{prefix}.exp_add.fa{bit}") | |
| # Mantissa partial products, LSB-first indices; index F is the implicit 1. | |
| for i in range(F + 1): | |
| for j in range(F + 1): | |
| add_gate(tensors, f"{prefix}.mant_mul.pp.a{i}b{j}", [1.0, 1.0], [-2.0]) | |
| # Shift-add accumulation: stage t (gate name s{t-1}) adds row t. | |
| for stage in range(F): | |
| for bit in range(2 * F + 2): | |
| add_full_adder(tensors, f"{prefix}.mant_mul.acc.s{stage}.fa{bit}") | |
| # exp_base = exp_a + exp_b - bias (E+2-bit signed). Right-shift the product | |
| # by rshift = (F+1) - min(nlz, exp_base): places the mantissa for normal | |
| # results, denormalizes for subnormal ones; a deeply negative exp_base makes | |
| # rshift exceed the product width and the result becomes zero. | |
| RESM = 2 * F + 2 | |
| nlzb = 0 | |
| while (1 << nlzb) <= RESM: | |
| nlzb += 1 | |
| RB = 0 | |
| while (1 << RB) < RESM: | |
| RB += 1 | |
| for bit in range(E + 2): | |
| add_full_adder(tensors, f"{prefix}.expb.fa{bit}") | |
| for pos in range(1, RESM): | |
| add_gate(tensors, f"{prefix}.lzc.any_higher{pos}", [1.0] * pos, [-1.0]) | |
| add_gate(tensors, f"{prefix}.lzc.is_highest{pos}.not_higher", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.lzc.is_highest{pos}.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.lzc.nz", [1.0] * RESM, [-1.0]) | |
| add_gate(tensors, f"{prefix}.lzc.not_nz", [-1.0], [0.0]) | |
| for bbit in range(nlzb): | |
| fan = sum(1 for pos in range(1, RESM) if (pos >> bbit) & 1) | |
| add_gate(tensors, f"{prefix}.lzc.nlz.bit{bbit}", [1.0] * max(1, fan), [-1.0]) | |
| for bbit in range(nlzb): | |
| add_gate(tensors, f"{prefix}.exp_nnlz.bit{bbit}", [-1.0], [0.0]) | |
| for k in range(E + 2): | |
| add_full_adder(tensors, f"{prefix}.exp_s1.fa{k}") # exp_base - nlz | |
| add_full_adder(tensors, f"{prefix}.exp_r.fa{k}") # + 1 | |
| add_gate(tensors, f"{prefix}.er.zero", [-1.0] * (E + 2), [0.0]) | |
| add_gate(tensors, f"{prefix}.er.underflow", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.er.not_underflow", [-1.0], [0.0]) | |
| for k in range(E + 2): | |
| add_gate(tensors, f"{prefix}.exprc.bit{k}", [1.0, 1.0], [-2.0]) # er clamped >= 0 | |
| for k in range(E + 2): # nsh = min(nlz, exp_base) | |
| add_gate(tensors, f"{prefix}.nsh.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.nsh.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.nsh.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.nsh.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| for k in range(E + 2): | |
| add_gate(tensors, f"{prefix}.nshc.bit{k}", [-1.0], [0.0]) # ~nsh | |
| add_full_adder(tensors, f"{prefix}.rsh.fa{k}") # rsh = F + ~nsh + 1 = F - nsh | |
| add_gate(tensors, f"{prefix}.too_deep", [1.0] * (E + 2 - RB), [-1.0]) | |
| add_gate(tensors, f"{prefix}.not_deep", [-1.0], [0.0]) | |
| for j in range(RB): # right-barrel by rshift | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.drop", [1.0] * (1 << j), [-1.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.st_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.sticky", [1.0, 1.0], [-1.0]) | |
| for k in range(RESM + 1): # product widened by 1 low bit | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.sig.bit{k}", [1.0, 1.0], [-2.0]) # shifted[k+1] AND not_deep | |
| add_gate(tensors, f"{prefix}.round.guard", [1.0, 1.0], [-2.0]) # shifted[0] AND not_deep | |
| add_gate(tensors, f"{prefix}.deep_nz", [1.0, 1.0], [-2.0]) # too_deep AND product nonzero | |
| add_gate(tensors, f"{prefix}.round.sticky", [1.0, 1.0], [-1.0]) # barrel sticky OR deep_nz | |
| add_gate(tensors, f"{prefix}.round.rsl", [1.0, 1.0], [-1.0]) # sticky OR lsb | |
| add_gate(tensors, f"{prefix}.round.up", [1.0, 1.0], [-2.0]) # guard AND rsl | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.carry", [1.0, 1.0], [-2.0]) | |
| for bit in range(E + 2): | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.zero", [-1.0] * (E + 2), [0.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.not_neg", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.and_low", [1.0] * E, [-float(E)]) | |
| add_gate(tensors, f"{prefix}.exp_r.ge_emax", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.overflow", [1.0, 1.0], [-2.0]) | |
| # Specials selection (one-hot priority: NaN > Inf/overflow > zero > normal). | |
| add_gate(tensors, f"{prefix}.sel.inputs_nan", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_zero1", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_zero2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_zero", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.nan", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_in", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero_in", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_or_ovf", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_inf_path", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero_or_unf", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_zero_unf", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.sel.norm", [1.0, 1.0, 1.0], [-3.0]) | |
| # Output word: sign, exponent, fraction. | |
| add_gate(tensors, f"{prefix}.sign_out", [1.0, 1.0], [-2.0]) | |
| for k in range(E): | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}", [1.0, 1.0, 1.0], [-1.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}", [1.0, 1.0], [-1.0]) | |
| def add_float16_mul(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float16 multiplication pipeline.""" | |
| add_float_mul(tensors, "float16", 5, 10) | |
| def add_float_div(tensors: Dict[str, torch.Tensor], family: str, | |
| exp_bits: int, frac_bits: int) -> None: | |
| """Add a complete float division pipeline. | |
| Self-contained circuit; external inputs are the raw operand words | |
| $a[0..W-1] / $b[0..W-1] (MSB-first). Contract: exact IEEE specials | |
| (NaN, infinities, signed zeros, x/0 -> inf, 0/0 and inf/inf -> NaN), | |
| subnormal operands and gradual-underflow subnormal results, | |
| round-to-nearest-even for the mantissa. | |
| Datapath: each operand mantissa is pre-normalized (a leading-zero count | |
| drives a left-barrel so the leading 1 sits at bit F, and na = eexp - lz); | |
| F+3 restoring-division stages produce the quotient; a one-bit normalize mux | |
| aligns it on the integer quotient bit; two E+2-bit exponent chains give | |
| er = na - nb + bias - 1 + q0; the aligned mantissa frame is right-shifted by | |
| max(0, 1 - er) to denormalize subnormal quotients; then round-to-nearest-even | |
| and a one-hot specials network produce the output word. | |
| """ | |
| E, F = exp_bits, frac_bits | |
| prefix = f"{family}.div" | |
| NSTAGE = F + 3 | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer2", [1.0, 1.0], [-2.0]) | |
| for op in ("a", "b"): | |
| add_gate(tensors, f"{prefix}.{op}.exp_zero", [-1.0] * E, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_nzero", [1.0] * E, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_max", [1.0] * E, [-float(E)]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_nz", [1.0] * F, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_zero", [-1.0] * F, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_nan", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_inf", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_zero", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.nonzero", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.eexp_lsb", [1.0, 1.0], [-1.0]) | |
| # Pre-normalize each operand mantissa: a leading-zero count over the F+1-bit | |
| # mantissa drives a left-barrel so the leading 1 sits at bit F; na = eexp - lz. | |
| ML = F + 1 | |
| mlzb = 0 | |
| while (1 << mlzb) <= ML: | |
| mlzb += 1 | |
| for op in ("a", "b"): | |
| for pos in range(1, ML): | |
| add_gate(tensors, f"{prefix}.{op}.pn.any_higher{pos}", [1.0] * pos, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.is_highest{pos}.not_higher", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.is_highest{pos}.and", [1.0, 1.0], [-2.0]) | |
| for bbit in range(mlzb): | |
| fan = sum(1 for pos in range(1, ML) if (pos >> bbit) & 1) | |
| add_gate(tensors, f"{prefix}.{op}.pn.lz.bit{bbit}", [1.0] * max(1, fan), [-1.0]) | |
| for bbit in range(mlzb): | |
| for k in range(ML): | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| for bbit in range(mlzb): | |
| add_gate(tensors, f"{prefix}.{op}.nlz.bit{bbit}", [-1.0], [0.0]) | |
| for bit in range(E + 2): | |
| add_full_adder(tensors, f"{prefix}.{op}.neff.fa{bit}") | |
| # Restoring division stages: per stage a bit-cascade GE comparator, the | |
| # quotient-bit OR (shifted-out top bit forces GE), a conditional | |
| # subtract, and the remainder mux. | |
| for stage in range(NSTAGE): | |
| add_bit_cascade_compare( | |
| tensors, | |
| cmp_prefix=f"{prefix}.mant_div.stage{stage}.cmp_bc", | |
| bits=F + 1, | |
| out_gt=f"{prefix}.mant_div.stage{stage}.cmp_bc.gt", | |
| out_lt=f"{prefix}.mant_div.stage{stage}.cmp_bc.lt", | |
| out_ge=f"{prefix}.mant_div.stage{stage}.cmp", | |
| out_le=f"{prefix}.mant_div.stage{stage}.cmp_bc.le", | |
| out_eq=f"{prefix}.mant_div.stage{stage}.cmp_bc.eq", | |
| ) | |
| add_gate(tensors, f"{prefix}.mant_div.stage{stage}.q", [1.0, 1.0], [-1.0]) | |
| for bit in range(F + 1): | |
| add_gate(tensors, f"{prefix}.mant_div.stage{stage}.sub.not_d.bit{bit}", [-1.0], [0.0]) | |
| add_full_adder(tensors, f"{prefix}.mant_div.stage{stage}.sub.fa{bit}") | |
| add_gate(tensors, f"{prefix}.mant_div.stage{stage}.mux.bit{bit}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.mant_div.stage{stage}.mux.bit{bit}.and_old", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.mant_div.stage{stage}.mux.bit{bit}.and_new", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.mant_div.stage{stage}.mux.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| # Normalize by one on the integer quotient bit q0. | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.norm.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.norm.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.norm.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.norm.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| # Round-to-nearest-even: guard is muxed on q0; sticky ORs the last | |
| # remainder plus the spilled quotient bit; round-up increments the frac. | |
| add_gate(tensors, f"{prefix}.round.g.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.round.g.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.round.g.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.round.g.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.round.rem_nz", [1.0] * (F + 1), [-1.0]) | |
| add_gate(tensors, f"{prefix}.round.q_extra", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.round.sticky_orig", [1.0, 1.0], [-1.0]) # rem_nz OR q_extra | |
| # exp_r = na - nb + bias - 1 + q0 (E+2-bit two's complement) using the | |
| # pre-normalized effective exponents na = a.neff, nb = b.neff. | |
| for bit in range(E + 2): | |
| add_gate(tensors, f"{prefix}.exp_nb.bit{bit}", [-1.0], [0.0]) # ~nb | |
| for bit in range(E + 2): | |
| add_full_adder(tensors, f"{prefix}.exp_s1.fa{bit}") # na + ~nb + q0 | |
| add_full_adder(tensors, f"{prefix}.exp_r.fa{bit}") # + bias | |
| # Gradual underflow: right-shift the aligned mantissa frame MF (bit 0 guard, | |
| # bits 1..F frac, bit F+1 implicit 1) by rsh = max(0, 1 - er). | |
| RBd = 0 | |
| while (1 << RBd) < (F + 2): | |
| RBd += 1 | |
| add_gate(tensors, f"{prefix}.er.zero", [-1.0] * (E + 2), [0.0]) | |
| add_gate(tensors, f"{prefix}.er.underflow", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.er.not_underflow", [-1.0], [0.0]) | |
| for k in range(E + 2): | |
| add_gate(tensors, f"{prefix}.exprc.bit{k}", [1.0, 1.0], [-2.0]) # er clamped >= 0 | |
| add_gate(tensors, f"{prefix}.ernot.bit{k}", [-1.0], [0.0]) # ~er | |
| add_full_adder(tensors, f"{prefix}.subv.fa{k}") # 1 - er | |
| add_gate(tensors, f"{prefix}.rsh.bit{k}", [1.0, 1.0], [-2.0]) # (1-er) AND underflow | |
| add_gate(tensors, f"{prefix}.too_deep", [1.0] * (E + 2 - RBd), [-1.0]) | |
| add_gate(tensors, f"{prefix}.not_deep", [-1.0], [0.0]) | |
| for j in range(RBd): | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.drop", [1.0] * (1 << j), [-1.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.st_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.sticky", [1.0, 1.0], [-1.0]) | |
| for k in range(F + 2): | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rbar.s{j}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.sig.bit{k}", [1.0, 1.0], [-2.0]) # shifted[k+1] AND not_deep | |
| add_gate(tensors, f"{prefix}.round.guard", [1.0, 1.0], [-2.0]) # shifted[0] AND not_deep | |
| add_gate(tensors, f"{prefix}.round.sticky", [1.0, 1.0, 1.0], [-1.0]) # barrel OR orig OR too_deep | |
| add_gate(tensors, f"{prefix}.round.rsl", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.round.up", [1.0, 1.0], [-2.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.carry", [1.0, 1.0], [-2.0]) | |
| for bit in range(E + 2): | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.zero", [-1.0] * (E + 2), [0.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.not_neg", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.and_low", [1.0] * E, [-float(E)]) | |
| add_gate(tensors, f"{prefix}.exp_r.ge_emax", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_r.overflow", [1.0, 1.0], [-2.0]) | |
| # Specials selection (one-hot priority: NaN > Inf/overflow > zero > normal). | |
| add_gate(tensors, f"{prefix}.sel.inputs_nan", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero_zero", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_inf", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.nan_cases", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.nan", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_in", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero_in", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf_or_ovf", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_inf_path", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.inf", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero_or_unf", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sel.not_zero_unf", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.sel.zero", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.sel.norm", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.sign_out", [1.0, 1.0], [-2.0]) | |
| for k in range(E): | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}", [1.0, 1.0, 1.0], [-1.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}", [1.0, 1.0], [-1.0]) | |
| def add_float16_div(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float16 division pipeline.""" | |
| add_float_div(tensors, "float16", 5, 10) | |
| def add_float_fma(tensors: Dict[str, torch.Tensor], family: str, | |
| exp_bits: int, frac_bits: int) -> None: | |
| """Fused multiply-add: round(a*b + c) with a SINGLE rounding (F-extension). | |
| Self-contained; external inputs are the raw operand words $a/$b/$c | |
| (MSB-first). Contract: exact IEEE specials (NaN, infinities, inf*0 and | |
| inf-inf -> NaN, signed zeros), subnormal operands and gradual-underflow | |
| subnormal results, round-to-nearest-even, single rounding of a*b+c. | |
| Datapath: each mantissa is pre-normalized (leading 1 at bit F, na=eexp-lz); | |
| the (F+1)x(F+1) product Mp (2F+2 bits) and the addend Mc are right-shifted by | |
| sticky barrel shifters into a common 3(F+1)+4-bit field anchored at | |
| max(product,c) exponent; a signed magnitude add (with a borrow when the | |
| shifted-out subtrahend leaves a tail) forms the sum; a leading-zero count and | |
| normalize give the significand, one round-to-nearest-even rounds it, and a | |
| one-hot specials network packs the result. | |
| """ | |
| E, F = exp_bits, frac_bits | |
| W = 1 + E + F | |
| p = F + 1 | |
| PW = 2 * p # product width | |
| WF = 3 * p + 4 # alignment field width | |
| prefix = f"{family}.fma" | |
| bias = (1 << (E - 1)) - 1 | |
| ML = F + 1 | |
| mlzb = 0 | |
| while (1 << mlzb) <= ML: | |
| mlzb += 1 | |
| SB = 0 # bits to hold a shift amount up to WF | |
| while (1 << SB) <= WF: | |
| SB += 1 | |
| NB = 0 # bits for the field leading-zero count | |
| while (1 << NB) <= WF: | |
| NB += 1 | |
| # sign of the product sp = sa ^ sb (c's sign is c(0) directly). | |
| add_gate(tensors, f"{prefix}.sign_p.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sign_p.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.sign_p.layer2", [1.0, 1.0], [-2.0]) | |
| # operand classification. | |
| for op in ("a", "b", "c"): | |
| add_gate(tensors, f"{prefix}.{op}.exp_zero", [-1.0] * E, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_nzero", [1.0] * E, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.exp_max", [1.0] * E, [-float(E)]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_nz", [1.0] * F, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.frac_zero", [-1.0] * F, [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_nan", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_inf", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.is_zero", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.nonzero", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.eexp_lsb", [1.0, 1.0], [-1.0]) | |
| # pre-normalize each mantissa: leading 1 to bit F, na = eexp - lz. | |
| for op in ("a", "b", "c"): | |
| for pos in range(1, ML): | |
| add_gate(tensors, f"{prefix}.{op}.pn.any_higher{pos}", [1.0] * pos, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.is_highest{pos}.not_higher", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.is_highest{pos}.and", [1.0, 1.0], [-2.0]) | |
| for bbit in range(mlzb): | |
| fan = sum(1 for pos in range(1, ML) if (pos >> bbit) & 1) | |
| add_gate(tensors, f"{prefix}.{op}.pn.lz.bit{bbit}", [1.0] * max(1, fan), [-1.0]) | |
| for bbit in range(mlzb): | |
| for k in range(ML): | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{op}.pn.s{bbit}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| for bbit in range(mlzb): | |
| add_gate(tensors, f"{prefix}.{op}.nlz.bit{bbit}", [-1.0], [0.0]) | |
| for bit in range(E + 2): | |
| add_full_adder(tensors, f"{prefix}.{op}.neff.fa{bit}") | |
| # product Mp = Ma * Mb (PW bits) by shift-add over the pre-normalized mantissas. | |
| for i in range(p): | |
| for j in range(p): | |
| add_gate(tensors, f"{prefix}.pp.a{i}b{j}", [1.0, 1.0], [-2.0]) | |
| for stage in range(F): | |
| for bit in range(PW): | |
| add_full_adder(tensors, f"{prefix}.acc.s{stage}.fa{bit}") | |
| # exponent alignment (E+3-bit two's complement). q1 = na+nb - bias + 1 (product | |
| # MSB exponent); q2 = nc; d = q1 - q2; neg_d = sign(d); |d|; sbig = |d| + 1. | |
| # rp = neg_d ? sbig : 1, rc = neg_d ? 1 : sbig; A = max(q1,q2)+1 = neg_d ? q2+1 : q1+1. | |
| for bit in range(E + 3): | |
| add_full_adder(tensors, f"{prefix}.nab.fa{bit}") # na + nb | |
| for bit in range(E + 3): | |
| add_full_adder(tensors, f"{prefix}.q1.fa{bit}") # nab + (1 - bias) | |
| # zero operands carry no exponent; force their q to a sentinel negative value | |
| # (sign bit set, rest 0) so max(q1,q2) picks the nonzero operand. | |
| add_gate(tensors, f"{prefix}.prod_zero", [1.0, 1.0], [-1.0]) # a_zero | b_zero | |
| add_gate(tensors, f"{prefix}.not_prod_zero", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.not_c_zero", [-1.0], [0.0]) | |
| for bit in range(E + 3): | |
| if bit in (E + 1, E + 2): # sentinel -2^(E+1) | |
| add_gate(tensors, f"{prefix}.q1e.bit{bit}", [1.0, 1.0], [-1.0]) # prod_zero | q1 | |
| add_gate(tensors, f"{prefix}.q2e.bit{bit}", [1.0, 1.0], [-1.0]) # c_zero | q2 | |
| else: | |
| add_gate(tensors, f"{prefix}.q1e.bit{bit}", [1.0, 1.0], [-2.0]) # ~prod_zero & q1 | |
| add_gate(tensors, f"{prefix}.q2e.bit{bit}", [1.0, 1.0], [-2.0]) # ~c_zero & q2 | |
| for bit in range(E + 3): | |
| add_gate(tensors, f"{prefix}.ncnot.bit{bit}", [-1.0], [0.0]) # ~q2e | |
| add_full_adder(tensors, f"{prefix}.d.fa{bit}") # q1e + ~q2e + 1 = q1e - q2e | |
| add_gate(tensors, f"{prefix}.not_neg_d", [-1.0], [0.0]) # ~sign(d) | |
| for bit in range(E + 3): | |
| add_gate(tensors, f"{prefix}.dxor.bit{bit}.a", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.dxor.bit{bit}.b", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.dxor.bit{bit}", [1.0, 1.0], [-2.0]) # d ^ neg_d | |
| add_full_adder(tensors, f"{prefix}.dabs.fa{bit}") # dxor + neg_d = |d| | |
| for bit in range(E + 3): | |
| add_full_adder(tensors, f"{prefix}.sbig.fa{bit}") # |d| + 1 | |
| for bit in range(SB): | |
| add_gate(tensors, f"{prefix}.rp.bit{bit}.and_big", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rp.bit{bit}.and_one", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rp.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.rc.bit{bit}.and_big", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rc.bit{bit}.and_one", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rc.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sbig.high", [1.0] * (E + 3 - SB), [-1.0]) # sbig >= 2^SB | |
| add_gate(tensors, f"{prefix}.rp.deep", [1.0, 1.0], [-2.0]) # neg_d & sbig.high | |
| add_gate(tensors, f"{prefix}.rc.deep", [1.0, 1.0], [-2.0]) # ~neg_d & sbig.high | |
| for bit in range(E + 3): | |
| add_full_adder(tensors, f"{prefix}.q1p.fa{bit}") # q1 + 1 | |
| add_full_adder(tensors, f"{prefix}.q2p.fa{bit}") # nc + 1 | |
| add_gate(tensors, f"{prefix}.atop.bit{bit}.and_p", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.atop.bit{bit}.and_c", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.atop.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| # two sticky right-barrel shifters into the WF-bit field. | |
| for tag in ("pf", "cf"): | |
| for j in range(SB): | |
| add_gate(tensors, f"{prefix}.{tag}.s{j}.drop", [1.0] * (1 << j), [-1.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.s{j}.st_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.s{j}.sticky", [1.0, 1.0], [-1.0]) | |
| for k in range(WF): | |
| add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.s{j}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.not_deep", [-1.0], [0.0]) | |
| for k in range(WF): # field masked to 0 when deep | |
| add_gate(tensors, f"{prefix}.{tag}.field.bit{k}", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.nz", [1.0] * WF, [-1.0]) | |
| add_gate(tensors, f"{prefix}.{tag}.deep_nz", [1.0, 1.0], [-2.0]) # deep & operand nonzero | |
| add_gate(tensors, f"{prefix}.{tag}.sticky", [1.0, 1.0], [-1.0]) # barrel | deep_nz | |
| # signed magnitude combine. eff_sub = sp ^ sc. | |
| add_gate(tensors, f"{prefix}.eff_sub.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.eff_sub.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.eff_sub", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.eff_add", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.s_tail", [1.0, 1.0], [-1.0]) # pf.sticky | cf.sticky (shifted-out) | |
| add_gate(tensors, f"{prefix}.not_s_tail", [-1.0], [0.0]) | |
| # sub path: D = PF - CF (WF+1 bits); ge = PF >= CF = ~sign(D); borrow for a tail. | |
| for bit in range(WF): | |
| add_gate(tensors, f"{prefix}.cfnot.bit{bit}", [-1.0], [0.0]) | |
| for bit in range(WF + 1): | |
| add_full_adder(tensors, f"{prefix}.dsum.fa{bit}") # PF + ~CF + cin | |
| add_gate(tensors, f"{prefix}.ge", [-1.0], [0.0]) # ~sign(D) | |
| add_gate(tensors, f"{prefix}.not_ge", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.borrow_in", [1.0, 1.0], [-2.0]) # eff_sub & ~s_tail -> cin=1 | |
| for bit in range(WF + 1): # dmagx = D ^ sign(D) | |
| add_gate(tensors, f"{prefix}.dmagx.bit{bit}.a", [1.0, 1.0], [-1.0]) # OR | |
| add_gate(tensors, f"{prefix}.dmagx.bit{bit}.b", [-1.0, -1.0], [1.0]) # NAND | |
| add_gate(tensors, f"{prefix}.dmagx.bit{bit}", [1.0, 1.0], [-2.0]) # AND(or, nand) = XOR | |
| for bit in range(WF + 1): | |
| add_full_adder(tensors, f"{prefix}.dmag.fa{bit}") # + (~ge as +1 for complement) | |
| # add path: A = PF + CF (WF+1 bits). | |
| for bit in range(WF + 1): | |
| add_full_adder(tensors, f"{prefix}.asum.fa{bit}") | |
| # mag = eff_sub ? dmag : asum (WF+1 bits) | |
| for bit in range(WF + 1): | |
| add_gate(tensors, f"{prefix}.mag.bit{bit}.and_sub", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.mag.bit{bit}.and_add", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.mag.bit{bit}.or", [1.0, 1.0], [-1.0]) | |
| # leading-zero count over mag (WF+1 bits) and normalize. | |
| MG = WF + 1 | |
| for pos in range(1, MG): | |
| add_gate(tensors, f"{prefix}.lz.any_higher{pos}", [1.0] * pos, [-1.0]) | |
| add_gate(tensors, f"{prefix}.lz.is_highest{pos}.not_higher", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.lz.is_highest{pos}.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.mag.nz", [1.0] * MG, [-1.0]) | |
| for bbit in range(NB): | |
| fan = sum(1 for pos in range(1, MG) if (pos >> bbit) & 1) | |
| add_gate(tensors, f"{prefix}.lz.bit{bbit}", [1.0] * max(1, fan), [-1.0]) | |
| for j in range(NB): # left-normalize barrel | |
| for k in range(MG): | |
| add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.nrm.s{j}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| # er = A - lz (biased result exponent), E+3-bit. | |
| for bit in range(E + 3): | |
| add_gate(tensors, f"{prefix}.nlzf.bit{bit}", [-1.0], [0.0]) | |
| add_full_adder(tensors, f"{prefix}.er.fa{bit}") | |
| add_gate(tensors, f"{prefix}.er.underflow", [1.0, 1.0], [-1.0]) # er <= 0 | |
| add_gate(tensors, f"{prefix}.er.zero", [-1.0] * (E + 3), [0.0]) | |
| add_gate(tensors, f"{prefix}.er.not_underflow", [-1.0], [0.0]) | |
| for bit in range(E + 3): | |
| add_gate(tensors, f"{prefix}.exprc.bit{bit}", [1.0, 1.0], [-2.0]) | |
| # subnormal-aware round. Build the mantissa frame from the normalized | |
| # significand (bit 0 guard, bits 1..F frac, bit F+1 implicit), right-shift it | |
| # by max(0,1-er) for gradual underflow, then round-to-nearest-even once. | |
| add_gate(tensors, f"{prefix}.round.sticky_lo", [1.0] * (MG - (F + 2)), [-1.0]) | |
| add_gate(tensors, f"{prefix}.round.sticky_pre", [1.0, 1.0], [-1.0]) # sticky_lo | s_tail | |
| RBd = 0 | |
| while (1 << RBd) < (F + 2): | |
| RBd += 1 | |
| for bit in range(E + 3): | |
| add_gate(tensors, f"{prefix}.ernot.bit{bit}", [-1.0], [0.0]) | |
| add_full_adder(tensors, f"{prefix}.subv.fa{bit}") # 1 - er | |
| add_gate(tensors, f"{prefix}.rsh.bit{bit}", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.too_deep", [1.0] * (E + 3 - RBd), [-1.0]) | |
| add_gate(tensors, f"{prefix}.not_deep", [-1.0], [0.0]) | |
| for j in range(RBd): | |
| add_gate(tensors, f"{prefix}.dsh.s{j}.drop", [1.0] * (1 << j), [-1.0]) | |
| add_gate(tensors, f"{prefix}.dsh.s{j}.st_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.dsh.s{j}.sticky", [1.0, 1.0], [-1.0]) | |
| for k in range(F + 2): | |
| add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.dsh.s{j}.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.sig.bit{k}", [1.0, 1.0], [-2.0]) # shifted(k+1) & not_deep | |
| add_gate(tensors, f"{prefix}.round.guard", [1.0, 1.0], [-2.0]) # shifted(0) & not_deep | |
| add_gate(tensors, f"{prefix}.round.sticky", [1.0, 1.0, 1.0], [-1.0]) # pre | barrel | too_deep | |
| add_gate(tensors, f"{prefix}.round.rsl", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.round.up", [1.0, 1.0], [-2.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.rnd.bit{k}.carry", [1.0, 1.0], [-2.0]) | |
| for bit in range(E + 3): | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.xor.layer2", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_round.bit{bit}.carry", [1.0, 1.0], [-2.0]) | |
| # specials + range detectors + output pack (priority NaN > Inf > Zero > Normal). | |
| add_gate(tensors, f"{prefix}.p_inf", [1.0, 1.0], [-1.0]) # a_inf | b_inf | |
| add_gate(tensors, f"{prefix}.not_p_inf", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.inf0a", [1.0, 1.0], [-2.0]) # a_inf & b_zero | |
| add_gate(tensors, f"{prefix}.inf0b", [1.0, 1.0], [-2.0]) # b_inf & a_zero | |
| add_gate(tensors, f"{prefix}.inf0", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.pc_sdiff.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.pc_sdiff.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.pc_sdiff", [1.0, 1.0], [-2.0]) # sp ^ sc | |
| add_gate(tensors, f"{prefix}.infinf_nan", [1.0, 1.0, 1.0], [-3.0]) # p_inf & c_inf & sdiff | |
| add_gate(tensors, f"{prefix}.in_nan", [1.0, 1.0, 1.0], [-1.0]) # a/b/c nan | |
| add_gate(tensors, f"{prefix}.nan", [1.0, 1.0, 1.0], [-1.0]) # in_nan | inf0 | infinf_nan | |
| add_gate(tensors, f"{prefix}.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.any_inf", [1.0, 1.0], [-1.0]) # p_inf | c_inf | |
| add_gate(tensors, f"{prefix}.not_any_inf", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.er.and_low", [1.0] * E, [-float(E)]) # exp_round low E bits all 1 | |
| add_gate(tensors, f"{prefix}.er.hi", [1.0, 1.0, 1.0], [-1.0]) # exp_round bit E/E+1/E+2 set | |
| add_gate(tensors, f"{prefix}.er.ge_emax", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.overflow", [1.0, 1.0], [-2.0]) # ge_emax & not_underflow | |
| add_gate(tensors, f"{prefix}.inf_or_ovf", [1.0, 1.0], [-1.0]) # any_inf | overflow | |
| add_gate(tensors, f"{prefix}.not_inf_path", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.inf", [1.0, 1.0], [-2.0]) # not_nan & inf_or_ovf | |
| add_gate(tensors, f"{prefix}.res_zero", [-1.0] * MG, [0.0]) # mag == 0 | |
| add_gate(tensors, f"{prefix}.not_res_zero", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.zero", [1.0, 1.0, 1.0], [-3.0]) # not_nan & not_inf_path & res_zero | |
| add_gate(tensors, f"{prefix}.norm", [1.0, 1.0, 1.0], [-3.0]) # not_nan & not_inf_path & ~res_zero | |
| # finite-path magnitude sign: eff_sub ? (ge?sp:sc) : sp. | |
| add_gate(tensors, f"{prefix}.fin_sign.sub.and_ge", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.fin_sign.sub.and_lt", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.fin_sign.sub", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.fin_sign.and_sub", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.fin_sign.and_add", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.fin_sign", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.p_zero", [1.0, 1.0], [-1.0]) # a_zero | b_zero | |
| add_gate(tensors, f"{prefix}.bothzero", [1.0, 1.0], [-2.0]) # p_zero & c_zero | |
| add_gate(tensors, f"{prefix}.negzero", [1.0, 1.0, 1.0], [-3.0]) # bothzero & sp & sc | |
| add_gate(tensors, f"{prefix}.sign_z.fin", [1.0, 1.0], [-2.0]) # fin_sign & ~res_zero | |
| add_gate(tensors, f"{prefix}.sign_z", [1.0, 1.0], [-1.0]) # sign_z.fin | negzero | |
| add_gate(tensors, f"{prefix}.sign.term_p", [1.0, 1.0], [-2.0]) # p_inf & sp | |
| add_gate(tensors, f"{prefix}.sign.term_c", [1.0, 1.0, 1.0], [-3.0]) # ~p_inf & c_inf & sc | |
| add_gate(tensors, f"{prefix}.sign.term_z", [1.0, 1.0], [-2.0]) # ~any_inf & sign_z | |
| add_gate(tensors, f"{prefix}.sign_pre", [1.0, 1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sign_out", [1.0, 1.0], [-2.0]) # sign_pre & not_nan | |
| for k in range(E): | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.exp_out.bit{k}", [1.0, 1.0, 1.0], [-1.0]) | |
| for k in range(F): | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}.norm_and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.frac_out.bit{k}", [1.0, 1.0], [-1.0]) | |
| def add_float_cmp(tensors: Dict[str, torch.Tensor], family: str, | |
| exp_bits: int, frac_bits: int) -> None: | |
| """Add a complete IEEE 754 comparison network (EQ, LT, LE, GT, GE). | |
| Self-contained circuit: external inputs are the raw operand words | |
| $a[0..W-1], $b[0..W-1] (MSB-first; bit 0 is the sign, bits 1..E the | |
| exponent, the rest the fraction). Composition: | |
| 1. NaN detection gates every result (any comparison with NaN is false). | |
| 2. both_zero makes +0 == -0 (and suppresses the mixed-sign LT/GT). | |
| 3. Same signs: IEEE encodings order like sign-magnitude integers, so the | |
| payload magnitude comparator decides, inverted for negatives. | |
| 4. Different signs: the negative operand is smaller (unless both zero). | |
| """ | |
| prefix = f"{family}.cmp" | |
| payload = exp_bits + frac_bits | |
| add_gate(tensors, f"{prefix}.a.exp_max", [1.0] * exp_bits, [-float(exp_bits)]) | |
| add_gate(tensors, f"{prefix}.a.frac_nz", [1.0] * frac_bits, [-1.0]) | |
| add_gate(tensors, f"{prefix}.a.is_nan", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.b.exp_max", [1.0] * exp_bits, [-float(exp_bits)]) | |
| add_gate(tensors, f"{prefix}.b.frac_nz", [1.0] * frac_bits, [-1.0]) | |
| add_gate(tensors, f"{prefix}.b.is_nan", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.either_nan", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer1.nand", [-1.0, -1.0], [1.0]) | |
| add_gate(tensors, f"{prefix}.sign_xor.layer2", [1.0, 1.0], [-2.0]) | |
| # XNOR of the signs, plus the sign complements the same-sign branches need. | |
| add_gate(tensors, f"{prefix}.same_sign.layer1.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.same_sign.layer1.nor", [-1.0, -1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.same_sign", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.not_a_sign", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.not_b_sign", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.a.is_zero.exp_zero", [-1.0] * exp_bits, [0.0]) | |
| add_gate(tensors, f"{prefix}.a.is_zero.frac_zero", [-1.0] * frac_bits, [0.0]) | |
| add_gate(tensors, f"{prefix}.a.is_zero.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.b.is_zero.exp_zero", [-1.0] * exp_bits, [0.0]) | |
| add_gate(tensors, f"{prefix}.b.is_zero.frac_zero", [-1.0] * frac_bits, [0.0]) | |
| add_gate(tensors, f"{prefix}.b.is_zero.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.both_zero", [1.0, 1.0], [-2.0]) | |
| # Bit-cascaded ternary magnitude comparator over the sign-cleared payload. | |
| add_bit_cascade_compare( | |
| tensors, | |
| cmp_prefix=f"{prefix}.mag_bc", | |
| bits=payload, | |
| out_gt=f"{prefix}.mag_a_gt_b", | |
| out_lt=f"{prefix}.mag_a_lt_b", | |
| out_ge=f"{prefix}.mag_a_ge_b", | |
| out_le=f"{prefix}.mag_a_le_b", | |
| out_eq=f"{prefix}.mag_eq.and", | |
| ) | |
| # EQ: not NaN, and (bitwise equal with matching signs, or both zero). | |
| add_gate(tensors, f"{prefix}.eq.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.eq.bits_eq", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.eq.eq_or_zero", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.eq.result", [1.0, 1.0], [-2.0]) | |
| # LT: (a negative with signs differing) or (same sign and the payload | |
| # order says so), suppressed for NaN and for +0 vs -0. | |
| add_gate(tensors, f"{prefix}.lt.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.lt.diff_sign.a_neg", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.lt.same_sign.pos_lt", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.lt.same_sign.neg_gt", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.lt.same_sign.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.lt.case_or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.lt.not_both_zero", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.lt.result", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.gt.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.gt.diff_sign.b_neg", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.gt.same_sign.pos_gt", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.gt.same_sign.neg_lt", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.gt.same_sign.or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.gt.case_or", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.gt.not_both_zero", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.gt.result", [1.0, 1.0, 1.0], [-3.0]) | |
| add_gate(tensors, f"{prefix}.le.eq_or_lt", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.le.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.le.result", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.ge.eq_or_gt", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.ge.not_nan", [-1.0], [0.0]) | |
| add_gate(tensors, f"{prefix}.ge.result", [1.0, 1.0], [-2.0]) | |
| def add_float16_cmp(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float16 comparison network (EQ, LT, LE, GT, GE).""" | |
| add_float_cmp(tensors, "float16", 5, 10) | |
| def add_float32_core(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add float32 core circuits (unpack, pack, classify). | |
| IEEE 754 single-precision format (32 bits): | |
| - Bit 31: Sign | |
| - Bits 30-23: Exponent (8 bits, bias=127) | |
| - Bits 22-0: Mantissa (23 bits, implicit leading 1) | |
| """ | |
| prefix = "float32" | |
| # Identity buffers: H(x - 1) reproduces a binary input (bias 0 would | |
| # make the gate constant-1, since H(0) = 1). | |
| for i in range(32): | |
| add_gate(tensors, f"{prefix}.unpack.bit{i}", [1.0], [-1.0]) | |
| add_gate(tensors, f"{prefix}.classify.exp_zero", [-1.0] * 8, [0.0]) | |
| add_gate(tensors, f"{prefix}.classify.exp_max", [1.0] * 8, [-8.0]) | |
| add_gate(tensors, f"{prefix}.classify.frac_zero", [-1.0] * 23, [0.0]) | |
| add_gate(tensors, f"{prefix}.classify.frac_nonzero", [1.0] * 23, [-1.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_zero.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_subnormal.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_inf.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"{prefix}.classify.is_nan.and", [1.0, 1.0], [-2.0]) | |
| for i in range(32): | |
| add_gate(tensors, f"{prefix}.pack.bit{i}", [1.0], [-1.0]) | |
| def add_float32_cmp(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float32 comparison network (EQ, LT, LE, GT, GE).""" | |
| add_float_cmp(tensors, "float32", 8, 23) | |
| def add_float32_add(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float32 addition pipeline.""" | |
| add_float_add(tensors, "float32", 8, 23) | |
| def add_float32_mul(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float32 multiplication pipeline.""" | |
| add_float_mul(tensors, "float32", 8, 23) | |
| def add_float32_div(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the float32 division pipeline.""" | |
| add_float_div(tensors, "float32", 8, 23) | |
| def update_manifest(tensors: Dict[str, torch.Tensor], data_bits: int, addr_bits: int, mem_bytes: int) -> None: | |
| """Update manifest metadata tensors. | |
| Args: | |
| data_bits: ALU/register width (8/16/32) | |
| addr_bits: Address bus width (determines memory size) | |
| mem_bytes: Memory size in bytes (2^addr_bits) | |
| """ | |
| tensors["manifest.data_bits"] = torch.tensor([float(data_bits)], dtype=torch.float32) | |
| tensors["manifest.addr_bits"] = torch.tensor([float(addr_bits)], dtype=torch.float32) | |
| tensors["manifest.memory_bytes"] = torch.tensor([float(mem_bytes)], dtype=torch.float32) | |
| tensors["manifest.pc_width"] = torch.tensor([float(addr_bits)], dtype=torch.float32) | |
| tensors["manifest.version"] = torch.tensor([4.0], dtype=torch.float32) # Bump version for N-bit support | |
| def write_manifest(path: Path, tensors: Dict[str, torch.Tensor]) -> None: | |
| lines: List[str] = [] | |
| lines.append("# Tensor Manifest") | |
| lines.append(f"# Total: {len(tensors)} tensors") | |
| for name in sorted(tensors.keys()): | |
| t = tensors[name] | |
| values = ", ".join(f"{v:.1f}" for v in t.flatten().tolist()) | |
| lines.append(f"{name}: shape={list(t.shape)}, values=[{values}]") | |
| path.write_text("\n".join(lines) + "\n", encoding="utf-8") | |
| def infer_boolean_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| if gate == 'boolean.not': | |
| return [reg.register("$x")] | |
| if gate in ['boolean.and', 'boolean.or', 'boolean.nand', 'boolean.nor', 'boolean.implies']: | |
| return [reg.register("$a"), reg.register("$b")] | |
| if '.layer1.neuron1' in gate or '.layer1.neuron2' in gate or '.layer1.or' in gate or '.layer1.nand' in gate: | |
| return [reg.register("$a"), reg.register("$b")] | |
| if '.layer2' in gate: | |
| parent = gate.rsplit('.layer2', 1)[0] | |
| if '.layer1.neuron1' in parent or 'xor' in parent or 'xnor' in parent or 'biimplies' in parent: | |
| parent = parent.rsplit('.layer1', 1)[0] if '.layer1' in parent else parent | |
| return [reg.register(f"{parent}.layer1.or"), reg.register(f"{parent}.layer1.nand")] | |
| return [] | |
| def infer_halfadder_inputs(gate: str, prefix: str, reg: SignalRegistry) -> List[int]: | |
| a = reg.register(f"{prefix}.$a") | |
| b = reg.register(f"{prefix}.$b") | |
| if '.sum.layer1' in gate: | |
| return [a, b] | |
| if '.sum.layer2' in gate: | |
| return [reg.register(f"{prefix}.sum.layer1.or"), reg.register(f"{prefix}.sum.layer1.nand")] | |
| if '.carry' in gate and '.layer' not in gate: | |
| return [a, b] | |
| return [a, b] | |
| def infer_fulladder_inputs(gate: str, prefix: str, reg: SignalRegistry) -> List[int]: | |
| a = reg.register(f"{prefix}.$a") | |
| b = reg.register(f"{prefix}.$b") | |
| cin = reg.register(f"{prefix}.$cin") | |
| if '.ha1.sum.layer1' in gate: | |
| return [a, b] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{prefix}.ha1.sum.layer1.or"), reg.register(f"{prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a, b] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{prefix}.ha2.sum.layer1.or"), reg.register(f"{prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{prefix}.ha1.carry"), reg.register(f"{prefix}.ha2.carry")] | |
| return [] | |
| def infer_ripplecarry_inputs(gate: str, prefix: str, bits: int, reg: SignalRegistry) -> List[int]: | |
| for i in range(bits): | |
| reg.register(f"{prefix}.$a[{i}]") | |
| reg.register(f"{prefix}.$b[{i}]") | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| a_bit = reg.get_id(f"{prefix}.$a[{bit}]") | |
| b_bit = reg.get_id(f"{prefix}.$b[{bit}]") | |
| cin = reg.get_id("#0") if bit == 0 else reg.register(f"{prefix}.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.fa{bit}" | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_bit, b_bit] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_bit, b_bit] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| def infer_expr_add_mul_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| """Infer inputs for A + B × C expression circuit (order of operations). | |
| Circuit structure: | |
| - Mask stage: mask.s[stage].b[bit] = B[bit] AND C[stage] | |
| - Accumulator stages 1-7: acc.s[stage] = acc.s[stage-1] + (mask.s[stage] << stage) | |
| - Final add: result = A + acc.s7 | |
| Bit ordering: MSB-first externally, LSB-first internally (fa0 = LSB, fa7 = MSB) | |
| - $x[7] = bit 0 (LSB), $x[0] = bit 7 (MSB) | |
| """ | |
| prefix = "arithmetic.expr_add_mul" | |
| # Register all inputs | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| reg.register(f"$c[{i}]") | |
| # Mask AND gates: mask.s[stage].b[bit] = B[bit] AND C[stage] | |
| if '.mul.mask.' in gate: | |
| m = re.search(r'\.s(\d+)\.b(\d+)', gate) | |
| if m: | |
| stage = int(m.group(1)) | |
| bit = int(m.group(2)) | |
| # MSB-first: $b[7-bit] is bit position 'bit', $c[7-stage] is stage position 'stage' | |
| b_input = reg.get_id(f"$b[{7-bit}]") | |
| c_input = reg.get_id(f"$c[{7-stage}]") | |
| return [b_input, c_input] | |
| return [] | |
| # Accumulator adders: acc.s[stage].fa[bit] | |
| if '.mul.acc.' in gate: | |
| m = re.search(r'\.s(\d+)\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| stage = int(m.group(1)) # 1-7 | |
| bit = int(m.group(2)) # 0-7 | |
| # A input: previous stage output | |
| if stage == 1: | |
| # First accumulator: A = mask.s0.b[bit] (AND gate output) | |
| a_input = reg.register(f"{prefix}.mul.mask.s0.b{bit}") | |
| else: | |
| # Later stages: A = previous accumulator sum | |
| a_input = reg.register(f"{prefix}.mul.acc.s{stage-1}.fa{bit}.ha2.sum.layer2") | |
| # B input: (mask.s[stage] << stage)[bit] | |
| # Shift left by 'stage' positions means: | |
| # - bit positions 0 to stage-1 get 0 | |
| # - bit position 'bit' gets mask.s[stage].b[bit-stage] | |
| if bit < stage: | |
| b_input = reg.get_id("#0") | |
| else: | |
| b_input = reg.register(f"{prefix}.mul.mask.s{stage}.b{bit-stage}") | |
| # Carry input | |
| if bit == 0: | |
| cin = reg.get_id("#0") | |
| else: | |
| cin = reg.register(f"{prefix}.mul.acc.s{stage}.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.mul.acc.s{stage}.fa{bit}" | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_input, b_input] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_input, b_input] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| # Final add stage: A + mul_result | |
| if '.add.fa' in gate: | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| # A input: $a[7-bit] (MSB-first to positional bit) | |
| a_input = reg.get_id(f"$a[{7-bit}]") | |
| # B input: multiplication result = acc.s7.fa[bit] sum output | |
| b_input = reg.register(f"{prefix}.mul.acc.s7.fa{bit}.ha2.sum.layer2") | |
| # Carry input | |
| if bit == 0: | |
| cin = reg.get_id("#0") | |
| else: | |
| cin = reg.register(f"{prefix}.add.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.add.fa{bit}" | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_input, b_input] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_input, b_input] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| return [] | |
| def infer_expr_paren_add_mul_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| """Infer inputs for (A + B) × C expression circuit (parenthetical override). | |
| Circuit structure: | |
| - Add stage: sum = A + B | |
| - Mask stage: mask.s[stage].b[bit] = sum[bit] AND C[stage] | |
| - Accumulator stages 1-7: acc.s[stage] = acc.s[stage-1] + (mask.s[stage] << stage) | |
| Bit ordering: MSB-first externally, LSB-first internally (fa0 = LSB, fa7 = MSB) | |
| """ | |
| prefix = "arithmetic.expr_paren_add_mul" | |
| # Register all inputs | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| reg.register(f"$c[{i}]") | |
| # Add stage: A + B | |
| if '.add.fa' in gate and '.mul.' not in gate: | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| # A input: $a[7-bit], B input: $b[7-bit] | |
| a_input = reg.get_id(f"$a[{7-bit}]") | |
| b_input = reg.get_id(f"$b[{7-bit}]") | |
| # Carry input | |
| if bit == 0: | |
| cin = reg.get_id("#0") | |
| else: | |
| cin = reg.register(f"{prefix}.add.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.add.fa{bit}" | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_input, b_input] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_input, b_input] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| # Mask AND gates: mask.s[stage].b[bit] = sum[bit] AND C[stage] | |
| if '.mul.mask.' in gate: | |
| m = re.search(r'\.s(\d+)\.b(\d+)', gate) | |
| if m: | |
| stage = int(m.group(1)) | |
| bit = int(m.group(2)) | |
| # sum[bit] comes from add.fa[bit].ha2.sum.layer2 | |
| sum_bit = reg.register(f"{prefix}.add.fa{bit}.ha2.sum.layer2") | |
| # C[stage] in MSB-first | |
| c_input = reg.get_id(f"$c[{7-stage}]") | |
| return [sum_bit, c_input] | |
| return [] | |
| # Accumulator adders: acc.s[stage].fa[bit] | |
| if '.mul.acc.' in gate: | |
| m = re.search(r'\.s(\d+)\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| stage = int(m.group(1)) # 1-7 | |
| bit = int(m.group(2)) # 0-7 | |
| # A input: previous stage output | |
| if stage == 1: | |
| # First accumulator: A = mask.s0.b[bit] (AND gate output) | |
| a_input = reg.register(f"{prefix}.mul.mask.s0.b{bit}") | |
| else: | |
| # Later stages: A = previous accumulator sum | |
| a_input = reg.register(f"{prefix}.mul.acc.s{stage-1}.fa{bit}.ha2.sum.layer2") | |
| # B input: (mask.s[stage] << stage)[bit] | |
| if bit < stage: | |
| b_input = reg.get_id("#0") | |
| else: | |
| b_input = reg.register(f"{prefix}.mul.mask.s{stage}.b{bit-stage}") | |
| # Carry input | |
| if bit == 0: | |
| cin = reg.get_id("#0") | |
| else: | |
| cin = reg.register(f"{prefix}.mul.acc.s{stage}.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.mul.acc.s{stage}.fa{bit}" | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_input, b_input] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_input, b_input] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| return [] | |
| def infer_expr_paren_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| """Infer inputs for (A + B) × C expression circuit (parenthetical grouping). | |
| Circuit structure: | |
| - Add stage: sum = A + B | |
| - Mask stage: mask.s[stage].b[bit] = sum[bit] AND C[stage] | |
| - Accumulator stages 1-7: acc.s[stage] = acc.s[stage-1] + (mask.s[stage] << stage) | |
| Bit ordering: MSB-first externally, LSB-first internally (fa0 = LSB, fa7 = MSB) | |
| """ | |
| prefix = "arithmetic.expr_paren" | |
| # Register all inputs | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| reg.register(f"$c[{i}]") | |
| # Add stage: sum = A + B | |
| if '.add.fa' in gate and '.mul.' not in gate: | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| # Inputs: $a[7-bit], $b[7-bit] | |
| a_input = reg.get_id(f"$a[{7-bit}]") | |
| b_input = reg.get_id(f"$b[{7-bit}]") | |
| # Carry input | |
| if bit == 0: | |
| cin = reg.get_id("#0") | |
| else: | |
| cin = reg.register(f"{prefix}.add.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.add.fa{bit}" | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_input, b_input] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_input, b_input] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| # Mask AND gates: mask.s[stage].b[bit] = sum[bit] AND C[stage] | |
| if '.mul.mask.' in gate: | |
| m = re.search(r'\.s(\d+)\.b(\d+)', gate) | |
| if m: | |
| stage = int(m.group(1)) | |
| bit = int(m.group(2)) | |
| # sum[bit] comes from add stage output | |
| sum_input = reg.register(f"{prefix}.add.fa{bit}.ha2.sum.layer2") | |
| # C[stage] in MSB-first: $c[7-stage] | |
| c_input = reg.get_id(f"$c[{7-stage}]") | |
| return [sum_input, c_input] | |
| return [] | |
| # Accumulator adders: acc.s[stage].fa[bit] | |
| if '.mul.acc.' in gate: | |
| m = re.search(r'\.s(\d+)\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| stage = int(m.group(1)) # 1-7 | |
| bit = int(m.group(2)) # 0-7 | |
| # A input: previous stage output | |
| if stage == 1: | |
| # First accumulator: A = mask.s0.b[bit] (AND gate output) | |
| a_input = reg.register(f"{prefix}.mul.mask.s0.b{bit}") | |
| else: | |
| # Later stages: A = previous accumulator sum | |
| a_input = reg.register(f"{prefix}.mul.acc.s{stage-1}.fa{bit}.ha2.sum.layer2") | |
| # B input: (mask.s[stage] << stage)[bit] | |
| if bit < stage: | |
| b_input = reg.get_id("#0") | |
| else: | |
| b_input = reg.register(f"{prefix}.mul.mask.s{stage}.b{bit-stage}") | |
| # Carry input | |
| if bit == 0: | |
| cin = reg.get_id("#0") | |
| else: | |
| cin = reg.register(f"{prefix}.mul.acc.s{stage}.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.mul.acc.s{stage}.fa{bit}" | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_input, b_input] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_input, b_input] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| return [] | |
| def infer_add3_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| """Infer inputs for 3-operand adder: A + B + C.""" | |
| prefix = "arithmetic.add3_8bit" | |
| # Register all inputs | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| reg.register(f"$c[{i}]") | |
| # Parse stage and bit | |
| if '.stage1.' in gate: | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| # Stage 1: A + B (LSB is index 7 in MSB-first) | |
| a_bit = reg.get_id(f"$a[{7-bit}]") | |
| b_bit = reg.get_id(f"$b[{7-bit}]") | |
| cin = reg.get_id("#0") if bit == 0 else reg.register(f"{prefix}.stage1.fa{bit-1}.carry_or") | |
| fa_prefix = f"{prefix}.stage1.fa{bit}" | |
| elif '.stage2.' in gate: | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| # Stage 2: stage1_result + C | |
| temp_bit = reg.register(f"{prefix}.stage1.fa{bit}.ha2.sum.layer2") | |
| c_bit = reg.get_id(f"$c[{7-bit}]") | |
| cin = reg.get_id("#0") if bit == 0 else reg.register(f"{prefix}.stage2.fa{bit-1}.carry_or") | |
| a_bit = temp_bit | |
| b_bit = c_bit | |
| fa_prefix = f"{prefix}.stage2.fa{bit}" | |
| else: | |
| return [] | |
| if '.ha1.sum.layer1' in gate: | |
| return [a_bit, b_bit] | |
| if '.ha1.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if '.ha1.carry' in gate and '.layer' not in gate: | |
| return [a_bit, b_bit] | |
| if '.ha2.sum.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.ha2.sum.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if '.ha2.carry' in gate and '.layer' not in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin] | |
| if '.carry_or' in gate: | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), reg.register(f"{fa_prefix}.ha2.carry")] | |
| return [] | |
| def infer_adcsbc_inputs(gate: str, prefix: str, is_sub: bool, reg: SignalRegistry) -> List[int]: | |
| for i in range(8): | |
| reg.register(f"{prefix}.$a[{i}]") | |
| reg.register(f"{prefix}.$b[{i}]") | |
| reg.register(f"{prefix}.$cin") | |
| if is_sub and '.notb' in gate: | |
| m = re.search(r'\.notb(\d+)', gate) | |
| if m: | |
| return [reg.get_id(f"{prefix}.$b[{int(m.group(1))}]")] | |
| return [] | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| if is_sub: | |
| a_bit = reg.get_id(f"{prefix}.$a[{bit}]") | |
| notb = reg.register(f"{prefix}.notb{bit}") | |
| else: | |
| a_bit = reg.get_id(f"{prefix}.$a[{bit}]") | |
| notb = reg.get_id(f"{prefix}.$b[{bit}]") | |
| cin = reg.get_id(f"{prefix}.$cin") if bit == 0 else reg.register(f"{prefix}.fa{bit-1}.or_carry") | |
| fa_prefix = f"{prefix}.fa{bit}" | |
| if '.xor1.layer1' in gate: | |
| return [a_bit, notb if is_sub else reg.get_id(f"{prefix}.$b[{bit}]")] | |
| if '.xor1.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.xor1.layer1.or"), reg.register(f"{fa_prefix}.xor1.layer1.nand")] | |
| if '.xor2.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.xor1.layer2"), cin] | |
| if '.xor2.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.xor2.layer1.or"), reg.register(f"{fa_prefix}.xor2.layer1.nand")] | |
| if '.and1' in gate: | |
| return [a_bit, notb if is_sub else reg.get_id(f"{prefix}.$b[{bit}]")] | |
| if '.and2' in gate: | |
| return [reg.register(f"{fa_prefix}.xor1.layer2"), cin] | |
| if '.or_carry' in gate: | |
| return [reg.register(f"{fa_prefix}.and1"), reg.register(f"{fa_prefix}.and2")] | |
| return [] | |
| def infer_sub8bit_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| prefix = "arithmetic.sub8bit" | |
| for i in range(8): | |
| reg.register(f"{prefix}.$a[{i}]") | |
| reg.register(f"{prefix}.$b[{i}]") | |
| if gate == f"{prefix}.carry_in": | |
| return [reg.get_id("#1")] | |
| if '.notb' in gate: | |
| m = re.search(r'\.notb(\d+)', gate) | |
| if m: | |
| return [reg.get_id(f"{prefix}.$b[{int(m.group(1))}]")] | |
| return [] | |
| m = re.search(r'\.fa(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| a_bit = reg.get_id(f"{prefix}.$a[{bit}]") | |
| notb = reg.register(f"{prefix}.notb{bit}") | |
| cin = reg.get_id("#1") if bit == 0 else reg.register(f"{prefix}.fa{bit-1}.or_carry") | |
| fa_prefix = f"{prefix}.fa{bit}" | |
| if '.xor1.layer1' in gate: | |
| return [a_bit, notb] | |
| if '.xor1.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.xor1.layer1.or"), reg.register(f"{fa_prefix}.xor1.layer1.nand")] | |
| if '.xor2.layer1' in gate: | |
| return [reg.register(f"{fa_prefix}.xor1.layer2"), cin] | |
| if '.xor2.layer2' in gate: | |
| return [reg.register(f"{fa_prefix}.xor2.layer1.or"), reg.register(f"{fa_prefix}.xor2.layer1.nand")] | |
| if '.and1' in gate: | |
| return [a_bit, notb] | |
| if '.and2' in gate: | |
| return [reg.register(f"{fa_prefix}.xor1.layer2"), cin] | |
| if '.or_carry' in gate: | |
| return [reg.register(f"{fa_prefix}.and1"), reg.register(f"{fa_prefix}.and2")] | |
| return [] | |
| def infer_threshold_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| for i in range(8): | |
| reg.register(f"$x[{i}]") | |
| return [reg.get_id(f"$x[{i}]") for i in range(8)] | |
| def infer_modular_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| for i in range(8): | |
| reg.register(f"$x[{i}]") | |
| if '.layer1' in gate or '.layer2' in gate or '.layer3' in gate: | |
| if 'layer1.geq' in gate or 'layer1.leq' in gate: | |
| return [reg.get_id(f"$x[{i}]") for i in range(8)] | |
| if 'layer2.eq' in gate: | |
| m = re.search(r'layer2\.eq(\d+)', gate) | |
| if m: | |
| idx = m.group(1) | |
| parent = gate.rsplit('.layer2', 1)[0] | |
| return [reg.register(f"{parent}.layer1.geq{idx}"), reg.register(f"{parent}.layer1.leq{idx}")] | |
| if 'layer3.or' in gate: | |
| parent = gate.rsplit('.layer3', 1)[0] | |
| eq_gates = [] | |
| for i in range(256): | |
| eq_gate = f"{parent}.layer2.eq{i}" | |
| if eq_gate in reg.name_to_id: | |
| eq_gates.append(reg.get_id(eq_gate)) | |
| return eq_gates if eq_gates else [reg.get_id(f"$x[{i}]") for i in range(8)] | |
| return [reg.get_id(f"$x[{i}]") for i in range(8)] | |
| def infer_control_jump_inputs(gate: str, prefix: str, reg: SignalRegistry) -> List[int]: | |
| for i in range(8): | |
| reg.register(f"{prefix}.$pc[{i}]") | |
| reg.register(f"{prefix}.$target[{i}]") | |
| flag = "$cond" | |
| if "jz" in prefix: | |
| flag = "$zero" | |
| elif "jc" in prefix: | |
| flag = "$carry" | |
| elif "jn" in prefix and "jnc" not in prefix and "jnz" not in prefix and "jnv" not in prefix: | |
| flag = "$negative" | |
| elif "jv" in prefix and "jnv" not in prefix: | |
| flag = "$overflow" | |
| elif "jp" in prefix: | |
| flag = "$positive" | |
| elif "jnc" in prefix: | |
| flag = "$not_carry" | |
| elif "jnz" in prefix: | |
| flag = "$not_zero" | |
| elif "jnv" in prefix: | |
| flag = "$not_overflow" | |
| reg.register(f"{prefix}.{flag}") | |
| m = re.search(r'\.bit(\d+)\.', gate) | |
| if not m: | |
| return [] | |
| bit = int(m.group(1)) | |
| bit_prefix = f"{prefix}.bit{bit}" | |
| if '.not_sel' in gate: | |
| return [reg.get_id(f"{prefix}.{flag}")] | |
| if '.and_a' in gate: | |
| return [reg.get_id(f"{prefix}.$pc[{bit}]"), reg.register(f"{bit_prefix}.not_sel")] | |
| if '.and_b' in gate: | |
| return [reg.get_id(f"{prefix}.$target[{bit}]"), reg.get_id(f"{prefix}.{flag}")] | |
| if '.or' in gate: | |
| return [reg.register(f"{bit_prefix}.and_a"), reg.register(f"{bit_prefix}.and_b")] | |
| return [] | |
| def infer_buffer_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| m = re.search(r'\.bit(\d+)$', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| prefix = gate.rsplit('.bit', 1)[0] | |
| return [reg.register(f"{prefix}.$data[{bit}]")] | |
| return [reg.register("$data")] | |
| def infer_memory_inputs(gate: str, reg: SignalRegistry, | |
| tensors: Dict[str, torch.Tensor] = None) -> List[int]: | |
| if 'addr_decode' in gate: | |
| addr_bits = 16 | |
| if tensors is not None: | |
| w = tensors.get("memory.addr_decode.weight") | |
| if w is not None and w.dim() == 2: | |
| addr_bits = w.shape[1] | |
| return [reg.register(f"$addr[{i}]") for i in range(addr_bits)] | |
| if 'read' in gate: | |
| return [reg.register("$mem"), reg.register("$sel")] | |
| if 'write' in gate: | |
| return [reg.register("$mem"), reg.register("$data"), reg.register("$sel"), reg.register("$we")] | |
| return [] | |
| def infer_alu_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| for i in range(4): | |
| reg.register(f"$opcode[{i}]") | |
| if 'alucontrol' in gate: | |
| return [reg.get_id(f"$opcode[{i}]") for i in range(4)] | |
| if 'aluflags' in gate: | |
| return [reg.register("$result"), reg.register("$carry"), reg.register("$overflow")] | |
| if '.shl.bit' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| if bit < 7: | |
| return [reg.get_id(f"$a[{bit + 1}]")] | |
| else: | |
| return [reg.get_id("#0")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if '.shr.bit' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| if bit > 0: | |
| return [reg.get_id(f"$a[{bit - 1}]")] | |
| else: | |
| return [reg.get_id("#0")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if '.mul.pp.a' in gate: | |
| m = re.search(r'a(\d+)b(\d+)', gate) | |
| if m: | |
| i, j = int(m.group(1)), int(m.group(2)) | |
| return [reg.get_id(f"$a[{i}]"), reg.get_id(f"$b[{j}]")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| if '.mul.' in gate: | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| if '.div.stage' in gate: | |
| if '.cmp' in gate: | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| if '.mux.bit' in gate: | |
| m = re.search(r'stage(\d+)\.mux\.bit(\d+)', gate) | |
| if m: | |
| stage, bit = int(m.group(1)), int(m.group(2)) | |
| prefix = f"alu.alu8bit.div.stage{stage}" | |
| if '.not_sel' in gate: | |
| return [reg.register(f"{prefix}.cmp")] | |
| if '.and_a' in gate: | |
| return [reg.register(f"$rem[{bit}]"), reg.register(f"{prefix}.mux.bit{bit}.not_sel")] | |
| if '.and_b' in gate: | |
| return [reg.register(f"$sub[{bit}]"), reg.register(f"{prefix}.cmp")] | |
| if '.or' in gate: | |
| return [reg.register(f"{prefix}.mux.bit{bit}.and_a"), reg.register(f"{prefix}.mux.bit{bit}.and_b")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| if '.inc.bit' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| prefix = f"alu.alu8bit.inc.bit{bit}" | |
| if 'layer1' in gate: | |
| if bit == 7: | |
| return [reg.get_id(f"$a[{bit}]"), reg.get_id("#1")] | |
| else: | |
| return [reg.get_id(f"$a[{bit}]"), reg.register(f"alu.alu8bit.inc.bit{bit+1}.carry")] | |
| if 'layer2' in gate: | |
| return [reg.register(f"{prefix}.xor.layer1.or"), reg.register(f"{prefix}.xor.layer1.nand")] | |
| if '.carry' in gate: | |
| if bit == 7: | |
| return [reg.get_id(f"$a[{bit}]"), reg.get_id("#1")] | |
| else: | |
| return [reg.get_id(f"$a[{bit}]"), reg.register(f"alu.alu8bit.inc.bit{bit+1}.carry")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if '.dec.bit' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| prefix = f"alu.alu8bit.dec.bit{bit}" | |
| if '.not_a' in gate: | |
| return [reg.get_id(f"$a[{bit}]")] | |
| if 'layer1' in gate: | |
| if bit == 7: | |
| return [reg.get_id(f"$a[{bit}]"), reg.get_id("#1")] | |
| else: | |
| return [reg.get_id(f"$a[{bit}]"), reg.register(f"alu.alu8bit.dec.bit{bit+1}.borrow")] | |
| if 'layer2' in gate: | |
| return [reg.register(f"{prefix}.xor.layer1.or"), reg.register(f"{prefix}.xor.layer1.nand")] | |
| if '.borrow' in gate: | |
| if bit == 7: | |
| return [reg.register(f"{prefix}.not_a"), reg.get_id("#1")] | |
| else: | |
| return [reg.register(f"{prefix}.not_a"), reg.register(f"alu.alu8bit.dec.bit{bit+1}.borrow")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if '.neg.' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| if '.not.bit' in gate: | |
| return [reg.get_id(f"$a[{bit}]")] | |
| prefix = f"alu.alu8bit.neg.inc.bit{bit}" | |
| not_bit = f"alu.alu8bit.neg.not.bit{bit}" | |
| if 'layer1' in gate: | |
| if bit == 7: | |
| return [reg.register(not_bit), reg.get_id("#1")] | |
| else: | |
| return [reg.register(not_bit), reg.register(f"alu.alu8bit.neg.inc.bit{bit+1}.carry")] | |
| if 'layer2' in gate: | |
| return [reg.register(f"{prefix}.xor.layer1.or"), reg.register(f"{prefix}.xor.layer1.nand")] | |
| if '.carry' in gate: | |
| if bit == 7: | |
| return [reg.register(not_bit), reg.get_id("#1")] | |
| else: | |
| return [reg.register(not_bit), reg.register(f"alu.alu8bit.neg.inc.bit{bit+1}.carry")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if '.rol.bit' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| src = (bit + 1) % 8 | |
| return [reg.get_id(f"$a[{src}]")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if '.ror.bit' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| src = (bit - 1) % 8 | |
| return [reg.get_id(f"$a[{src}]")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if '.and' in gate or '.or' in gate or '.xor' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| return [reg.get_id(f"$a[{bit}]"), reg.get_id(f"$b[{bit}]")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| if '.not' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| return [reg.get_id(f"$a[{int(m.group(1))}]")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if 'layer1' in gate or 'layer2' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| if 'layer1' in gate: | |
| return [reg.get_id(f"$a[{bit}]"), reg.get_id(f"$b[{bit}]")] | |
| parent = gate.rsplit('.layer2', 1)[0] | |
| return [reg.register(f"{parent}.layer1.or"), reg.register(f"{parent}.layer1.nand")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| def infer_pattern_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| for i in range(8): | |
| reg.register(f"$x[{i}]") | |
| if 'hammingdistance' in gate: | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| return [reg.get_id(f"$x[{i}]") for i in range(8)] | |
| def infer_error_detection_inputs(gate: str, reg: SignalRegistry) -> List[int]: | |
| for i in range(8): | |
| reg.register(f"$x[{i}]") | |
| if 'hamming' in gate: | |
| if 'encode' in gate: | |
| for i in range(4): | |
| reg.register(f"$d[{i}]") | |
| return [reg.get_id(f"$d[{i}]") for i in range(4)] | |
| if 'decode' in gate or 'syndrome' in gate: | |
| for i in range(7): | |
| reg.register(f"$c[{i}]") | |
| return [reg.get_id(f"$c[{i}]") for i in range(7)] | |
| if 'crc' in gate: | |
| return [reg.register(f"$data[{i}]") for i in range(8)] | |
| if 'parity' in gate and 'stage' in gate: | |
| m = re.search(r'stage(\d+)\.xor(\d+)', gate) | |
| if m: | |
| stage = int(m.group(1)) | |
| idx = int(m.group(2)) | |
| if stage == 1: | |
| return [reg.get_id(f"$x[{2*idx}]"), reg.get_id(f"$x[{2*idx+1}]")] | |
| parent = gate.rsplit(f'.stage{stage}', 1)[0] | |
| prev_stage = stage - 1 | |
| return [ | |
| reg.register(f"{parent}.stage{prev_stage}.xor{2*idx}.layer2"), | |
| reg.register(f"{parent}.stage{prev_stage}.xor{2*idx+1}.layer2") | |
| ] | |
| if 'output.not' in gate: | |
| parent = gate.rsplit('.output', 1)[0] | |
| return [reg.register(f"{parent}.stage3.xor0.layer2")] | |
| return [reg.get_id(f"$x[{i}]") for i in range(8)] | |
| def infer_combinational_inputs(gate: str, reg: SignalRegistry, tensors: Dict[str, torch.Tensor] = None) -> List[int]: | |
| if 'decoder3to8' in gate: | |
| for i in range(3): | |
| reg.register(f"$sel[{i}]") | |
| return [reg.get_id(f"$sel[{i}]") for i in range(3)] | |
| if 'encoder8to3' in gate: | |
| for i in range(8): | |
| reg.register(f"$x[{i}]") | |
| return [reg.get_id(f"$x[{i}]") for i in range(8)] | |
| if 'multiplexer' in gate: | |
| if '2to1' in gate: | |
| return [reg.register("$a"), reg.register("$b"), reg.register("$sel")] | |
| if '4to1' in gate: | |
| return [reg.register(f"$x[{i}]") for i in range(4)] + [reg.register(f"$sel[{i}]") for i in range(2)] | |
| if '8to1' in gate: | |
| return [reg.register(f"$x[{i}]") for i in range(8)] + [reg.register(f"$sel[{i}]") for i in range(3)] | |
| if 'demultiplexer' in gate: | |
| return [reg.register("$x"), reg.register("$sel")] | |
| if 'regmux4to1' in gate: | |
| for r in range(4): | |
| for i in range(8): | |
| reg.register(f"$r{r}[{i}]") | |
| for i in range(2): | |
| reg.register(f"$sel[{i}]") | |
| if gate == "combinational.regmux4to1.not_s0": | |
| return [reg.get_id("$sel[0]")] | |
| if gate == "combinational.regmux4to1.not_s1": | |
| return [reg.get_id("$sel[1]")] | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| if '.not_s' in gate: | |
| sidx = 0 if 's0' in gate else 1 | |
| return [reg.get_id(f"$sel[{sidx}]")] | |
| if '.and' in gate: | |
| and_m = re.search(r'\.and(\d+)', gate) | |
| if and_m: | |
| and_idx = int(and_m.group(1)) | |
| sel0 = "combinational.regmux4to1.not_s0" if (and_idx & 1) == 0 else "$sel[0]" | |
| sel1 = "combinational.regmux4to1.not_s1" if (and_idx & 2) == 0 else "$sel[1]" | |
| return [reg.get_id(f"$r{and_idx}[{bit}]"), reg.register(sel0), reg.register(sel1)] | |
| if '.or' in gate: | |
| return [reg.register(f"combinational.regmux4to1.bit{bit}.and{i}") for i in range(4)] | |
| return [] | |
| if 'barrelshifter' in gate: | |
| import math as _math | |
| bs_match = re.search(r'barrelshifter(\d*)', gate) | |
| bits = int(bs_match.group(1)) if bs_match and bs_match.group(1) else 8 | |
| bs_prefix = f"combinational.barrelshifter{bs_match.group(1) if bs_match else ''}" | |
| num_layers = max(1, _math.ceil(_math.log2(bits))) if bits > 1 else 1 | |
| for i in range(bits): | |
| reg.register(f"$x[{i}]") | |
| for i in range(num_layers): | |
| reg.register(f"$shift[{i}]") | |
| m = re.search(r'layer(\d+)\.bit(\d+)', gate) | |
| if m: | |
| layer, bit = int(m.group(1)), int(m.group(2)) | |
| shift_amount = 1 << (num_layers - 1 - layer) | |
| prefix = f"{bs_prefix}.layer{layer}.bit{bit}" | |
| sel_idx = num_layers - 1 - layer | |
| if '.not_sel' in gate: | |
| return [reg.get_id(f"$shift[{sel_idx}]")] | |
| if '.and_a' in gate: | |
| if layer == 0: | |
| return [reg.get_id(f"$x[{bit}]"), reg.register(f"{prefix}.not_sel")] | |
| else: | |
| prev_prefix = f"{bs_prefix}.layer{layer-1}.bit{bit}" | |
| return [reg.register(f"{prev_prefix}.or"), reg.register(f"{prefix}.not_sel")] | |
| if '.and_b' in gate: | |
| src = (bit + shift_amount) % bits | |
| if layer == 0: | |
| return [reg.get_id(f"$x[{src}]"), reg.get_id(f"$shift[{sel_idx}]")] | |
| else: | |
| prev_prefix = f"{bs_prefix}.layer{layer-1}.bit{src}" | |
| return [reg.register(f"{prev_prefix}.or"), reg.get_id(f"$shift[{sel_idx}]")] | |
| if '.or' in gate: | |
| return [reg.register(f"{prefix}.and_a"), reg.register(f"{prefix}.and_b")] | |
| return [reg.get_id(f"$x[{i}]") for i in range(bits)] | |
| if 'priorityencoder' in gate: | |
| pe_match = re.search(r'priorityencoder(\d*)', gate) | |
| bits = int(pe_match.group(1)) if pe_match and pe_match.group(1) else 8 | |
| pe_prefix = f"combinational.priorityencoder{pe_match.group(1) if pe_match else ''}" | |
| for i in range(bits): | |
| reg.register(f"$x[{i}]") | |
| # Legacy 8-bit naming: any_ge{pos} = OR of bits at positions [pos..bits-1] | |
| if '.any_ge' in gate: | |
| m = re.search(r'any_ge(\d+)', gate) | |
| if m: | |
| pos = int(m.group(1)) | |
| return [reg.get_id(f"$x[{i}]") for i in range(pos, bits)] | |
| # N-bit naming: any_higher{pos} = OR of bits 0..pos-1 | |
| if '.any_higher' in gate: | |
| m = re.search(r'any_higher(\d+)', gate) | |
| if m: | |
| pos = int(m.group(1)) | |
| return [reg.get_id(f"$x[{i}]") for i in range(pos)] | |
| if '.is_highest' in gate: | |
| m = re.search(r'is_highest(\d+)', gate) | |
| if m: | |
| pos = int(m.group(1)) | |
| if '.not_higher' in gate: | |
| if pos == 0: | |
| return [reg.get_id("#0")] | |
| else: | |
| # Try N-bit any_higher first, fall back to legacy any_ge | |
| ah_key = f"{pe_prefix}.any_higher{pos}" | |
| if tensors is not None and f"{ah_key}.weight" in tensors: | |
| return [reg.register(ah_key)] | |
| return [reg.register(f"{pe_prefix}.any_ge{pos-1}")] | |
| if '.and' in gate: | |
| return [reg.get_id(f"$x[{pos}]"), reg.register(f"{pe_prefix}.is_highest{pos}.not_higher")] | |
| if '.out' in gate: | |
| m = re.search(r'out(\d+)', gate) | |
| if m: | |
| out_bit = int(m.group(1)) | |
| inputs = [] | |
| for pos in range(bits): | |
| if (pos >> out_bit) & 1: | |
| inputs.append(reg.register(f"{pe_prefix}.is_highest{pos}.and")) | |
| return inputs | |
| if '.valid' in gate: | |
| return [reg.get_id(f"$x[{i}]") for i in range(bits)] | |
| return [reg.get_id(f"$x[{i}]") for i in range(bits)] | |
| return [] | |
| def _infer_bit_cascade(gate: str, reg: SignalRegistry, cmp_prefix: str, | |
| a_template: str, b_template: str) -> List[int] | None: | |
| """If `gate` is part of the bit-cascade comparator at `cmp_prefix`, return | |
| its inputs. `a_template` / `b_template` are format strings like | |
| "$a[{}]" / "$b[{}]" that produce the per-bit input signal names. Returns | |
| None if `gate` isn't a member of this cascade. | |
| """ | |
| if not gate.startswith(cmp_prefix + "."): | |
| return None | |
| suffix = gate[len(cmp_prefix) + 1:] | |
| m = re.match(r"^bit(\d+)(?:\.(.+))?$", suffix) | |
| if m: | |
| i = int(m.group(1)) | |
| sub = m.group(2) or "" | |
| a_sig = a_template.format(i) | |
| b_sig = b_template.format(i) | |
| if sub in ("gt", "lt", "eq.layer1.and", "eq.layer1.nor"): | |
| reg.register(a_sig) | |
| reg.register(b_sig) | |
| return [reg.get_id(a_sig), reg.get_id(b_sig)] | |
| if sub == "eq": | |
| return [ | |
| reg.register(f"{cmp_prefix}.bit{i}.eq.layer1.and"), | |
| reg.register(f"{cmp_prefix}.bit{i}.eq.layer1.nor"), | |
| ] | |
| m = re.match(r"^cascade\.eq_prefix\.bit(\d+)$", suffix) | |
| if m: | |
| i = int(m.group(1)) | |
| return [reg.register(f"{cmp_prefix}.bit{j}.eq") for j in range(i)] | |
| m = re.match(r"^cascade\.(gt|lt)\.bit(\d+)$", suffix) | |
| if m: | |
| kind = m.group(1) | |
| i = int(m.group(2)) | |
| return [ | |
| reg.register(f"{cmp_prefix}.cascade.eq_prefix.bit{i}"), | |
| reg.register(f"{cmp_prefix}.bit{i}.{kind}"), | |
| ] | |
| return None | |
| def _infer_compare_final(gate: str, reg: SignalRegistry, cmp_prefix: str, | |
| out_gt: str, out_lt: str, out_ge: str, out_le: str, | |
| out_eq: str, bits: int) -> List[int] | None: | |
| """Inputs for the final outputs of an add_bit_cascade_compare emit: | |
| out_gt/out_lt are N-input ORs; out_eq is N-input AND; ge/le are | |
| NOT-then-buffer chains over lt/gt. | |
| """ | |
| if gate == out_gt: | |
| return ([reg.register(f"{cmp_prefix}.bit0.gt")] | |
| + [reg.register(f"{cmp_prefix}.cascade.gt.bit{i}") for i in range(1, bits)]) | |
| if gate == out_lt: | |
| return ([reg.register(f"{cmp_prefix}.bit0.lt")] | |
| + [reg.register(f"{cmp_prefix}.cascade.lt.bit{i}") for i in range(1, bits)]) | |
| if gate == out_eq: | |
| return [reg.register(f"{cmp_prefix}.bit{i}.eq") for i in range(bits)] | |
| if gate == f"{out_ge}.not_lt": | |
| return [reg.register(out_lt)] | |
| if gate == out_ge: | |
| return [reg.register(f"{out_ge}.not_lt")] | |
| if gate == f"{out_le}.not_gt": | |
| return [reg.register(out_gt)] | |
| if gate == out_le: | |
| return [reg.register(f"{out_le}.not_gt")] | |
| return None | |
| def infer_float_cmp_inputs(gate: str, reg: SignalRegistry, family: str, | |
| exp_bits: int, frac_bits: int) -> Optional[List[int]]: | |
| """Complete wiring for the self-contained float comparison network. | |
| External inputs are the raw operand words $a[0..W-1] / $b[0..W-1], | |
| MSB-first: bit 0 is the sign, bits 1..E the exponent, the rest the | |
| fraction. Field extraction is input wiring, not gates. | |
| """ | |
| prefix = f"{family}.cmp" | |
| if gate != prefix and not gate.startswith(prefix + "."): | |
| return None | |
| word = 1 + exp_bits + frac_bits | |
| payload = exp_bits + frac_bits | |
| for i in range(word): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| def a(i: int) -> int: | |
| return reg.get_id(f"$a[{i}]") | |
| def b(i: int) -> int: | |
| return reg.get_id(f"$b[{i}]") | |
| def R(name: str) -> int: | |
| return reg.register(f"{prefix}.{name}") | |
| exp_a = [a(1 + i) for i in range(exp_bits)] | |
| exp_b = [b(1 + i) for i in range(exp_bits)] | |
| frac_a = [a(1 + exp_bits + i) for i in range(frac_bits)] | |
| frac_b = [b(1 + exp_bits + i) for i in range(frac_bits)] | |
| suffix = gate[len(prefix) + 1:] if gate != prefix else "" | |
| table = { | |
| "a.exp_max": exp_a, | |
| "a.frac_nz": frac_a, | |
| "a.is_nan": [R("a.exp_max"), R("a.frac_nz")], | |
| "b.exp_max": exp_b, | |
| "b.frac_nz": frac_b, | |
| "b.is_nan": [R("b.exp_max"), R("b.frac_nz")], | |
| "either_nan": [R("a.is_nan"), R("b.is_nan")], | |
| "sign_xor.layer1.or": [a(0), b(0)], | |
| "sign_xor.layer1.nand": [a(0), b(0)], | |
| "sign_xor.layer2": [R("sign_xor.layer1.or"), R("sign_xor.layer1.nand")], | |
| "same_sign.layer1.and": [a(0), b(0)], | |
| "same_sign.layer1.nor": [a(0), b(0)], | |
| "same_sign": [R("same_sign.layer1.and"), R("same_sign.layer1.nor")], | |
| "not_a_sign": [a(0)], | |
| "not_b_sign": [b(0)], | |
| "a.is_zero.exp_zero": exp_a, | |
| "a.is_zero.frac_zero": frac_a, | |
| "a.is_zero.and": [R("a.is_zero.exp_zero"), R("a.is_zero.frac_zero")], | |
| "b.is_zero.exp_zero": exp_b, | |
| "b.is_zero.frac_zero": frac_b, | |
| "b.is_zero.and": [R("b.is_zero.exp_zero"), R("b.is_zero.frac_zero")], | |
| "both_zero": [R("a.is_zero.and"), R("b.is_zero.and")], | |
| "eq.not_nan": [R("either_nan")], | |
| "eq.bits_eq": [R("same_sign"), R("mag_eq.and")], | |
| "eq.eq_or_zero": [R("eq.bits_eq"), R("both_zero")], | |
| "eq.result": [R("eq.not_nan"), R("eq.eq_or_zero")], | |
| "lt.not_nan": [R("either_nan")], | |
| "lt.diff_sign.a_neg": [R("sign_xor.layer2"), a(0)], | |
| "lt.same_sign.pos_lt": [R("same_sign"), R("not_a_sign"), R("mag_a_lt_b")], | |
| "lt.same_sign.neg_gt": [R("same_sign"), a(0), R("mag_a_gt_b")], | |
| "lt.same_sign.or": [R("lt.same_sign.pos_lt"), R("lt.same_sign.neg_gt")], | |
| "lt.case_or": [R("lt.diff_sign.a_neg"), R("lt.same_sign.or")], | |
| "lt.not_both_zero": [R("both_zero")], | |
| "lt.result": [R("lt.not_nan"), R("lt.case_or"), R("lt.not_both_zero")], | |
| "gt.not_nan": [R("either_nan")], | |
| "gt.diff_sign.b_neg": [R("sign_xor.layer2"), b(0)], | |
| "gt.same_sign.pos_gt": [R("same_sign"), R("not_b_sign"), R("mag_a_gt_b")], | |
| "gt.same_sign.neg_lt": [R("same_sign"), b(0), R("mag_a_lt_b")], | |
| "gt.same_sign.or": [R("gt.same_sign.pos_gt"), R("gt.same_sign.neg_lt")], | |
| "gt.case_or": [R("gt.diff_sign.b_neg"), R("gt.same_sign.or")], | |
| "gt.not_both_zero": [R("both_zero")], | |
| "gt.result": [R("gt.not_nan"), R("gt.case_or"), R("gt.not_both_zero")], | |
| "le.eq_or_lt": [R("eq.result"), R("lt.result")], | |
| "le.not_nan": [R("either_nan")], | |
| "le.result": [R("le.eq_or_lt"), R("le.not_nan")], | |
| "ge.eq_or_gt": [R("eq.result"), R("gt.result")], | |
| "ge.not_nan": [R("either_nan")], | |
| "ge.result": [R("ge.eq_or_gt"), R("ge.not_nan")], | |
| } | |
| if suffix in table: | |
| return table[suffix] | |
| # Magnitude comparator over the payload: bit i of the cascade reads word | |
| # bit 1+i of each operand. | |
| m = re.match(r"^mag_bc\.bit(\d+)(?:\.(.+))?$", suffix) | |
| if m: | |
| i = int(m.group(1)) | |
| sub = m.group(2) or "" | |
| if sub in ("gt", "lt", "eq.layer1.and", "eq.layer1.nor"): | |
| return [a(1 + i), b(1 + i)] | |
| if sub == "eq": | |
| return [R(f"mag_bc.bit{i}.eq.layer1.and"), R(f"mag_bc.bit{i}.eq.layer1.nor")] | |
| m = re.match(r"^mag_bc\.cascade\.eq_prefix\.bit(\d+)$", suffix) | |
| if m: | |
| i = int(m.group(1)) | |
| return [R(f"mag_bc.bit{j}.eq") for j in range(i)] | |
| m = re.match(r"^mag_bc\.cascade\.(gt|lt)\.bit(\d+)$", suffix) | |
| if m: | |
| kind, i = m.group(1), int(m.group(2)) | |
| return [R(f"mag_bc.cascade.eq_prefix.bit{i}"), R(f"mag_bc.bit{i}.{kind}")] | |
| if suffix == "mag_a_gt_b": | |
| return [R("mag_bc.bit0.gt")] + [R(f"mag_bc.cascade.gt.bit{i}") for i in range(1, payload)] | |
| if suffix == "mag_a_lt_b": | |
| return [R("mag_bc.bit0.lt")] + [R(f"mag_bc.cascade.lt.bit{i}") for i in range(1, payload)] | |
| if suffix == "mag_eq.and": | |
| return [R(f"mag_bc.bit{i}.eq") for i in range(payload)] | |
| if suffix == "mag_a_ge_b.not_lt": | |
| return [R("mag_a_lt_b")] | |
| if suffix == "mag_a_ge_b": | |
| return [R("mag_a_ge_b.not_lt")] | |
| if suffix == "mag_a_le_b.not_gt": | |
| return [R("mag_a_gt_b")] | |
| if suffix == "mag_a_le_b": | |
| return [R("mag_a_le_b.not_gt")] | |
| return None | |
| def _fa_member_inputs(sub: str, fa_prefix: str, a_sig: int, b_sig: int, | |
| cin_sig: int, reg: SignalRegistry) -> Optional[List[int]]: | |
| """Wiring for one gate of an add_full_adder cell, given the cell's | |
| operand/carry signal IDs. `sub` is the gate suffix inside the cell.""" | |
| if sub == "ha1.sum.layer1.or" or sub == "ha1.sum.layer1.nand": | |
| return [a_sig, b_sig] | |
| if sub == "ha1.sum.layer2": | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer1.or"), | |
| reg.register(f"{fa_prefix}.ha1.sum.layer1.nand")] | |
| if sub == "ha1.carry": | |
| return [a_sig, b_sig] | |
| if sub == "ha2.sum.layer1.or" or sub == "ha2.sum.layer1.nand": | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin_sig] | |
| if sub == "ha2.sum.layer2": | |
| return [reg.register(f"{fa_prefix}.ha2.sum.layer1.or"), | |
| reg.register(f"{fa_prefix}.ha2.sum.layer1.nand")] | |
| if sub == "ha2.carry": | |
| return [reg.register(f"{fa_prefix}.ha1.sum.layer2"), cin_sig] | |
| if sub == "carry_or": | |
| return [reg.register(f"{fa_prefix}.ha1.carry"), | |
| reg.register(f"{fa_prefix}.ha2.carry")] | |
| return None | |
| def infer_float_mul_inputs(gate: str, reg: SignalRegistry, family: str, | |
| exp_bits: int, frac_bits: int) -> Optional[List[int]]: | |
| """Complete wiring for the self-contained float multiplication pipeline. | |
| Operand words $a/$b are MSB-first. Internally the mantissa and exponent | |
| signals are LSB-first: mantissa bit k of `a` is $a[W-1-k] for k < F with | |
| the implicit leading one (index F) wired to #1; exponent bit k is | |
| $a[E-k]. The accumulated product bit k is acc.s{F-1}.fa{k}'s sum. | |
| """ | |
| E, F = exp_bits, frac_bits | |
| W = 1 + E + F | |
| prefix = f"{family}.mul" | |
| if gate != prefix and not gate.startswith(prefix + "."): | |
| return None | |
| for i in range(W): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| def a(i: int) -> int: | |
| return reg.get_id(f"$a[{i}]") | |
| def b(i: int) -> int: | |
| return reg.get_id(f"$b[{i}]") | |
| def R(name: str) -> int: | |
| return reg.register(f"{prefix}.{name}") | |
| zero = reg.get_id("#0") | |
| one = reg.get_id("#1") | |
| def m_a(k: int) -> int: | |
| return R("a.exp_nzero") if k == F else a(W - 1 - k) | |
| def m_b(k: int) -> int: | |
| return R("b.exp_nzero") if k == F else b(W - 1 - k) | |
| def eexp(op, k: int) -> int: | |
| w = a if op == "a" else b | |
| return R(f"{op}.eexp_lsb") if k == 0 else (w(E - k) if k < E else zero) | |
| def P(k: int) -> int: | |
| return R(f"mant_mul.acc.s{F - 1}.fa{k}.ha2.sum.layer2") | |
| exp_a_field = [a(1 + i) for i in range(E)] | |
| exp_b_field = [b(1 + i) for i in range(E)] | |
| frac_a_field = [a(1 + E + i) for i in range(F)] | |
| frac_b_field = [b(1 + E + i) for i in range(F)] | |
| suffix = gate[len(prefix) + 1:] if gate != prefix else "" | |
| bias = (1 << (E - 1)) - 1 | |
| RESM = 2 * F + 2 | |
| nlzb = 0 | |
| while (1 << nlzb) <= RESM: | |
| nlzb += 1 | |
| RB = 0 | |
| while (1 << RB) < RESM: | |
| RB += 1 | |
| def exp_l(k: int) -> int: # exp_base = exp_a + exp_b - bias | |
| return R(f"expb.fa{k}.ha2.sum.layer2") if k < E + 2 else zero | |
| def shifted(k: int) -> int: # product right-shifted by rsh | |
| return R(f"rbar.s{RB - 1}.bit{k}.or") | |
| table = { | |
| "sign_xor.layer1.or": [a(0), b(0)], | |
| "sign_xor.layer1.nand": [a(0), b(0)], | |
| "sign_xor.layer2": [R("sign_xor.layer1.or"), R("sign_xor.layer1.nand")], | |
| "a.exp_zero": exp_a_field, "a.exp_nzero": exp_a_field, "a.exp_max": exp_a_field, | |
| "a.frac_nz": frac_a_field, "a.frac_zero": frac_a_field, | |
| "a.is_nan": [R("a.exp_max"), R("a.frac_nz")], | |
| "a.is_inf": [R("a.exp_max"), R("a.frac_zero")], | |
| "a.is_zero": [R("a.exp_zero"), R("a.frac_zero")], "a.nonzero": [R("a.is_zero")], | |
| "a.eexp_lsb": [a(E), R("a.exp_zero")], | |
| "b.exp_zero": exp_b_field, "b.exp_nzero": exp_b_field, "b.exp_max": exp_b_field, | |
| "b.frac_nz": frac_b_field, "b.frac_zero": frac_b_field, | |
| "b.is_nan": [R("b.exp_max"), R("b.frac_nz")], | |
| "b.is_inf": [R("b.exp_max"), R("b.frac_zero")], | |
| "b.is_zero": [R("b.exp_zero"), R("b.frac_zero")], "b.nonzero": [R("b.is_zero")], | |
| "b.eexp_lsb": [b(E), R("b.exp_zero")], | |
| "lzc.nz": [P(k) for k in range(RESM)], | |
| "lzc.not_nz": [R("lzc.nz")], | |
| "er.zero": [R(f"exp_r.fa{k}.ha2.sum.layer2") for k in range(E + 2)], | |
| "er.underflow": [R(f"exp_r.fa{E + 1}.ha2.sum.layer2"), R("er.zero")], | |
| "er.not_underflow": [R("er.underflow")], | |
| "too_deep": [R(f"rsh.fa{k}.ha2.sum.layer2") for k in range(RB, E + 2)], | |
| "not_deep": [R("too_deep")], | |
| "deep_nz": [R("too_deep"), R("lzc.nz")], | |
| "round.guard": [shifted(0), R("not_deep")], | |
| "round.sticky": [R(f"rbar.s{RB - 1}.sticky"), R("deep_nz")], | |
| "round.rsl": [R("round.sticky"), R("sig.bit0")], | |
| "round.up": [R("round.guard"), R("round.rsl")], | |
| "exp_r.zero": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E + 2)], | |
| "exp_r.not_neg": [R(f"exp_round.bit{E + 1}.xor.layer2")], | |
| "exp_r.and_low": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E)], | |
| "exp_r.ge_emax": [R(f"exp_round.bit{E}.xor.layer2"), R("exp_r.and_low")], | |
| "exp_r.overflow": [R("exp_r.not_neg"), R("exp_r.ge_emax")], | |
| "sel.inputs_nan": [R("a.is_nan"), R("b.is_nan")], | |
| "sel.inf_zero1": [R("a.is_inf"), R("b.is_zero")], | |
| "sel.inf_zero2": [R("b.is_inf"), R("a.is_zero")], | |
| "sel.inf_zero": [R("sel.inf_zero1"), R("sel.inf_zero2")], | |
| "sel.nan": [R("sel.inputs_nan"), R("sel.inf_zero")], | |
| "sel.not_nan": [R("sel.nan")], | |
| "sel.inf_in": [R("a.is_inf"), R("b.is_inf")], | |
| "sel.zero_in": [R("a.is_zero"), R("b.is_zero")], | |
| "sel.inf_or_ovf": [R("sel.inf_in"), R("exp_r.overflow")], | |
| "sel.not_inf_path": [R("sel.inf_or_ovf")], | |
| "sel.inf": [R("sel.not_nan"), R("sel.inf_or_ovf")], | |
| "sel.zero_or_unf": [R("sel.zero_in"), R("sel.zero_in")], | |
| "sel.not_zero_unf": [R("sel.zero_or_unf")], | |
| "sel.zero": [R("sel.not_nan"), R("sel.not_inf_path"), R("sel.zero_or_unf")], | |
| "sel.norm": [R("sel.not_nan"), R("sel.not_inf_path"), R("sel.not_zero_unf")], | |
| "sign_out": [R("sign_xor.layer2"), R("sel.not_nan")], | |
| } | |
| if suffix in table: | |
| return table[suffix] | |
| m = re.match(r"^mant_mul\.pp\.a(\d+)b(\d+)$", suffix) | |
| if m: | |
| return [m_a(int(m.group(1))), m_b(int(m.group(2)))] | |
| m = re.match(r"^exp_add\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| cin = zero if k == 0 else R(f"exp_add.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_add.fa{k}", | |
| eexp("a", k), eexp("b", k), cin, reg) | |
| m = re.match(r"^expb\.fa(\d+)\.(.+)$", suffix) # exp_base = exp_add - bias | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = R(f"exp_add.fa{k}.ha2.sum.layer2") if k <= E else zero | |
| b_sig = zero if (bias >> k) & 1 else one | |
| cin = one if k == 0 else R(f"expb.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.expb.fa{k}", a_sig, b_sig, cin, reg) | |
| m = re.match(r"^mant_mul\.acc\.s(\d+)\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| t = int(m.group(1)) | |
| k = int(m.group(2)) | |
| row = t + 1 | |
| if t == 0: | |
| a_sig = R(f"mant_mul.pp.a{k}b0") if k <= F else zero | |
| else: | |
| a_sig = R(f"mant_mul.acc.s{t - 1}.fa{k}.ha2.sum.layer2") | |
| b_sig = R(f"mant_mul.pp.a{k - row}b{row}") if row <= k <= row + F else zero | |
| cin = zero if k == 0 else R(f"mant_mul.acc.s{t}.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(3), f"{prefix}.mant_mul.acc.s{t}.fa{k}", | |
| a_sig, b_sig, cin, reg) | |
| m = re.match(r"^lzc\.any_higher(\d+)$", suffix) | |
| if m: | |
| pos = int(m.group(1)) | |
| return [P(RESM - 1 - t) for t in range(pos)] | |
| m = re.match(r"^lzc\.is_highest(\d+)\.(not_higher|and)$", suffix) | |
| if m: | |
| pos = int(m.group(1)) | |
| if m.group(2) == "not_higher": | |
| return [R(f"lzc.any_higher{pos}")] | |
| return [P(RESM - 1 - pos), R(f"lzc.is_highest{pos}.not_higher")] | |
| m = re.match(r"^lzc\.nlz\.bit(\d+)$", suffix) | |
| if m: | |
| bbit = int(m.group(1)) | |
| srcs = [R(f"lzc.is_highest{pos}.and") for pos in range(1, RESM) if (pos >> bbit) & 1] | |
| return srcs if srcs else [zero] | |
| m = re.match(r"^exp_nnlz\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"lzc.nlz.bit{int(m.group(1))}")] | |
| m = re.match(r"^exp_s1\.fa(\d+)\.(.+)$", suffix) # exp_base - nlz | |
| if m: | |
| k = int(m.group(1)) | |
| b_sig = R(f"exp_nnlz.bit{k}") if k < nlzb else one | |
| cin = one if k == 0 else R(f"exp_s1.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_s1.fa{k}", exp_l(k), b_sig, cin, reg) | |
| m = re.match(r"^exp_r\.fa(\d+)\.(.+)$", suffix) # + 1 | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = R(f"exp_s1.fa{k}.ha2.sum.layer2") | |
| cin = one if k == 0 else R(f"exp_r.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_r.fa{k}", a_sig, zero, cin, reg) | |
| m = re.match(r"^exprc\.bit(\d+)$", suffix) # er clamped to >= 0 | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"exp_r.fa{k}.ha2.sum.layer2"), R("er.not_underflow")] | |
| m = re.match(r"^nsh\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) # min(nlz, exp_base) | |
| if m: | |
| k = int(m.group(1)) | |
| kind = m.group(2) | |
| nlz_bit = R(f"lzc.nlz.bit{k}") if k < nlzb else zero | |
| if kind == "not_sel": | |
| return [R("er.underflow")] | |
| if kind == "and_a": | |
| return [nlz_bit, R(f"nsh.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [exp_l(k), R("er.underflow")] | |
| return [R(f"nsh.bit{k}.and_a"), R(f"nsh.bit{k}.and_b")] | |
| m = re.match(r"^nshc\.bit(\d+)$", suffix) # ~nsh | |
| if m: | |
| return [R(f"nsh.bit{int(m.group(1))}.or")] | |
| m = re.match(r"^rsh\.fa(\d+)\.(.+)$", suffix) # rshift = (F+1) + ~nsh + 1 | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = one if ((F + 1) >> k) & 1 else zero | |
| cin = one if k == 0 else R(f"rsh.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.rsh.fa{k}", a_sig, R(f"nshc.bit{k}"), cin, reg) | |
| m = re.match(r"^rbar\.s(\d+)\.(.+)$", suffix) # right-barrel shift by rshift | |
| if m: | |
| j = int(m.group(1)) | |
| rest = m.group(2) | |
| sel = R(f"rsh.fa{j}.ha2.sum.layer2") | |
| def rin(k: int) -> int: # widened product Pw = P << 1 | |
| if k < 0 or k > RESM: | |
| return zero | |
| if j == 0: | |
| return zero if k == 0 else P(k - 1) | |
| return R(f"rbar.s{j - 1}.bit{k}.or") | |
| if rest == "drop": | |
| return [rin(k) for k in range(1 << j)] | |
| if rest == "st_and": | |
| return [sel, R(f"rbar.s{j}.drop")] | |
| if rest == "sticky": | |
| prev = zero if j == 0 else R(f"rbar.s{j - 1}.sticky") | |
| return [prev, R(f"rbar.s{j}.st_and")] | |
| mm = re.match(r"^bit(\d+)\.(not_sel|and_a|and_b|or)$", rest) | |
| if mm: | |
| k = int(mm.group(1)) | |
| kind = mm.group(2) | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [rin(k), R(f"rbar.s{j}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [rin(k + (1 << j)), sel] | |
| return [R(f"rbar.s{j}.bit{k}.and_a"), R(f"rbar.s{j}.bit{k}.and_b")] | |
| m = re.match(r"^sig\.bit(\d+)$", suffix) # mantissa frac bit (masked) | |
| if m: | |
| return [shifted(int(m.group(1)) + 1), R("not_deep")] | |
| m = re.match(r"^rnd\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| rest = m.group(2) | |
| cin = R("round.up") if k == 0 else R(f"rnd.bit{k - 1}.carry") | |
| f_bit = R(f"sig.bit{k}") | |
| if rest == "xor.layer1.or" or rest == "xor.layer1.nand": | |
| return [f_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"rnd.bit{k}.xor.layer1.or"), R(f"rnd.bit{k}.xor.layer1.nand")] | |
| if rest == "carry": | |
| return [f_bit, cin] | |
| m = re.match(r"^exp_round\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| rest = m.group(2) | |
| cin = R(f"rnd.bit{F - 1}.carry") if k == 0 else R(f"exp_round.bit{k - 1}.carry") | |
| e_bit = R(f"exprc.bit{k}") | |
| if rest == "xor.layer1.or" or rest == "xor.layer1.nand": | |
| return [e_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"exp_round.bit{k}.xor.layer1.or"), R(f"exp_round.bit{k}.xor.layer1.nand")] | |
| if rest == "carry": | |
| return [e_bit, cin] | |
| m = re.match(r"^exp_out\.bit(\d+)(\.norm_and)?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| if m.group(2): | |
| return [R("sel.norm"), R(f"exp_round.bit{k}.xor.layer2")] | |
| return [R("sel.nan"), R("sel.inf"), R(f"exp_out.bit{k}.norm_and")] | |
| m = re.match(r"^frac_out\.bit(\d+)(\.norm_and)?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| if m.group(2): | |
| return [R("sel.norm"), R(f"rnd.bit{k}.xor.layer2")] | |
| nan_sig = R("sel.nan") if k == F - 1 else zero | |
| return [nan_sig, R(f"frac_out.bit{k}.norm_and")] | |
| return None | |
| def infer_float_div_inputs(gate: str, reg: SignalRegistry, family: str, | |
| exp_bits: int, frac_bits: int) -> Optional[List[int]]: | |
| """Complete wiring for the self-contained float division pipeline. | |
| Stage 0 compares the dividend mantissa against the divisor directly; | |
| every later stage shifts the previous remainder left by one (the | |
| shifted-out top bit forces the quotient bit through the stage OR) and | |
| conditionally subtracts. The quotient bit of stage s is | |
| mant_div.stage{s}.q; the remainder bits are the stage mux outputs. | |
| """ | |
| E, F = exp_bits, frac_bits | |
| W = 1 + E + F | |
| prefix = f"{family}.div" | |
| if gate != prefix and not gate.startswith(prefix + "."): | |
| return None | |
| for i in range(W): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| def a(i: int) -> int: | |
| return reg.get_id(f"$a[{i}]") | |
| def b(i: int) -> int: | |
| return reg.get_id(f"$b[{i}]") | |
| def R(name: str) -> int: | |
| return reg.register(f"{prefix}.{name}") | |
| zero = reg.get_id("#0") | |
| one = reg.get_id("#1") | |
| ML = F + 1 | |
| mlzb = 0 | |
| while (1 << mlzb) <= ML: | |
| mlzb += 1 | |
| RBd = 0 | |
| while (1 << RBd) < (F + 2): | |
| RBd += 1 | |
| def m_raw(op: str, k: int) -> int: # raw mantissa (implicit = exp!=0) | |
| w = a if op == "a" else b | |
| return R(f"{op}.exp_nzero") if k == F else w(W - 1 - k) | |
| def m_a(k: int) -> int: # pre-normalized dividend | |
| return R(f"a.pn.s{mlzb - 1}.bit{k}.or") | |
| def m_b(k: int) -> int: # pre-normalized divisor | |
| return R(f"b.pn.s{mlzb - 1}.bit{k}.or") | |
| def eexp(op: str, k: int) -> int: | |
| w = a if op == "a" else b | |
| return R(f"{op}.eexp_lsb") if k == 0 else (w(E - k) if k < E else zero) | |
| def na(op: str, k: int) -> int: # effective exponent na = eexp - lz | |
| return R(f"{op}.neff.fa{k}.ha2.sum.layer2") if k < E + 2 else zero | |
| def MF(k: int) -> int: # aligned mantissa frame | |
| if k == 0: | |
| return R("round.g.or") # guard | |
| if k <= F: | |
| return R(f"norm.bit{k - 1}.or") # frac[k-1] | |
| return one # implicit | |
| def shifted(k: int) -> int: # frame right-shifted by rsh | |
| return R(f"rbar.s{RBd - 1}.bit{k}.or") | |
| def rem_prev(stage: int, k: int) -> int: | |
| return R(f"mant_div.stage{stage - 1}.mux.bit{k}.or") | |
| def shifted_low(stage: int, k: int) -> int: | |
| if stage == 0: | |
| return m_a(k) | |
| return rem_prev(stage, k - 1) if k >= 1 else zero | |
| def sh_top(stage: int) -> int: | |
| return rem_prev(stage, F) if stage >= 1 else zero | |
| def q(stage: int) -> int: | |
| return R(f"mant_div.stage{stage}.q") | |
| exp_a_field = [a(1 + i) for i in range(E)] | |
| exp_b_field = [b(1 + i) for i in range(E)] | |
| frac_a_field = [a(1 + E + i) for i in range(F)] | |
| frac_b_field = [b(1 + E + i) for i in range(F)] | |
| suffix = gate[len(prefix) + 1:] if gate != prefix else "" | |
| bias = (1 << (E - 1)) - 1 | |
| table = { | |
| "sign_xor.layer1.or": [a(0), b(0)], | |
| "sign_xor.layer1.nand": [a(0), b(0)], | |
| "sign_xor.layer2": [R("sign_xor.layer1.or"), R("sign_xor.layer1.nand")], | |
| "a.exp_zero": exp_a_field, "a.exp_nzero": exp_a_field, "a.exp_max": exp_a_field, | |
| "a.frac_nz": frac_a_field, "a.frac_zero": frac_a_field, | |
| "a.is_nan": [R("a.exp_max"), R("a.frac_nz")], | |
| "a.is_inf": [R("a.exp_max"), R("a.frac_zero")], | |
| "a.is_zero": [R("a.exp_zero"), R("a.frac_zero")], "a.nonzero": [R("a.is_zero")], | |
| "a.eexp_lsb": [a(E), R("a.exp_zero")], | |
| "b.exp_zero": exp_b_field, "b.exp_nzero": exp_b_field, "b.exp_max": exp_b_field, | |
| "b.frac_nz": frac_b_field, "b.frac_zero": frac_b_field, | |
| "b.is_nan": [R("b.exp_max"), R("b.frac_nz")], | |
| "b.is_inf": [R("b.exp_max"), R("b.frac_zero")], | |
| "b.is_zero": [R("b.exp_zero"), R("b.frac_zero")], "b.nonzero": [R("b.is_zero")], | |
| "b.eexp_lsb": [b(E), R("b.exp_zero")], | |
| "er.zero": [R(f"exp_r.fa{k}.ha2.sum.layer2") for k in range(E + 2)], | |
| "er.underflow": [R(f"exp_r.fa{E + 1}.ha2.sum.layer2"), R("er.zero")], | |
| "er.not_underflow": [R("er.underflow")], | |
| "too_deep": [R(f"rsh.bit{k}") for k in range(RBd, E + 2)], | |
| "not_deep": [R("too_deep")], | |
| "exp_r.zero": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E + 2)], | |
| "exp_r.not_neg": [R(f"exp_round.bit{E + 1}.xor.layer2")], | |
| "exp_r.and_low": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E)], | |
| "exp_r.ge_emax": [R(f"exp_round.bit{E}.xor.layer2"), R("exp_r.and_low")], | |
| "exp_r.overflow": [R("exp_r.not_neg"), R("exp_r.ge_emax")], | |
| "round.g.not_sel": [R("mant_div.stage0.q")], | |
| "round.g.and_a": [R(f"mant_div.stage{F + 2}.q"), R("round.g.not_sel")], | |
| "round.g.and_b": [R(f"mant_div.stage{F + 1}.q"), R("mant_div.stage0.q")], | |
| "round.g.or": [R("round.g.and_a"), R("round.g.and_b")], | |
| "round.rem_nz": [R(f"mant_div.stage{F + 2}.mux.bit{k}.or") for k in range(F + 1)], | |
| "round.q_extra": [R("mant_div.stage0.q"), R(f"mant_div.stage{F + 2}.q")], | |
| "round.sticky_orig": [R("round.rem_nz"), R("round.q_extra")], | |
| "round.guard": [shifted(0), R("not_deep")], | |
| "round.sticky": [R(f"rbar.s{RBd - 1}.sticky"), R("round.sticky_orig"), R("too_deep")], | |
| "round.rsl": [R("round.sticky"), R("sig.bit0")], | |
| "round.up": [R("round.guard"), R("round.rsl")], | |
| "sel.inputs_nan": [R("a.is_nan"), R("b.is_nan")], | |
| "sel.zero_zero": [R("a.is_zero"), R("b.is_zero")], | |
| "sel.inf_inf": [R("a.is_inf"), R("b.is_inf")], | |
| "sel.nan_cases": [R("sel.zero_zero"), R("sel.inf_inf")], | |
| "sel.nan": [R("sel.inputs_nan"), R("sel.nan_cases")], | |
| "sel.not_nan": [R("sel.nan")], | |
| "sel.inf_in": [R("a.is_inf"), R("b.is_zero")], | |
| "sel.zero_in": [R("a.is_zero"), R("b.is_inf")], | |
| "sel.inf_or_ovf": [R("sel.inf_in"), R("exp_r.overflow")], | |
| "sel.not_inf_path": [R("sel.inf_or_ovf")], | |
| "sel.inf": [R("sel.not_nan"), R("sel.inf_or_ovf")], | |
| "sel.zero_or_unf": [R("sel.zero_in"), R("sel.zero_in")], | |
| "sel.not_zero_unf": [R("sel.zero_or_unf")], | |
| "sel.zero": [R("sel.not_nan"), R("sel.not_inf_path"), R("sel.zero_or_unf")], | |
| "sel.norm": [R("sel.not_nan"), R("sel.not_inf_path"), R("sel.not_zero_unf")], | |
| "sign_out": [R("sign_xor.layer2"), R("sel.not_nan")], | |
| } | |
| if suffix in table: | |
| return table[suffix] | |
| m = re.match(r"^([ab])\.pn\.any_higher(\d+)$", suffix) # pre-normalize: LZC | |
| if m: | |
| op = m.group(1) | |
| pos = int(m.group(2)) | |
| return [m_raw(op, ML - 1 - t) for t in range(pos)] | |
| m = re.match(r"^([ab])\.pn\.is_highest(\d+)\.(not_higher|and)$", suffix) | |
| if m: | |
| op = m.group(1) | |
| pos = int(m.group(2)) | |
| if m.group(3) == "not_higher": | |
| return [R(f"{op}.pn.any_higher{pos}")] | |
| return [m_raw(op, ML - 1 - pos), R(f"{op}.pn.is_highest{pos}.not_higher")] | |
| m = re.match(r"^([ab])\.pn\.lz\.bit(\d+)$", suffix) | |
| if m: | |
| op = m.group(1) | |
| bbit = int(m.group(2)) | |
| srcs = [R(f"{op}.pn.is_highest{pos}.and") for pos in range(1, ML) if (pos >> bbit) & 1] | |
| return srcs if srcs else [zero] | |
| m = re.match(r"^([ab])\.pn\.s(\d+)\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) # left-barrel | |
| if m: | |
| op = m.group(1) | |
| bbit = int(m.group(2)) | |
| k = int(m.group(3)) | |
| kind = m.group(4) | |
| sel = R(f"{op}.pn.lz.bit{bbit}") | |
| def lin(kk: int) -> int: | |
| if kk < 0 or kk >= ML: | |
| return zero | |
| return m_raw(op, kk) if bbit == 0 else R(f"{op}.pn.s{bbit - 1}.bit{kk}.or") | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [lin(k), R(f"{op}.pn.s{bbit}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [lin(k - (1 << bbit)), sel] | |
| return [R(f"{op}.pn.s{bbit}.bit{k}.and_a"), R(f"{op}.pn.s{bbit}.bit{k}.and_b")] | |
| m = re.match(r"^([ab])\.nlz\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"{m.group(1)}.pn.lz.bit{int(m.group(2))}")] | |
| m = re.match(r"^([ab])\.neff\.fa(\d+)\.(.+)$", suffix) # na = eexp - lz | |
| if m: | |
| op = m.group(1) | |
| k = int(m.group(2)) | |
| b_sig = R(f"{op}.nlz.bit{k}") if k < mlzb else one | |
| cin = one if k == 0 else R(f"{op}.neff.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(3), f"{prefix}.{op}.neff.fa{k}", eexp(op, k), b_sig, cin, reg) | |
| m = re.match(r"^exp_nb\.bit(\d+)$", suffix) # ~nb | |
| if m: | |
| return [na("b", int(m.group(1)))] | |
| m = re.match(r"^exp_s1\.fa(\d+)\.(.+)$", suffix) # na + ~nb + q0 | |
| if m: | |
| k = int(m.group(1)) | |
| cin = q(0) if k == 0 else R(f"exp_s1.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_s1.fa{k}", | |
| na("a", k), R(f"exp_nb.bit{k}"), cin, reg) | |
| m = re.match(r"^exp_r\.fa(\d+)\.(.+)$", suffix) # + bias | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = R(f"exp_s1.fa{k}.ha2.sum.layer2") | |
| b_sig = one if (bias >> k) & 1 else zero | |
| cin = zero if k == 0 else R(f"exp_r.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_r.fa{k}", a_sig, b_sig, cin, reg) | |
| m = re.match(r"^exprc\.bit(\d+)$", suffix) # er clamped >= 0 | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"exp_r.fa{k}.ha2.sum.layer2"), R("er.not_underflow")] | |
| m = re.match(r"^ernot\.bit(\d+)$", suffix) # ~er | |
| if m: | |
| return [R(f"exp_r.fa{int(m.group(1))}.ha2.sum.layer2")] | |
| m = re.match(r"^subv\.fa(\d+)\.(.+)$", suffix) # 1 - er | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = one if (1 >> k) & 1 else zero | |
| cin = one if k == 0 else R(f"subv.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.subv.fa{k}", a_sig, R(f"ernot.bit{k}"), cin, reg) | |
| m = re.match(r"^rsh\.bit(\d+)$", suffix) # rsh = (1-er) AND underflow | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"subv.fa{k}.ha2.sum.layer2"), R("er.underflow")] | |
| m = re.match(r"^rbar\.s(\d+)\.(.+)$", suffix) # right-barrel on MF by rsh | |
| if m: | |
| j = int(m.group(1)) | |
| rest = m.group(2) | |
| sel = R(f"rsh.bit{j}") | |
| def rin(k: int) -> int: | |
| if k < 0 or k > F + 1: | |
| return zero | |
| return MF(k) if j == 0 else R(f"rbar.s{j - 1}.bit{k}.or") | |
| if rest == "drop": | |
| return [rin(k) for k in range(1 << j)] | |
| if rest == "st_and": | |
| return [sel, R(f"rbar.s{j}.drop")] | |
| if rest == "sticky": | |
| prev = zero if j == 0 else R(f"rbar.s{j - 1}.sticky") | |
| return [prev, R(f"rbar.s{j}.st_and")] | |
| mm = re.match(r"^bit(\d+)\.(not_sel|and_a|and_b|or)$", rest) | |
| if mm: | |
| k = int(mm.group(1)) | |
| kind = mm.group(2) | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [rin(k), R(f"rbar.s{j}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [rin(k + (1 << j)), sel] | |
| return [R(f"rbar.s{j}.bit{k}.and_a"), R(f"rbar.s{j}.bit{k}.and_b")] | |
| m = re.match(r"^sig\.bit(\d+)$", suffix) # mantissa frac bit (masked) | |
| if m: | |
| return [shifted(int(m.group(1)) + 1), R("not_deep")] | |
| m = re.match(r"^mant_div\.stage(\d+)\.(.+)$", suffix) | |
| if m: | |
| s = int(m.group(1)) | |
| rest = m.group(2) | |
| # Bit-cascade GE comparator: cascade bit i (MSB-first) compares | |
| # shifted-remainder bit F-i against divisor bit F-i. | |
| mm = re.match(r"^cmp_bc\.bit(\d+)(?:\.(.+))?$", rest) | |
| if mm: | |
| i = int(mm.group(1)) | |
| sub = mm.group(2) or "" | |
| sa = shifted_low(s, F - i) | |
| sb = m_b(F - i) | |
| if sub in ("gt", "lt", "eq.layer1.and", "eq.layer1.nor"): | |
| return [sa, sb] | |
| if sub == "eq": | |
| return [R(f"mant_div.stage{s}.cmp_bc.bit{i}.eq.layer1.and"), | |
| R(f"mant_div.stage{s}.cmp_bc.bit{i}.eq.layer1.nor")] | |
| mm = re.match(r"^cmp_bc\.cascade\.eq_prefix\.bit(\d+)$", rest) | |
| if mm: | |
| i = int(mm.group(1)) | |
| return [R(f"mant_div.stage{s}.cmp_bc.bit{j}.eq") for j in range(i)] | |
| mm = re.match(r"^cmp_bc\.cascade\.(gt|lt)\.bit(\d+)$", rest) | |
| if mm: | |
| kind, i = mm.group(1), int(mm.group(2)) | |
| return [R(f"mant_div.stage{s}.cmp_bc.cascade.eq_prefix.bit{i}"), | |
| R(f"mant_div.stage{s}.cmp_bc.bit{i}.{kind}")] | |
| if rest == "cmp_bc.gt": | |
| return ([R(f"mant_div.stage{s}.cmp_bc.bit0.gt")] | |
| + [R(f"mant_div.stage{s}.cmp_bc.cascade.gt.bit{i}") for i in range(1, F + 1)]) | |
| if rest == "cmp_bc.lt": | |
| return ([R(f"mant_div.stage{s}.cmp_bc.bit0.lt")] | |
| + [R(f"mant_div.stage{s}.cmp_bc.cascade.lt.bit{i}") for i in range(1, F + 1)]) | |
| if rest == "cmp_bc.eq": | |
| return [R(f"mant_div.stage{s}.cmp_bc.bit{i}.eq") for i in range(F + 1)] | |
| if rest == "cmp_bc.le.not_gt": | |
| return [R(f"mant_div.stage{s}.cmp_bc.gt")] | |
| if rest == "cmp_bc.le": | |
| return [R(f"mant_div.stage{s}.cmp_bc.le.not_gt")] | |
| if rest == "cmp.not_lt": | |
| return [R(f"mant_div.stage{s}.cmp_bc.lt")] | |
| if rest == "cmp": | |
| return [R(f"mant_div.stage{s}.cmp.not_lt")] | |
| if rest == "q": | |
| return [sh_top(s), R(f"mant_div.stage{s}.cmp")] | |
| mm = re.match(r"^sub\.not_d\.bit(\d+)$", rest) | |
| if mm: | |
| return [m_b(int(mm.group(1)))] | |
| mm = re.match(r"^sub\.fa(\d+)\.(.+)$", rest) | |
| if mm: | |
| k = int(mm.group(1)) | |
| a_sig = shifted_low(s, k) | |
| b_sig = R(f"mant_div.stage{s}.sub.not_d.bit{k}") | |
| cin = one if k == 0 else R(f"mant_div.stage{s}.sub.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(mm.group(2), f"{prefix}.mant_div.stage{s}.sub.fa{k}", | |
| a_sig, b_sig, cin, reg) | |
| mm = re.match(r"^mux\.bit(\d+)\.(not_sel|and_old|and_new|or)$", rest) | |
| if mm: | |
| k = int(mm.group(1)) | |
| kind = mm.group(2) | |
| if kind == "not_sel": | |
| return [q(s)] | |
| if kind == "and_old": | |
| return [shifted_low(s, k), R(f"mant_div.stage{s}.mux.bit{k}.not_sel")] | |
| if kind == "and_new": | |
| return [R(f"mant_div.stage{s}.sub.fa{k}.ha2.sum.layer2"), q(s)] | |
| return [R(f"mant_div.stage{s}.mux.bit{k}.and_old"), | |
| R(f"mant_div.stage{s}.mux.bit{k}.and_new")] | |
| return [] | |
| m = re.match(r"^norm\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| kind = m.group(2) | |
| if kind == "not_sel": | |
| return [q(0)] | |
| if kind == "and_a": | |
| return [q(F + 1 - k), R(f"norm.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [q(F - k), q(0)] | |
| return [R(f"norm.bit{k}.and_a"), R(f"norm.bit{k}.and_b")] | |
| # Fraction round-up incrementer and exponent rounding incrementer. | |
| m = re.match(r"^rnd\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| rest = m.group(2) | |
| cin = R("round.up") if k == 0 else R(f"rnd.bit{k - 1}.carry") | |
| f_bit = R(f"sig.bit{k}") | |
| if rest == "xor.layer1.or" or rest == "xor.layer1.nand": | |
| return [f_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"rnd.bit{k}.xor.layer1.or"), R(f"rnd.bit{k}.xor.layer1.nand")] | |
| if rest == "carry": | |
| return [f_bit, cin] | |
| m = re.match(r"^exp_round\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| rest = m.group(2) | |
| cin = R(f"rnd.bit{F - 1}.carry") if k == 0 else R(f"exp_round.bit{k - 1}.carry") | |
| e_bit = R(f"exprc.bit{k}") | |
| if rest == "xor.layer1.or" or rest == "xor.layer1.nand": | |
| return [e_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"exp_round.bit{k}.xor.layer1.or"), R(f"exp_round.bit{k}.xor.layer1.nand")] | |
| if rest == "carry": | |
| return [e_bit, cin] | |
| m = re.match(r"^exp_out\.bit(\d+)(\.norm_and)?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| if m.group(2): | |
| return [R("sel.norm"), R(f"exp_round.bit{k}.xor.layer2")] | |
| return [R("sel.nan"), R("sel.inf"), R(f"exp_out.bit{k}.norm_and")] | |
| m = re.match(r"^frac_out\.bit(\d+)(\.norm_and)?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| if m.group(2): | |
| return [R("sel.norm"), R(f"rnd.bit{k}.xor.layer2")] | |
| nan_sig = R("sel.nan") if k == F - 1 else zero | |
| return [nan_sig, R(f"frac_out.bit{k}.norm_and")] | |
| return None | |
| def infer_float_fma_inputs(gate: str, reg: SignalRegistry, family: str, | |
| exp_bits: int, frac_bits: int) -> Optional[List[int]]: | |
| """Complete wiring for the self-contained fused-multiply-add pipeline.""" | |
| E, F = exp_bits, frac_bits | |
| W = 1 + E + F | |
| p = F + 1 | |
| PW = 2 * p | |
| WF = 3 * p + 4 | |
| MG = WF + 1 | |
| prefix = f"{family}.fma" | |
| if gate != prefix and not gate.startswith(prefix + "."): | |
| return None | |
| bias = (1 << (E - 1)) - 1 | |
| for i in range(W): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| reg.register(f"$c[{i}]") | |
| def a(i: int) -> int: | |
| return reg.get_id(f"$a[{i}]") | |
| def b(i: int) -> int: | |
| return reg.get_id(f"$b[{i}]") | |
| def c(i: int) -> int: | |
| return reg.get_id(f"$c[{i}]") | |
| def R(name: str) -> int: | |
| return reg.register(f"{prefix}.{name}") | |
| zero = reg.get_id("#0") | |
| one = reg.get_id("#1") | |
| WOP = {"a": a, "b": b, "c": c} | |
| ML = F + 1 | |
| mlzb = 0 | |
| while (1 << mlzb) <= ML: | |
| mlzb += 1 | |
| SB = 0 | |
| while (1 << SB) <= WF: | |
| SB += 1 | |
| NB = 0 | |
| while (1 << NB) <= WF: | |
| NB += 1 | |
| RBd = 0 | |
| while (1 << RBd) < (F + 2): | |
| RBd += 1 | |
| def m_raw(op: str, k: int) -> int: | |
| return R(f"{op}.exp_nzero") if k == F else WOP[op](W - 1 - k) | |
| def Mn(op: str, k: int) -> int: | |
| return R(f"{op}.pn.s{mlzb - 1}.bit{k}.or") | |
| def eexp(op: str, k: int) -> int: | |
| w = WOP[op] | |
| return R(f"{op}.eexp_lsb") if k == 0 else (w(E - k) if k < E else zero) | |
| def na(op: str, k: int) -> int: # signed, sign-extended from bit E+1 | |
| return R(f"{op}.neff.fa{min(k, E + 1)}.ha2.sum.layer2") | |
| def Pbit(k: int) -> int: | |
| return R(f"acc.s{F - 1}.fa{k}.ha2.sum.layer2") | |
| def Pt(m: int) -> int: # top-aligned product field bit | |
| k = m - (WF - PW) | |
| return Pbit(k) if 0 <= k < PW else zero | |
| def Ct(m: int) -> int: # top-aligned addend field bit | |
| k = m - (WF - p) | |
| return Mn("c", k) if 0 <= k < p else zero | |
| sp = R("sign_p.layer2") | |
| sc = c(0) | |
| q1c = (1 - bias) & ((1 << (E + 3)) - 1) # constant nab + (1-bias) | |
| suffix = gate[len(prefix) + 1:] if gate != prefix else "" | |
| table = { | |
| "sign_p.layer1.or": [a(0), b(0)], | |
| "sign_p.layer1.nand": [a(0), b(0)], | |
| "sign_p.layer2": [R("sign_p.layer1.or"), R("sign_p.layer1.nand")], | |
| } | |
| for op in ("a", "b", "c"): | |
| w = WOP[op] | |
| expf = [w(1 + i) for i in range(E)] | |
| fracf = [w(1 + E + i) for i in range(F)] | |
| table[f"{op}.exp_zero"] = expf | |
| table[f"{op}.exp_nzero"] = expf | |
| table[f"{op}.exp_max"] = expf | |
| table[f"{op}.frac_nz"] = fracf | |
| table[f"{op}.frac_zero"] = fracf | |
| table[f"{op}.is_nan"] = [R(f"{op}.exp_max"), R(f"{op}.frac_nz")] | |
| table[f"{op}.is_inf"] = [R(f"{op}.exp_max"), R(f"{op}.frac_zero")] | |
| table[f"{op}.is_zero"] = [R(f"{op}.exp_zero"), R(f"{op}.frac_zero")] | |
| table[f"{op}.nonzero"] = [R(f"{op}.is_zero")] | |
| table[f"{op}.eexp_lsb"] = [w(E), R(f"{op}.exp_zero")] | |
| if suffix in table: | |
| return table[suffix] | |
| # --- pre-normalizers (a, b, c) --- | |
| m = re.match(r"^([abc])\.pn\.any_higher(\d+)$", suffix) | |
| if m: | |
| op, pos = m.group(1), int(m.group(2)) | |
| return [m_raw(op, ML - 1 - t) for t in range(pos)] | |
| m = re.match(r"^([abc])\.pn\.is_highest(\d+)\.(not_higher|and)$", suffix) | |
| if m: | |
| op, pos = m.group(1), int(m.group(2)) | |
| if m.group(3) == "not_higher": | |
| return [R(f"{op}.pn.any_higher{pos}")] | |
| return [m_raw(op, ML - 1 - pos), R(f"{op}.pn.is_highest{pos}.not_higher")] | |
| m = re.match(r"^([abc])\.pn\.lz\.bit(\d+)$", suffix) | |
| if m: | |
| op, bbit = m.group(1), int(m.group(2)) | |
| srcs = [R(f"{op}.pn.is_highest{pos}.and") for pos in range(1, ML) if (pos >> bbit) & 1] | |
| return srcs if srcs else [zero] | |
| m = re.match(r"^([abc])\.pn\.s(\d+)\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) | |
| if m: | |
| op, bbit, k, kind = m.group(1), int(m.group(2)), int(m.group(3)), m.group(4) | |
| sel = R(f"{op}.pn.lz.bit{bbit}") | |
| def lin(kk: int) -> int: | |
| if kk < 0 or kk >= ML: | |
| return zero | |
| return m_raw(op, kk) if bbit == 0 else R(f"{op}.pn.s{bbit - 1}.bit{kk}.or") | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [lin(k), R(f"{op}.pn.s{bbit}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [lin(k - (1 << bbit)), sel] | |
| return [R(f"{op}.pn.s{bbit}.bit{k}.and_a"), R(f"{op}.pn.s{bbit}.bit{k}.and_b")] | |
| m = re.match(r"^([abc])\.nlz\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"{m.group(1)}.pn.lz.bit{int(m.group(2))}")] | |
| m = re.match(r"^([abc])\.neff\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| op, k = m.group(1), int(m.group(2)) | |
| b_sig = R(f"{op}.nlz.bit{k}") if k < mlzb else one | |
| cin = one if k == 0 else R(f"{op}.neff.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(3), f"{prefix}.{op}.neff.fa{k}", eexp(op, k), b_sig, cin, reg) | |
| # --- multiplier --- | |
| m = re.match(r"^pp\.a(\d+)b(\d+)$", suffix) | |
| if m: | |
| return [Mn("a", int(m.group(1))), Mn("b", int(m.group(2)))] | |
| m = re.match(r"^acc\.s(\d+)\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| t, k = int(m.group(1)), int(m.group(2)) | |
| row = t + 1 | |
| if t == 0: | |
| a_sig = R(f"pp.a{k}b0") if k <= F else zero | |
| else: | |
| a_sig = R(f"acc.s{t - 1}.fa{k}.ha2.sum.layer2") | |
| b_sig = R(f"pp.a{k - row}b{row}") if row <= k <= row + F else zero | |
| cin = zero if k == 0 else R(f"acc.s{t}.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(3), f"{prefix}.acc.s{t}.fa{k}", a_sig, b_sig, cin, reg) | |
| # --- exponent alignment --- | |
| m = re.match(r"^nab\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| cin = zero if k == 0 else R(f"nab.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.nab.fa{k}", na("a", k), na("b", k), cin, reg) | |
| m = re.match(r"^q1\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = R(f"nab.fa{k}.ha2.sum.layer2") | |
| b_sig = one if (q1c >> k) & 1 else zero | |
| cin = zero if k == 0 else R(f"q1.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.q1.fa{k}", a_sig, b_sig, cin, reg) | |
| if suffix == "prod_zero": | |
| return [R("a.is_zero"), R("b.is_zero")] | |
| if suffix == "not_prod_zero": | |
| return [R("prod_zero")] | |
| if suffix == "not_c_zero": | |
| return [R("c.is_zero")] | |
| m = re.match(r"^q1e\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| q1b = R(f"q1.fa{k}.ha2.sum.layer2") | |
| return [R("prod_zero"), q1b] if k in (E + 1, E + 2) else [R("not_prod_zero"), q1b] | |
| m = re.match(r"^q2e\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| return [R("c.is_zero"), na("c", k)] if k in (E + 1, E + 2) else [R("not_c_zero"), na("c", k)] | |
| m = re.match(r"^ncnot\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"q2e.bit{int(m.group(1))}")] | |
| m = re.match(r"^d\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = R(f"q1e.bit{k}") | |
| cin = one if k == 0 else R(f"d.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.d.fa{k}", a_sig, R(f"ncnot.bit{k}"), cin, reg) | |
| neg_d = R(f"d.fa{E + 2}.ha2.sum.layer2") | |
| if suffix == "not_neg_d": | |
| return [neg_d] | |
| m = re.match(r"^dxor\.bit(\d+)\.(a|b)$", suffix) | |
| if m: | |
| return [R(f"d.fa{int(m.group(1))}.ha2.sum.layer2"), neg_d] | |
| m = re.match(r"^dxor\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"dxor.bit{k}.a"), R(f"dxor.bit{k}.b")] | |
| m = re.match(r"^dabs\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| cin = neg_d if k == 0 else R(f"dabs.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.dabs.fa{k}", R(f"dxor.bit{k}"), zero, cin, reg) | |
| m = re.match(r"^sbig\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = R(f"dabs.fa{k}.ha2.sum.layer2") | |
| cin = one if k == 0 else R(f"sbig.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.sbig.fa{k}", a_sig, zero, cin, reg) | |
| if suffix == "sbig.high": | |
| return [R(f"sbig.fa{k}.ha2.sum.layer2") for k in range(SB, E + 3)] | |
| if suffix == "rp.deep": | |
| return [neg_d, R("sbig.high")] | |
| if suffix == "rc.deep": | |
| return [R("not_neg_d"), R("sbig.high")] | |
| m = re.match(r"^r([pc])\.bit(\d+)\.(and_big|and_one|or)$", suffix) | |
| if m: | |
| which, k, kind = m.group(1), int(m.group(2)), m.group(3) | |
| big = R(f"sbig.fa{k}.ha2.sum.layer2") | |
| one_bit = one if k == 0 else zero | |
| sel_big = neg_d if which == "p" else R("not_neg_d") # rp big when neg_d; rc big when ~neg_d | |
| sel_one = R("not_neg_d") if which == "p" else neg_d | |
| if kind == "and_big": | |
| return [big, sel_big] | |
| if kind == "and_one": | |
| return [one_bit, sel_one] | |
| return [R(f"r{which}.bit{k}.and_big"), R(f"r{which}.bit{k}.and_one")] | |
| m = re.match(r"^q1p\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| b_sig = one if k == 1 else zero # + 2 | |
| cin = zero if k == 0 else R(f"q1p.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.q1p.fa{k}", R(f"q1e.bit{k}"), b_sig, cin, reg) | |
| m = re.match(r"^q2p\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| b_sig = one if k == 1 else zero # + 2 | |
| cin = zero if k == 0 else R(f"q2p.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.q2p.fa{k}", R(f"q2e.bit{k}"), b_sig, cin, reg) | |
| m = re.match(r"^atop\.bit(\d+)\.(and_p|and_c|or)$", suffix) | |
| if m: | |
| k, kind = int(m.group(1)), m.group(2) | |
| if kind == "and_p": | |
| return [R(f"q1p.fa{k}.ha2.sum.layer2"), R("not_neg_d")] | |
| if kind == "and_c": | |
| return [R(f"q2p.fa{k}.ha2.sum.layer2"), neg_d] | |
| return [R(f"atop.bit{k}.and_p"), R(f"atop.bit{k}.and_c")] | |
| # --- align shifters (pf: product, cf: addend) --- | |
| m = re.match(r"^(pf|cf)\.s(\d+)\.(.+)$", suffix) | |
| if m: | |
| tag, j, rest = m.group(1), int(m.group(2)), m.group(3) | |
| rsel = R(f"r{'p' if tag == 'pf' else 'c'}.bit{j}.or") | |
| topfn = Pt if tag == "pf" else Ct | |
| def sin(k: int) -> int: | |
| if k < 0 or k >= WF: | |
| return zero | |
| return topfn(k) if j == 0 else R(f"{tag}.s{j - 1}.bit{k}.or") | |
| if rest == "drop": | |
| return [sin(k) for k in range(1 << j)] | |
| if rest == "st_and": | |
| return [rsel, R(f"{tag}.s{j}.drop")] | |
| if rest == "sticky": | |
| prev = zero if j == 0 else R(f"{tag}.s{j - 1}.sticky") | |
| return [prev, R(f"{tag}.s{j}.st_and")] | |
| mm = re.match(r"^bit(\d+)\.(not_sel|and_a|and_b|or)$", rest) | |
| if mm: | |
| k, kind = int(mm.group(1)), mm.group(2) | |
| if kind == "not_sel": | |
| return [rsel] | |
| if kind == "and_a": | |
| return [sin(k), R(f"{tag}.s{j}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [sin(k + (1 << j)), rsel] | |
| return [R(f"{tag}.s{j}.bit{k}.and_a"), R(f"{tag}.s{j}.bit{k}.and_b")] | |
| m = re.match(r"^(pf|cf)\.not_deep$", suffix) | |
| if m: | |
| tag = m.group(1) | |
| return [R(f"r{'p' if tag == 'pf' else 'c'}.deep")] | |
| m = re.match(r"^(pf|cf)\.field\.bit(\d+)$", suffix) | |
| if m: | |
| tag, k = m.group(1), int(m.group(2)) | |
| return [R(f"{tag}.s{SB - 1}.bit{k}.or"), R(f"{tag}.not_deep")] | |
| m = re.match(r"^(pf|cf)\.nz$", suffix) | |
| if m: | |
| tag = m.group(1) | |
| topfn = Pt if tag == "pf" else Ct | |
| return [topfn(k) for k in range(WF)] | |
| m = re.match(r"^(pf|cf)\.deep_nz$", suffix) | |
| if m: | |
| tag = m.group(1) | |
| return [R(f"r{'p' if tag == 'pf' else 'c'}.deep"), R(f"{tag}.nz")] | |
| m = re.match(r"^(pf|cf)\.sticky$", suffix) | |
| if m: | |
| tag = m.group(1) | |
| return [R(f"{tag}.s{SB - 1}.sticky"), R(f"{tag}.deep_nz")] | |
| # --- signed magnitude combine --- | |
| def PF(k: int) -> int: | |
| return R(f"pf.field.bit{k}") if k < WF else zero | |
| def CF(k: int) -> int: | |
| return R(f"cf.field.bit{k}") if k < WF else zero | |
| def Dbit(k: int) -> int: | |
| return R(f"dsum.fa{k}.ha2.sum.layer2") | |
| sign_D = Dbit(WF) | |
| combine = { | |
| "eff_sub.layer1.or": [sp, sc], | |
| "eff_sub.layer1.nand": [sp, sc], | |
| "eff_sub": [R("eff_sub.layer1.or"), R("eff_sub.layer1.nand")], | |
| "eff_add": [R("eff_sub")], | |
| "s_tail": [R("pf.sticky"), R("cf.sticky")], | |
| "not_s_tail": [R("s_tail")], | |
| "borrow_in": [R("eff_sub"), R("not_s_tail")], | |
| "ge": [sign_D], | |
| "not_ge": [R("ge")], | |
| "mag.nz": [R(f"mag.bit{k}.or") for k in range(MG)], | |
| "er.zero": [R(f"er.fa{k}.ha2.sum.layer2") for k in range(E + 3)], | |
| "er.underflow": [R(f"er.fa{E + 2}.ha2.sum.layer2"), R("er.zero")], | |
| "er.not_underflow": [R("er.underflow")], | |
| } | |
| if suffix in combine: | |
| return combine[suffix] | |
| m = re.match(r"^cfnot\.bit(\d+)$", suffix) | |
| if m: | |
| return [CF(int(m.group(1)))] | |
| m = re.match(r"^dsum\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = PF(k) | |
| b_sig = R(f"cfnot.bit{k}") if k < WF else one | |
| cin = R("borrow_in") if k == 0 else R(f"dsum.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.dsum.fa{k}", a_sig, b_sig, cin, reg) | |
| m = re.match(r"^dmagx\.bit(\d+)\.(a|b)$", suffix) | |
| if m: | |
| return [Dbit(int(m.group(1))), sign_D] | |
| m = re.match(r"^dmagx\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"dmagx.bit{k}.a"), R(f"dmagx.bit{k}.b")] | |
| m = re.match(r"^dmag\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| cin = sign_D if k == 0 else R(f"dmag.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.dmag.fa{k}", R(f"dmagx.bit{k}"), zero, cin, reg) | |
| m = re.match(r"^asum\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| cin = zero if k == 0 else R(f"asum.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.asum.fa{k}", PF(k), CF(k), cin, reg) | |
| m = re.match(r"^mag\.bit(\d+)\.(and_sub|and_add|or)$", suffix) | |
| if m: | |
| k, kind = int(m.group(1)), m.group(2) | |
| if kind == "and_sub": | |
| return [R(f"dmag.fa{k}.ha2.sum.layer2"), R("eff_sub")] | |
| if kind == "and_add": | |
| return [R(f"asum.fa{k}.ha2.sum.layer2"), R("eff_add")] | |
| return [R(f"mag.bit{k}.and_sub"), R(f"mag.bit{k}.and_add")] | |
| # --- leading-zero count + normalize (MG bits, MSB at MG-1) --- | |
| def magbit(k: int) -> int: | |
| return R(f"mag.bit{k}.or") | |
| m = re.match(r"^lz\.any_higher(\d+)$", suffix) | |
| if m: | |
| pos = int(m.group(1)) | |
| return [magbit(MG - 1 - t) for t in range(pos)] | |
| m = re.match(r"^lz\.is_highest(\d+)\.(not_higher|and)$", suffix) | |
| if m: | |
| pos = int(m.group(1)) | |
| if m.group(2) == "not_higher": | |
| return [R(f"lz.any_higher{pos}")] | |
| return [magbit(MG - 1 - pos), R(f"lz.is_highest{pos}.not_higher")] | |
| m = re.match(r"^lz\.bit(\d+)$", suffix) | |
| if m: | |
| bbit = int(m.group(1)) | |
| srcs = [R(f"lz.is_highest{pos}.and") for pos in range(1, MG) if (pos >> bbit) & 1] | |
| return srcs if srcs else [zero] | |
| m = re.match(r"^nrm\.s(\d+)\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) | |
| if m: | |
| j, k, kind = int(m.group(1)), int(m.group(2)), m.group(3) | |
| sel = R(f"lz.bit{j}") | |
| def nin(kk: int) -> int: | |
| if kk < 0 or kk >= MG: | |
| return zero | |
| return magbit(kk) if j == 0 else R(f"nrm.s{j - 1}.bit{kk}.or") | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [nin(k), R(f"nrm.s{j}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [nin(k - (1 << j)), sel] | |
| return [R(f"nrm.s{j}.bit{k}.and_a"), R(f"nrm.s{j}.bit{k}.and_b")] | |
| # --- er = atop - lz --- | |
| def atopbit(k: int) -> int: | |
| return R(f"atop.bit{k}.or") | |
| m = re.match(r"^nlzf\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"lz.bit{k}")] if k < NB else [zero] | |
| m = re.match(r"^er\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| cin = one if k == 0 else R(f"er.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.er.fa{k}", atopbit(k), R(f"nlzf.bit{k}"), cin, reg) | |
| m = re.match(r"^exprc\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"er.fa{k}.ha2.sum.layer2"), R("er.not_underflow")] | |
| # --- subnormal-aware round on the normalized significand --- | |
| def nrmbit(k: int) -> int: | |
| return R(f"nrm.s{NB - 1}.bit{k}.or") | |
| def MF(k: int) -> int: # mantissa frame | |
| if k == 0: | |
| return nrmbit(MG - 2 - F) # guard | |
| if k <= F: | |
| return nrmbit(MG - 2 - F + k) # frac[k-1] | |
| return nrmbit(MG - 1) # implicit | |
| def shifted(k: int) -> int: | |
| return R(f"dsh.s{RBd - 1}.bit{k}.or") | |
| rnd_round = { | |
| "round.sticky_lo": [nrmbit(k) for k in range(MG - F - 2)], | |
| "round.sticky_pre": [R("round.sticky_lo"), R("s_tail")], | |
| "too_deep": [R(f"rsh.bit{k}") for k in range(RBd, E + 3)], | |
| "not_deep": [R("too_deep")], | |
| "round.guard": [shifted(0), R("not_deep")], | |
| "round.sticky": [R("round.sticky_pre"), R(f"dsh.s{RBd - 1}.sticky"), R("too_deep")], | |
| "round.rsl": [R("round.sticky"), R("sig.bit0")], | |
| "round.up": [R("round.guard"), R("round.rsl")], | |
| } | |
| if suffix in rnd_round: | |
| return rnd_round[suffix] | |
| m = re.match(r"^ernot\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"er.fa{int(m.group(1))}.ha2.sum.layer2")] | |
| m = re.match(r"^subv\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = one if k == 0 else zero | |
| cin = one if k == 0 else R(f"subv.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.subv.fa{k}", a_sig, R(f"ernot.bit{k}"), cin, reg) | |
| m = re.match(r"^rsh\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"subv.fa{k}.ha2.sum.layer2"), R("er.underflow")] | |
| m = re.match(r"^dsh\.s(\d+)\.(.+)$", suffix) | |
| if m: | |
| j, rest = int(m.group(1)), m.group(2) | |
| sel = R(f"rsh.bit{j}") | |
| def din(k: int) -> int: | |
| if k < 0 or k > F + 1: | |
| return zero | |
| return MF(k) if j == 0 else R(f"dsh.s{j - 1}.bit{k}.or") | |
| if rest == "drop": | |
| return [din(k) for k in range(1 << j)] | |
| if rest == "st_and": | |
| return [sel, R(f"dsh.s{j}.drop")] | |
| if rest == "sticky": | |
| prev = zero if j == 0 else R(f"dsh.s{j - 1}.sticky") | |
| return [prev, R(f"dsh.s{j}.st_and")] | |
| mm = re.match(r"^bit(\d+)\.(not_sel|and_a|and_b|or)$", rest) | |
| if mm: | |
| k, kind = int(mm.group(1)), mm.group(2) | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [din(k), R(f"dsh.s{j}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [din(k + (1 << j)), sel] | |
| return [R(f"dsh.s{j}.bit{k}.and_a"), R(f"dsh.s{j}.bit{k}.and_b")] | |
| m = re.match(r"^sig\.bit(\d+)$", suffix) | |
| if m: | |
| return [shifted(int(m.group(1)) + 1), R("not_deep")] | |
| m = re.match(r"^rnd\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k, rest = int(m.group(1)), m.group(2) | |
| cin = R("round.up") if k == 0 else R(f"rnd.bit{k - 1}.carry") | |
| f_bit = R(f"sig.bit{k}") | |
| if rest in ("xor.layer1.or", "xor.layer1.nand", "carry"): | |
| return [f_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"rnd.bit{k}.xor.layer1.or"), R(f"rnd.bit{k}.xor.layer1.nand")] | |
| m = re.match(r"^exp_round\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k, rest = int(m.group(1)), m.group(2) | |
| cin = R(f"rnd.bit{F - 1}.carry") if k == 0 else R(f"exp_round.bit{k - 1}.carry") | |
| e_bit = R(f"exprc.bit{k}") | |
| if rest in ("xor.layer1.or", "xor.layer1.nand", "carry"): | |
| return [e_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"exp_round.bit{k}.xor.layer1.or"), R(f"exp_round.bit{k}.xor.layer1.nand")] | |
| # --- specials, sign selection, output pack --- | |
| specials = { | |
| "p_inf": [R("a.is_inf"), R("b.is_inf")], | |
| "not_p_inf": [R("p_inf")], | |
| "inf0a": [R("a.is_inf"), R("b.is_zero")], | |
| "inf0b": [R("b.is_inf"), R("a.is_zero")], | |
| "inf0": [R("inf0a"), R("inf0b")], | |
| "pc_sdiff.or": [sp, sc], | |
| "pc_sdiff.nand": [sp, sc], | |
| "pc_sdiff": [R("pc_sdiff.or"), R("pc_sdiff.nand")], | |
| "infinf_nan": [R("p_inf"), R("c.is_inf"), R("pc_sdiff")], | |
| "in_nan": [R("a.is_nan"), R("b.is_nan"), R("c.is_nan")], | |
| "nan": [R("in_nan"), R("inf0"), R("infinf_nan")], | |
| "not_nan": [R("nan")], | |
| "any_inf": [R("p_inf"), R("c.is_inf")], | |
| "not_any_inf": [R("any_inf")], | |
| "er.and_low": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E)], | |
| "er.hi": [R(f"exp_round.bit{E}.xor.layer2"), R(f"exp_round.bit{E + 1}.xor.layer2"), | |
| R(f"exp_round.bit{E + 2}.xor.layer2")], | |
| "er.ge_emax": [R("er.and_low"), R("er.hi")], | |
| "overflow": [R("er.ge_emax"), R("er.not_underflow")], | |
| "inf_or_ovf": [R("any_inf"), R("overflow")], | |
| "not_inf_path": [R("inf_or_ovf")], | |
| "inf": [R("not_nan"), R("inf_or_ovf")], | |
| "res_zero": [R(f"mag.bit{k}.or") for k in range(MG)], | |
| "not_res_zero": [R("res_zero")], | |
| "zero": [R("not_nan"), R("not_inf_path"), R("res_zero")], | |
| "norm": [R("not_nan"), R("not_inf_path"), R("not_res_zero")], | |
| "fin_sign.sub.and_ge": [R("ge"), sp], | |
| "fin_sign.sub.and_lt": [R("not_ge"), sc], | |
| "fin_sign.sub": [R("fin_sign.sub.and_ge"), R("fin_sign.sub.and_lt")], | |
| "fin_sign.and_sub": [R("fin_sign.sub"), R("eff_sub")], | |
| "fin_sign.and_add": [sp, R("eff_add")], | |
| "fin_sign": [R("fin_sign.and_sub"), R("fin_sign.and_add")], | |
| "p_zero": [R("a.is_zero"), R("b.is_zero")], | |
| "bothzero": [R("p_zero"), R("c.is_zero")], | |
| "negzero": [R("bothzero"), sp, sc], | |
| "sign_z.fin": [R("fin_sign"), R("not_res_zero")], | |
| "sign_z": [R("sign_z.fin"), R("negzero")], | |
| "sign.term_p": [R("p_inf"), sp], | |
| "sign.term_c": [R("not_p_inf"), R("c.is_inf"), sc], | |
| "sign.term_z": [R("not_any_inf"), R("sign_z")], | |
| "sign_pre": [R("sign.term_p"), R("sign.term_c"), R("sign.term_z")], | |
| "sign_out": [R("sign_pre"), R("not_nan")], | |
| } | |
| if suffix in specials: | |
| return specials[suffix] | |
| m = re.match(r"^exp_out\.bit(\d+)(\.norm_and)?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| if m.group(2): | |
| return [R("norm"), R(f"exp_round.bit{k}.xor.layer2")] | |
| return [R("nan"), R("inf"), R(f"exp_out.bit{k}.norm_and")] | |
| m = re.match(r"^frac_out\.bit(\d+)(\.norm_and)?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| if m.group(2): | |
| return [R("norm"), R(f"rnd.bit{k}.xor.layer2")] | |
| nan_sig = R("nan") if k == F - 1 else zero | |
| return [nan_sig, R(f"frac_out.bit{k}.norm_and")] | |
| return None | |
| def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str, | |
| exp_bits: int, frac_bits: int) -> Optional[List[int]]: | |
| """Complete wiring for the self-contained float addition pipeline.""" | |
| E, F = exp_bits, frac_bits | |
| W = 1 + E + F | |
| P = E + F | |
| FRAME = F + 3 | |
| RES = F + 4 | |
| prefix = f"{family}.add" | |
| if gate != prefix and not gate.startswith(prefix + "."): | |
| return None | |
| for i in range(W): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| def a(i: int) -> int: | |
| return reg.get_id(f"$a[{i}]") | |
| def b(i: int) -> int: | |
| return reg.get_id(f"$b[{i}]") | |
| def R(name: str) -> int: | |
| return reg.register(f"{prefix}.{name}") | |
| zero = reg.get_id("#0") | |
| one = reg.get_id("#1") | |
| num_stages = 0 | |
| while (1 << num_stages) - 1 < FRAME: | |
| num_stages += 1 | |
| nlz_bits = 0 | |
| while (1 << nlz_bits) <= RES: | |
| nlz_bits += 1 | |
| def exp_l(k: int) -> int: | |
| return R(f"swap.exp_l.bit{k}.or") | |
| def mant_l(k: int) -> int: | |
| return R(f"swap.mant_l.bit{k}.or") | |
| def mant_s(k: int) -> int: | |
| return R(f"swap.mant_s.bit{k}.or") | |
| # Frame (LSB-first): bit0 round, bit1 guard, bits 2..F+1 fraction, | |
| # bit F+2 implicit 1. | |
| def l_ext(k: int) -> int: | |
| if k == F + 2: | |
| return R("swap.impl_l.or") # implicit bit: 1 normal, 0 subnormal | |
| if 2 <= k <= F + 1: | |
| return mant_l(k - 2) | |
| return zero | |
| def s_frame_in(k: int) -> int: | |
| if k == F + 2: | |
| return R("swap.impl_s.or") | |
| if 2 <= k <= F + 1: | |
| return mant_s(k - 2) | |
| return zero | |
| def align_in(j: int, k: int) -> int: | |
| if k >= FRAME: | |
| return zero | |
| return s_frame_in(k) if j == 0 else R(f"align.s{j - 1}.bit{k}.or") | |
| def d_bit(k: int) -> int: | |
| return R(f"exp_diff.fa{k}.ha2.sum.layer2") | |
| def res_bit(k: int) -> int: | |
| return R(f"res.bit{k}.or") | |
| def shifted(k: int) -> int: | |
| return R(f"norm.s{nlz_bits - 1}.bit{k}.or") | |
| def trunc(k: int) -> int: | |
| return shifted(k + 3) | |
| exp_a_field = [a(1 + i) for i in range(E)] | |
| exp_b_field = [b(1 + i) for i in range(E)] | |
| frac_a_field = [a(1 + E + i) for i in range(F)] | |
| frac_b_field = [b(1 + E + i) for i in range(F)] | |
| suffix = gate[len(prefix) + 1:] if gate != prefix else "" | |
| table = { | |
| "sign_xor.layer1.or": [a(0), b(0)], | |
| "sign_xor.layer1.nand": [a(0), b(0)], | |
| "sign_xor.layer2": [R("sign_xor.layer1.or"), R("sign_xor.layer1.nand")], | |
| "a.exp_zero": exp_a_field, "a.exp_nzero": exp_a_field, | |
| "a.exp_max": exp_a_field, | |
| "a.frac_nz": frac_a_field, "a.frac_zero": frac_a_field, | |
| "a.is_nan": [R("a.exp_max"), R("a.frac_nz")], | |
| "a.is_inf": [R("a.exp_max"), R("a.frac_zero")], | |
| "a.eexp_lsb": [a(E), R("a.exp_zero")], | |
| "a.is_zero": [R("a.exp_zero"), R("a.frac_zero")], | |
| "a.nonzero": [R("a.is_zero")], | |
| "b.exp_zero": exp_b_field, "b.exp_nzero": exp_b_field, | |
| "b.exp_max": exp_b_field, | |
| "b.frac_nz": frac_b_field, "b.frac_zero": frac_b_field, | |
| "b.is_nan": [R("b.exp_max"), R("b.frac_nz")], | |
| "b.is_inf": [R("b.exp_max"), R("b.frac_zero")], | |
| "b.eexp_lsb": [b(E), R("b.exp_zero")], | |
| "b.is_zero": [R("b.exp_zero"), R("b.frac_zero")], | |
| "b.nonzero": [R("b.is_zero")], | |
| "swap.not_sel": [R("pl.lt")], | |
| "align.not_sat": [R("align.sat")], | |
| "align.sticky_final": [R(f"align.s{num_stages - 1}.sticky"), R("align.sat")], | |
| "align.not_sticky": [R("align.sticky_final")], | |
| "res.not_sub": [R("sign_xor.layer2")], | |
| "lzc.nz": [res_bit(k) for k in range(RES)], | |
| "lzc.not_nz": [R("lzc.nz")], | |
| # RNE: guard shifted[2], round shifted[1], extra low shifted[0] plus | |
| # the alignment sticky; round-up = guard AND (LSB OR round OR low OR St). | |
| "round.tail": [shifted(1), shifted(0), R("align.sticky_final")], | |
| "round.rsl": [R("round.tail"), trunc(0)], | |
| "round.up": [shifted(2), R("round.rsl")], | |
| "er.zero": [R(f"exp_r.fa{k}.ha2.sum.layer2") for k in range(E + 2)], | |
| "er.underflow": [R(f"exp_r.fa{E + 1}.ha2.sum.layer2"), R("er.zero")], | |
| "er.not_underflow": [R("er.underflow")], | |
| "exp_r.zero": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E + 2)], | |
| "exp_r.underflow": [R(f"exp_round.bit{E + 1}.xor.layer2"), R("exp_r.zero")], | |
| "exp_r.not_neg": [R(f"exp_round.bit{E + 1}.xor.layer2")], | |
| "exp_r.and_low": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E)], | |
| "exp_r.ge_emax": [R(f"exp_round.bit{E}.xor.layer2"), R("exp_r.and_low")], | |
| "exp_r.overflow": [R("exp_r.not_neg"), R("exp_r.ge_emax")], | |
| "sel.inf_opp": [R("a.is_inf"), R("b.is_inf"), R("sign_xor.layer2")], | |
| "sel.inputs_nan": [R("a.is_nan"), R("b.is_nan")], | |
| "sel.nan": [R("sel.inputs_nan"), R("sel.inf_opp")], | |
| "sel.not_nan": [R("sel.nan")], | |
| "sel.inf_ops": [R("a.is_inf"), R("b.is_inf")], | |
| "sel.not_inf_ops": [R("sel.inf_ops")], | |
| "sel.inf_operand": [R("sel.not_nan"), R("sel.inf_ops")], | |
| "sel.both_zero": [R("sel.not_nan"), R("sel.not_inf_ops"), | |
| R("a.is_zero"), R("b.is_zero")], | |
| "sel.a_zero_pass": [R("sel.not_nan"), R("sel.not_inf_ops"), | |
| R("a.is_zero"), R("b.nonzero")], | |
| "sel.b_zero_pass": [R("sel.not_nan"), R("sel.not_inf_ops"), | |
| R("b.is_zero"), R("a.nonzero")], | |
| "sel.dp_reach": [R("sel.not_nan"), R("sel.not_inf_ops"), | |
| R("a.nonzero"), R("b.nonzero")], | |
| "sel.dp_zero_c": [R("lzc.not_nz")], | |
| "sel.not_dp_zero_c": [R("sel.dp_zero_c")], | |
| "sel.not_ovf": [R("exp_r.overflow")], | |
| "sel.dp_zero": [R("sel.dp_reach"), R("sel.dp_zero_c")], | |
| "sel.dp_inf": [R("sel.dp_reach"), R("exp_r.overflow"), R("sel.not_dp_zero_c")], | |
| "sel.dp_norm": [R("sel.dp_reach"), R("sel.not_dp_zero_c"), R("sel.not_ovf")], | |
| "sel.inf_all": [R("sel.inf_operand"), R("sel.dp_inf")], | |
| "sel.inf_sign.a_and": [R("a.is_inf"), a(0)], | |
| "sel.inf_sign.b_and": [R("b.is_inf"), b(0)], | |
| "sel.inf_sign": [R("sel.inf_sign.a_and"), R("sel.inf_sign.b_and")], | |
| "sel.zero_sign.and_signs": [a(0), b(0)], | |
| "sel.dp_zero_sign": [R("lzc.nz"), R("swap.sign_l.or")], | |
| "sign_out.inf": [R("sel.inf_operand"), R("sel.inf_sign")], | |
| "sign_out.bz": [R("sel.both_zero"), R("sel.zero_sign.and_signs")], | |
| "sign_out.az": [R("sel.a_zero_pass"), b(0)], | |
| "sign_out.bzp": [R("sel.b_zero_pass"), a(0)], | |
| "sign_out.dpz": [R("sel.dp_zero"), R("sel.dp_zero_sign")], | |
| "sign_out.dpi": [R("sel.dp_inf"), R("swap.sign_l.or")], | |
| "sign_out.dpn": [R("sel.dp_norm"), R("swap.sign_l.or")], | |
| "sign_out": [R("sign_out.inf"), R("sign_out.bz"), R("sign_out.az"), | |
| R("sign_out.bzp"), R("sign_out.dpz"), R("sign_out.dpi"), | |
| R("sign_out.dpn")], | |
| } | |
| if suffix in table: | |
| return table[suffix] | |
| # Payload comparison cascade over exp+frac (word bits 1..W-1). | |
| m = re.match(r"^pl_bc\.bit(\d+)(?:\.(.+))?$", suffix) | |
| if m: | |
| i = int(m.group(1)) | |
| sub = m.group(2) or "" | |
| if sub in ("gt", "lt", "eq.layer1.and", "eq.layer1.nor"): | |
| return [a(1 + i), b(1 + i)] | |
| if sub == "eq": | |
| return [R(f"pl_bc.bit{i}.eq.layer1.and"), R(f"pl_bc.bit{i}.eq.layer1.nor")] | |
| m = re.match(r"^pl_bc\.cascade\.eq_prefix\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"pl_bc.bit{j}.eq") for j in range(int(m.group(1)))] | |
| m = re.match(r"^pl_bc\.cascade\.(gt|lt)\.bit(\d+)$", suffix) | |
| if m: | |
| kind, i = m.group(1), int(m.group(2)) | |
| return [R(f"pl_bc.cascade.eq_prefix.bit{i}"), R(f"pl_bc.bit{i}.{kind}")] | |
| if suffix == "pl.gt": | |
| return [R("pl_bc.bit0.gt")] + [R(f"pl_bc.cascade.gt.bit{i}") for i in range(1, P)] | |
| if suffix == "pl.lt": | |
| return [R("pl_bc.bit0.lt")] + [R(f"pl_bc.cascade.lt.bit{i}") for i in range(1, P)] | |
| if suffix == "pl.eq": | |
| return [R(f"pl_bc.bit{i}.eq") for i in range(P)] | |
| if suffix == "pl.ge.not_lt": | |
| return [R("pl.lt")] | |
| if suffix == "pl.ge": | |
| return [R("pl.ge.not_lt")] | |
| if suffix == "pl.le.not_gt": | |
| return [R("pl.gt")] | |
| if suffix == "pl.le": | |
| return [R("pl.le.not_gt")] | |
| # Swap muxes: sel = pl.lt; L side takes b when sel, S side takes a. | |
| m = re.match(r"^swap\.(sign_l|exp_l|exp_s|mant_l|mant_s|impl_l|impl_s)(?:\.bit(\d+))?\.(and_a|and_b|or)$", suffix) | |
| if m: | |
| name = m.group(1) | |
| k = int(m.group(2)) if m.group(2) is not None else 0 | |
| kind = m.group(3) | |
| gbase = f"swap.{name}.bit{k}" if m.group(2) is not None else f"swap.{name}" | |
| if name == "sign_l": | |
| a_side, b_side = a(0), b(0) | |
| elif name in ("exp_l", "exp_s"): | |
| # LSB carries the subnormal correction (effective exponent >= 1). | |
| a_side, b_side = ((R("a.eexp_lsb"), R("b.eexp_lsb")) if k == 0 | |
| else (a(E - k), b(E - k))) | |
| elif name in ("impl_l", "impl_s"): | |
| a_side, b_side = R("a.exp_nzero"), R("b.exp_nzero") | |
| else: | |
| a_side, b_side = a(W - 1 - k), b(W - 1 - k) | |
| l_side = name.endswith("_l") or name == "sign_l" | |
| if kind == "and_a": | |
| return [a_side if l_side else b_side, R("swap.not_sel")] | |
| if kind == "and_b": | |
| return [b_side if l_side else a_side, R("pl.lt")] | |
| return [R(f"{gbase}.and_a"), R(f"{gbase}.and_b")] | |
| m = re.match(r"^exp_nb\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"swap.exp_s.bit{int(m.group(1))}.or")] | |
| m = re.match(r"^exp_diff\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = exp_l(k) if k < E else zero | |
| b_sig = R(f"exp_nb.bit{k}") if k < E else one | |
| cin = one if k == 0 else R(f"exp_diff.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_diff.fa{k}", | |
| a_sig, b_sig, cin, reg) | |
| # Alignment shifter with sticky collection. | |
| m = re.match(r"^align\.s(\d+)\.(.+)$", suffix) | |
| if m: | |
| j = int(m.group(1)) | |
| rest = m.group(2) | |
| sel = d_bit(j) | |
| if rest == "drop": | |
| return [align_in(j, t) for t in range(1 << j)] | |
| if rest == "st_and": | |
| return [sel, R(f"align.s{j}.drop")] | |
| if rest == "sticky": | |
| prev = zero if j == 0 else R(f"align.s{j - 1}.sticky") | |
| return [prev, R(f"align.s{j}.st_and")] | |
| mm = re.match(r"^bit(\d+)\.(not_sel|and_a|and_b|or)$", rest) | |
| if mm: | |
| k = int(mm.group(1)) | |
| kind = mm.group(2) | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [align_in(j, k), R(f"align.s{j}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [align_in(j, k + (1 << j)), sel] | |
| return [R(f"align.s{j}.bit{k}.and_a"), R(f"align.s{j}.bit{k}.and_b")] | |
| return [] | |
| if suffix == "align.sat": | |
| sat_bits = E - num_stages | |
| return [d_bit(num_stages + t) for t in range(sat_bits)] | |
| m = re.match(r"^align\.out\.bit(\d+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"align.s{num_stages - 1}.bit{k}.or"), R("align.not_sat")] | |
| # Parallel add / subtract over the frame, then select by sign parity. | |
| m = re.match(r"^addp\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = l_ext(k) if k <= F + 2 else zero | |
| b_sig = R(f"align.out.bit{k}") if k <= F + 2 else zero | |
| cin = zero if k == 0 else R(f"addp.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.addp.fa{k}", | |
| a_sig, b_sig, cin, reg) | |
| m = re.match(r"^subp\.not_s\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"align.out.bit{int(m.group(1))}")] | |
| m = re.match(r"^subp\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| # carry-in = NOT(sticky): subtract one extra round-ULP when a nonzero | |
| # tail was dropped, making the round bit exact for subtraction. | |
| cin = R("align.not_sticky") if k == 0 else R(f"subp.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.subp.fa{k}", | |
| l_ext(k), R(f"subp.not_s.bit{k}"), cin, reg) | |
| m = re.match(r"^res\.bit(\d+)\.(and_add|and_sub|or)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| kind = m.group(2) | |
| if kind == "and_add": | |
| return [R(f"addp.fa{k}.ha2.sum.layer2"), R("res.not_sub")] | |
| if kind == "and_sub": | |
| sub_sig = R(f"subp.fa{k}.ha2.sum.layer2") if k <= F + 2 else zero | |
| return [sub_sig, R("sign_xor.layer2")] | |
| return [R(f"res.bit{k}.and_add"), R(f"res.bit{k}.and_sub")] | |
| # Leading-zero count and renormalization. | |
| m = re.match(r"^lzc\.any_higher(\d+)$", suffix) | |
| if m: | |
| pos = int(m.group(1)) | |
| return [res_bit(RES - 1 - t) for t in range(pos)] | |
| m = re.match(r"^lzc\.is_highest(\d+)\.(not_higher|and)$", suffix) | |
| if m: | |
| pos = int(m.group(1)) | |
| if m.group(2) == "not_higher": | |
| return [R(f"lzc.any_higher{pos}")] | |
| return [res_bit(RES - 1 - pos), R(f"lzc.is_highest{pos}.not_higher")] | |
| m = re.match(r"^lzc\.nlz\.bit(\d+)$", suffix) | |
| if m: | |
| bbit = int(m.group(1)) | |
| srcs = [R(f"lzc.is_highest{pos}.and") for pos in range(1, RES) if (pos >> bbit) & 1] | |
| return srcs if srcs else [zero] | |
| m = re.match(r"^norm\.s(\d+)\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) | |
| if m: | |
| bbit = int(m.group(1)) | |
| k = int(m.group(2)) | |
| kind = m.group(3) | |
| sel = R(f"nsh.bit{bbit}.or") # clamped shift = min(nlz, exp_L) | |
| def nin(kk: int) -> int: | |
| if kk < 0 or kk >= RES: | |
| return zero | |
| return res_bit(kk) if bbit == 0 else R(f"norm.s{bbit - 1}.bit{kk}.or") | |
| if kind == "not_sel": | |
| return [sel] | |
| if kind == "and_a": | |
| return [nin(k), R(f"norm.s{bbit}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [nin(k - (1 << bbit)), sel] | |
| return [R(f"norm.s{bbit}.bit{k}.and_a"), R(f"norm.s{bbit}.bit{k}.and_b")] | |
| # RNE fraction round-up incrementer on the retained fraction trunc(k). | |
| m = re.match(r"^rnd\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| rest = m.group(2) | |
| cin = R("round.up") if k == 0 else R(f"rnd.bit{k - 1}.carry") | |
| f_bit = trunc(k) | |
| if rest == "xor.layer1.or" or rest == "xor.layer1.nand": | |
| return [f_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"rnd.bit{k}.xor.layer1.or"), R(f"rnd.bit{k}.xor.layer1.nand")] | |
| if rest == "carry": | |
| return [f_bit, cin] | |
| # Exponent rounding incrementer: exp_r + fraction carry-out. | |
| m = re.match(r"^exp_round\.bit(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| rest = m.group(2) | |
| cin = R(f"rnd.bit{F - 1}.carry") if k == 0 else R(f"exp_round.bit{k - 1}.carry") | |
| e_bit = R(f"exprc.bit{k}") # exp_r clamped to 0 when subnormal | |
| if rest == "xor.layer1.or" or rest == "xor.layer1.nand": | |
| return [e_bit, cin] | |
| if rest == "xor.layer2": | |
| return [R(f"exp_round.bit{k}.xor.layer1.or"), R(f"exp_round.bit{k}.xor.layer1.nand")] | |
| if rest == "carry": | |
| return [e_bit, cin] | |
| m = re.match(r"^exp_nnlz\.bit(\d+)$", suffix) | |
| if m: | |
| return [R(f"lzc.nlz.bit{int(m.group(1))}")] | |
| m = re.match(r"^exprc\.bit(\d+)$", suffix) # exp_r clamped to 0 on underflow | |
| if m: | |
| k = int(m.group(1)) | |
| return [R(f"exp_r.fa{k}.ha2.sum.layer2"), R("er.not_underflow")] | |
| m = re.match(r"^nsh\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) # min(nlz, exp_L) | |
| if m: | |
| bbit = int(m.group(1)) | |
| kind = m.group(2) | |
| if kind == "not_sel": | |
| return [R("er.underflow")] | |
| if kind == "and_a": | |
| return [R(f"lzc.nlz.bit{bbit}"), R(f"nsh.bit{bbit}.not_sel")] | |
| if kind == "and_b": | |
| return [exp_l(bbit), R("er.underflow")] | |
| return [R(f"nsh.bit{bbit}.and_a"), R(f"nsh.bit{bbit}.and_b")] | |
| m = re.match(r"^exp_s1\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = exp_l(k) if k < E else zero | |
| b_sig = R(f"exp_nnlz.bit{k}") if k < nlz_bits else one | |
| cin = one if k == 0 else R(f"exp_s1.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_s1.fa{k}", | |
| a_sig, b_sig, cin, reg) | |
| m = re.match(r"^exp_r\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| a_sig = R(f"exp_s1.fa{k}.ha2.sum.layer2") | |
| cin = one if k == 0 else R(f"exp_r.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"{prefix}.exp_r.fa{k}", | |
| a_sig, zero, cin, reg) | |
| m = re.match(r"^exp_out\.bit(\d+)(?:\.(az|bz|norm_and))?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| kind = m.group(2) | |
| if kind == "az": | |
| return [R("sel.a_zero_pass"), b(E - k)] | |
| if kind == "bz": | |
| return [R("sel.b_zero_pass"), a(E - k)] | |
| if kind == "norm_and": | |
| return [R("sel.dp_norm"), R(f"exp_round.bit{k}.xor.layer2")] | |
| return [R("sel.nan"), R("sel.inf_all"), R(f"exp_out.bit{k}.az"), | |
| R(f"exp_out.bit{k}.bz"), R(f"exp_out.bit{k}.norm_and")] | |
| m = re.match(r"^frac_out\.bit(\d+)(?:\.(az|bz|norm_and))?$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| kind = m.group(2) | |
| if kind == "az": | |
| return [R("sel.a_zero_pass"), b(W - 1 - k)] | |
| if kind == "bz": | |
| return [R("sel.b_zero_pass"), a(W - 1 - k)] | |
| if kind == "norm_and": | |
| return [R("sel.dp_norm"), R(f"rnd.bit{k}.xor.layer2")] | |
| nan_sig = R("sel.nan") if k == F - 1 else zero | |
| return [nan_sig, R(f"frac_out.bit{k}.az"), R(f"frac_out.bit{k}.bz"), | |
| R(f"frac_out.bit{k}.norm_and")] | |
| return None | |
| def add_subleq_core(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Add the SUBLEQ one-instruction datapath. | |
| The machine: M[B] -= M[A]; branch to C when the result is <= 0 (two's | |
| complement). External inputs, LSB-first: $a (subtrahend M[A]), $b | |
| (minuend M[B]), $pc (current PC), $c (branch target). Outputs: the new | |
| M[B] on sub.fa{k} sums, and the next PC on pc_mux.bit{k}.or. The one | |
| architectural decision -- branch or fall through -- is the single | |
| threshold neuron subleq.leq over (sign, is-zero). | |
| """ | |
| for k in range(8): | |
| add_gate(tensors, f"subleq.sub.not_a.bit{k}", [-1.0], [0.0]) | |
| add_full_adder(tensors, f"subleq.sub.fa{k}") | |
| add_gate(tensors, "subleq.zero", [-1.0] * 8, [0.0]) | |
| add_gate(tensors, "subleq.leq", [1.0, 1.0], [-1.0]) | |
| for k in range(8): | |
| add_full_adder(tensors, f"subleq.pc_inc.fa{k}") | |
| for k in range(8): | |
| add_gate(tensors, f"subleq.pc_mux.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"subleq.pc_mux.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"subleq.pc_mux.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"subleq.pc_mux.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| def infer_subleq_inputs(gate: str, reg: SignalRegistry) -> Optional[List[int]]: | |
| """Complete wiring for the SUBLEQ datapath (all signals LSB-first).""" | |
| if gate != "subleq" and not gate.startswith("subleq."): | |
| return None | |
| for i in range(8): | |
| for nm in ("$a", "$b", "$pc", "$c"): | |
| reg.register(f"{nm}[{i}]") | |
| def sig(nm, i): | |
| return reg.get_id(f"{nm}[{i}]") | |
| def R(name): | |
| return reg.register(f"subleq.{name}") | |
| zero = reg.get_id("#0") | |
| one = reg.get_id("#1") | |
| suffix = gate[len("subleq."):] | |
| if suffix == "zero": | |
| return [R(f"sub.fa{k}.ha2.sum.layer2") for k in range(8)] | |
| if suffix == "leq": | |
| return [R("sub.fa7.ha2.sum.layer2"), R("zero")] | |
| m = re.match(r"^sub\.not_a\.bit(\d+)$", suffix) | |
| if m: | |
| return [sig("$a", int(m.group(1)))] | |
| m = re.match(r"^sub\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| cin = one if k == 0 else R(f"sub.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"subleq.sub.fa{k}", | |
| sig("$b", k), R(f"sub.not_a.bit{k}"), cin, reg) | |
| m = re.match(r"^pc_inc\.fa(\d+)\.(.+)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| const = one if (3 >> k) & 1 else zero | |
| cin = zero if k == 0 else R(f"pc_inc.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(m.group(2), f"subleq.pc_inc.fa{k}", | |
| sig("$pc", k), const, cin, reg) | |
| m = re.match(r"^pc_mux\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) | |
| if m: | |
| k = int(m.group(1)) | |
| kind = m.group(2) | |
| if kind == "not_sel": | |
| return [R("leq")] | |
| if kind == "and_a": | |
| return [R(f"pc_inc.fa{k}.ha2.sum.layer2"), R(f"pc_mux.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [sig("$c", k), R("leq")] | |
| return [R(f"pc_mux.bit{k}.and_a"), R(f"pc_mux.bit{k}.and_b")] | |
| return None | |
| def infer_rv32_inputs(gate: str, reg: SignalRegistry) -> Optional[List[int]]: | |
| """Complete wiring for the rv32-specific sub-circuits: the 64-bit MULH | |
| accumulator (fed by the shared 32-bit partial products), the SRA select | |
| muxes, the NEUR neuron datapath, and the dual-issue hazard comparators. | |
| All operand-side ports are external ($rv32_...); internal chains resolve | |
| to gate outputs, so each sub-circuit's netlist is recoverable in full. | |
| """ | |
| if not gate.startswith("rv32."): | |
| return None | |
| def R(name): | |
| return reg.register(name) | |
| zero = reg.get_id("#0") | |
| one = reg.get_id("#1") | |
| # 64-bit shift-add accumulator. Partial product at LSB positions (i, j) | |
| # is the shared gate alu.alu32bit.mul.pp.a{31-i}b{31-j} (MSB-first names). | |
| def pp(i_lsb, j_lsb): | |
| if not (0 <= i_lsb <= 31 and 0 <= j_lsb <= 31): | |
| return zero | |
| return R(f"alu.alu32bit.mul.pp.a{31 - i_lsb}b{31 - j_lsb}") | |
| m = re.match(r"^rv32\.mulacc\.s(\d+)\.fa(\d+)\.(.+)$", gate) | |
| if m: | |
| t, k, sub = int(m.group(1)), int(m.group(2)), m.group(3) | |
| row = t + 1 | |
| a_sig = pp(k, 0) if t == 0 else R(f"rv32.mulacc.s{t - 1}.fa{k}.ha2.sum.layer2") | |
| b_sig = pp(k - row, row) if row <= k <= row + 31 else zero | |
| cin = zero if k == 0 else R(f"rv32.mulacc.s{t}.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(sub, f"rv32.mulacc.s{t}.fa{k}", a_sig, b_sig, cin, reg) | |
| # SRA per-bit mux: sel = sign; a-path SRL(x), b-path NOT(SRL(NOT x)). | |
| m = re.match(r"^rv32\.sra_mux\.bit(\d+)\.(not_sel|and_a|and_b|or)$", gate) | |
| if m: | |
| k, kind = int(m.group(1)), m.group(2) | |
| sign = R("$rv32_sra_sign") | |
| if kind == "not_sel": | |
| return [sign] | |
| if kind == "and_a": | |
| return [R(f"$rv32_srl_x[{k}]"), R(f"rv32.sra_mux.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [R(f"$rv32_srl_nx[{k}]"), sign] | |
| return [R(f"rv32.sra_mux.bit{k}.and_a"), R(f"rv32.sra_mux.bit{k}.and_b")] | |
| # NEUR datapath. x/pos/neg are the low bytes (MSB-first bit i = index i). | |
| m = re.match(r"^rv32\.neur\.(pos|neg)\.bit(\d+)$", gate) | |
| if m: | |
| side, i = m.group(1), int(m.group(2)) | |
| return [R(f"$rv32_neur_x[{i}]"), R(f"$rv32_neur_{side}[{i}]")] | |
| # popcount trees over the 8 masked bits (fixed adder wiring per side). | |
| m = re.match(r"^rv32\.neur\.(pcnt|ncnt)\.fa(\d+)\.(.+)$", gate) | |
| if m: | |
| side, t, sub = m.group(1), int(m.group(2)), m.group(3) | |
| src = "pos" if side == "pcnt" else "neg" | |
| b = [R(f"rv32.neur.{src}.bit{i}") for i in range(8)] | |
| wiring = { | |
| 0: (b[0], b[1], b[2]), | |
| 1: (b[3], b[4], b[5]), | |
| 2: (b[6], b[7], zero), | |
| 3: (R(f"rv32.neur.{side}.fa0.ha2.sum.layer2"), | |
| R(f"rv32.neur.{side}.fa1.ha2.sum.layer2"), | |
| R(f"rv32.neur.{side}.fa2.ha2.sum.layer2")), | |
| 4: (R(f"rv32.neur.{side}.fa0.carry_or"), | |
| R(f"rv32.neur.{side}.fa1.carry_or"), | |
| R(f"rv32.neur.{side}.fa2.carry_or")), | |
| 5: (R(f"rv32.neur.{side}.fa3.carry_or"), | |
| R(f"rv32.neur.{side}.fa4.ha2.sum.layer2"), zero), | |
| 6: (R(f"rv32.neur.{side}.fa4.carry_or"), | |
| R(f"rv32.neur.{side}.fa5.carry_or"), zero), | |
| }[t] | |
| return _fa_member_inputs(sub, f"rv32.neur.{side}.fa{t}", wiring[0], wiring[1], wiring[2], reg) | |
| m = re.match(r"^rv32\.neur\.nnot\.bit(\d+)$", gate) | |
| if m: | |
| k = int(m.group(1)) | |
| ncnt = [R(f"rv32.neur.ncnt.fa3.ha2.sum.layer2"), | |
| R(f"rv32.neur.ncnt.fa5.ha2.sum.layer2"), | |
| R(f"rv32.neur.ncnt.fa6.ha2.sum.layer2"), | |
| R(f"rv32.neur.ncnt.fa6.carry_or")] | |
| return [ncnt[k] if k < 4 else zero] | |
| m = re.match(r"^rv32\.neur\.diff\.fa(\d+)\.(.+)$", gate) | |
| if m: | |
| k, sub = int(m.group(1)), m.group(2) | |
| pcnt = [R(f"rv32.neur.pcnt.fa3.ha2.sum.layer2"), | |
| R(f"rv32.neur.pcnt.fa5.ha2.sum.layer2"), | |
| R(f"rv32.neur.pcnt.fa6.ha2.sum.layer2"), | |
| R(f"rv32.neur.pcnt.fa6.carry_or")] | |
| a_sig = pcnt[k] if k < 4 else zero | |
| b_sig = R(f"rv32.neur.nnot.bit{k}") | |
| cin = one if k == 0 else R(f"rv32.neur.diff.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(sub, f"rv32.neur.diff.fa{k}", a_sig, b_sig, cin, reg) | |
| m = re.match(r"^rv32\.neur\.act\.fa(\d+)\.(.+)$", gate) | |
| if m: | |
| k, sub = int(m.group(1)), m.group(2) | |
| a_sig = R(f"rv32.neur.diff.fa{k}.ha2.sum.layer2") | |
| b_sig = R(f"$rv32_neur_bias[{k}]") | |
| cin = zero if k == 0 else R(f"rv32.neur.act.fa{k - 1}.carry_or") | |
| return _fa_member_inputs(sub, f"rv32.neur.act.fa{k}", a_sig, b_sig, cin, reg) | |
| if gate == "rv32.neur.out": | |
| return [R("rv32.neur.act.fa5.ha2.sum.layer2")] | |
| # Hazard comparators: 5-bit index equality, four register pairs. | |
| m = re.match(r"^rv32\.hazard\.([a-z0-9_]+)\.bit(\d+)\.eq(\.layer1\.(and|nor))?$", gate) | |
| if m: | |
| pair, k, layer = m.group(1), int(m.group(2)), m.group(3) | |
| if layer: | |
| return [R(f"$rv32_hz_{pair}_i[{k}]"), R(f"$rv32_hz_{pair}_j[{k}]")] | |
| return [R(f"rv32.hazard.{pair}.bit{k}.eq.layer1.and"), | |
| R(f"rv32.hazard.{pair}.bit{k}.eq.layer1.nor")] | |
| m = re.match(r"^rv32\.hazard\.([a-z0-9_]+)\.all$", gate) | |
| if m: | |
| pair = m.group(1) | |
| return [R(f"rv32.hazard.{pair}.bit{k}.eq") for k in range(5)] | |
| # Instruction decode: opcode-class detectors, format-select, active immediate. | |
| m = re.match(r"^rv32\.decode\.is_(\w+)$", gate) | |
| if m: | |
| name = m.group(1) | |
| if name == "fma": | |
| return [R(f"$rv32_instr[{j}]") for j in (0, 1, 4, 5, 6)] | |
| return [R(f"$rv32_instr[{j}]") for j in range(7)] | |
| m = re.match(r"^rv32\.decode\.use_([ISBUJ])$", gate) | |
| if m: | |
| return [R(f"rv32.decode.is_{cls}") for cls in RV32_USE[m.group(1)]] | |
| m = re.match(r"^rv32\.decode\.imm\.bit(\d+)\.and_([ISBUJ])$", gate) | |
| if m: | |
| k, fmt = int(m.group(1)), m.group(2) | |
| return [R(f"rv32.decode.use_{fmt}"), R(f"$rv32_instr[{_rv32_imm_src(fmt, k)}]")] | |
| m = re.match(r"^rv32\.decode\.imm\.bit(\d+)$", gate) | |
| if m: | |
| k = int(m.group(1)) | |
| terms = [fmt for fmt in RV32_IMM_FMTS if _rv32_imm_src(fmt, k) is not None] | |
| return [R(f"rv32.decode.imm.bit{k}.and_{fmt}") for fmt in terms] if terms else [zero] | |
| # PC sequencing: next-PC mux (pc4 / pc+imm / (rs1+imm)&~1). | |
| m = re.match(r"^rv32\.pcnext\.m1\.bit(\d+)\.(not_sel|and_a|and_b|or)$", gate) | |
| if m: | |
| k, kind = int(m.group(1)), m.group(2) | |
| if kind == "not_sel": | |
| return [R("$rv32_sel_target")] | |
| if kind == "and_a": | |
| return [R(f"$rv32_pc4[{k}]"), R(f"rv32.pcnext.m1.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [R(f"$rv32_pcimm[{k}]"), R("$rv32_sel_target")] | |
| return [R(f"rv32.pcnext.m1.bit{k}.and_a"), R(f"rv32.pcnext.m1.bit{k}.and_b")] | |
| m = re.match(r"^rv32\.pcnext\.bit(\d+)\.(not_sel|and_a|and_b|or)$", gate) | |
| if m: | |
| k, kind = int(m.group(1)), m.group(2) | |
| if kind == "not_sel": | |
| return [R("$rv32_sel_jalr")] | |
| if kind == "and_a": | |
| return [R(f"rv32.pcnext.m1.bit{k}.or"), R(f"rv32.pcnext.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| return [R(f"$rv32_jalr[{k}]"), R("$rv32_sel_jalr")] | |
| return [R(f"rv32.pcnext.bit{k}.and_a"), R(f"rv32.pcnext.bit{k}.and_b")] | |
| return None | |
| def _infer_threshold_computer_bit_cascade(gate: str, reg: SignalRegistry) -> List[int] | None: | |
| """Match every bit-cascade location used in this codebase: integer | |
| comparators, integer division stage cmps, float magnitude comparators, | |
| float exp_cmp, and float division mantissa cmps. | |
| """ | |
| # Integer arithmetic.cmp{N}bit.* + the public greaterthan/lessthan/etc. | |
| m = re.match(r"^arithmetic\.cmp(\d+)bit\..+", gate) | |
| if m: | |
| bits = int(m.group(1)) | |
| return _infer_bit_cascade(gate, reg, f"arithmetic.cmp{bits}bit", "$a[{}]", "$b[{}]") | |
| m = re.match(r"^arithmetic\.(greaterthan|lessthan|equality|greaterorequal|lessorequal)(\d+)bit(\.not_lt|\.not_gt)?$", gate) | |
| if m: | |
| bits = int(m.group(2)) | |
| return _infer_compare_final( | |
| gate, reg, f"arithmetic.cmp{bits}bit", | |
| f"arithmetic.greaterthan{bits}bit", f"arithmetic.lessthan{bits}bit", | |
| f"arithmetic.greaterorequal{bits}bit", f"arithmetic.lessorequal{bits}bit", | |
| f"arithmetic.equality{bits}bit", bits, | |
| ) | |
| # Integer division stage cmps: alu.alu{N}bit.div.stage{S}.cmp_bc.* + .cmp + .cmp.not_lt | |
| m = re.match(r"^alu\.alu(\d+)bit\.div\.stage(\d+)\.cmp_bc\..+", gate) | |
| if m: | |
| bits, stage = int(m.group(1)), int(m.group(2)) | |
| cp = f"alu.alu{bits}bit.div.stage{stage}.cmp_bc" | |
| return _infer_bit_cascade(gate, reg, cp, "$rem[{}]", "$div[{}]") | |
| m = re.match(r"^alu\.alu(\d+)bit\.div\.stage(\d+)\.cmp(\.not_lt)?$", gate) | |
| if m: | |
| bits, stage = int(m.group(1)), int(m.group(2)) | |
| cp = f"alu.alu{bits}bit.div.stage{stage}.cmp_bc" | |
| cmp_name = f"alu.alu{bits}bit.div.stage{stage}.cmp" | |
| if gate == f"{cmp_name}.not_lt": | |
| return [reg.register(f"{cp}.lt")] | |
| if gate == cmp_name: | |
| return [reg.register(f"{cmp_name}.not_lt")] | |
| # Float comparison networks are handled by infer_float_cmp_inputs | |
| # (dispatched first in infer_inputs_for_gate), including their mag_bc | |
| # cascades, which read the raw word bits. | |
| # Float add, mul, div, and cmp networks are wired by their dedicated | |
| # infer_float_*_inputs functions (dispatched first). | |
| # Float division stage comparators are wired by infer_float_div_inputs. | |
| # Modular ternary: per-multiple equality detectors + final OR | |
| m = re.match(r"^modular\.mod(\d+)\.eq\.k(\d+)\.bit(\d+)\.match$", gate) | |
| if m: | |
| bit_i = int(m.group(3)) | |
| sig = f"$x[{bit_i}]" | |
| reg.register(sig) | |
| return [reg.get_id(sig)] | |
| m = re.match(r"^modular\.mod(\d+)\.eq\.k(\d+)\.all$", gate) | |
| if m: | |
| mod, k = int(m.group(1)), int(m.group(2)) | |
| prefix = f"modular.mod{mod}.eq.k{k}" | |
| return [reg.register(f"{prefix}.bit{i}.match") for i in range(8)] | |
| m = re.match(r"^modular\.mod(\d+)$", gate) | |
| if m: | |
| mod = int(m.group(1)) | |
| multiples = list(range(0, 256, mod)) | |
| return [reg.register(f"modular.mod{mod}.eq.k{k}.all") for k in multiples] | |
| return None | |
| _ROUTING_TABLE_CACHE: Dict[str, Any] | None = None | |
| def _load_routing_table() -> Dict[str, Any]: | |
| """Load `routing/routing.json` once; on missing file, return {}. | |
| The JSON maps each top-level circuit name (e.g. ``arithmetic.multiplier8x8``) | |
| to a dict with at least ``inputs`` (top-level external ports) and | |
| ``internal`` (gate-suffix -> list-of-source-signal-name). The source | |
| signal names are either external port references (``$a[3]``), constants | |
| (``#0`` / ``#1``), or relative gate names within the same circuit | |
| (e.g. ``pp.r0.c3``). Anchored cross-circuit references are not | |
| expressed here; circuits in the table are assumed self-contained. | |
| """ | |
| global _ROUTING_TABLE_CACHE | |
| if _ROUTING_TABLE_CACHE is not None: | |
| return _ROUTING_TABLE_CACHE | |
| path = Path(__file__).parent.parent / "routing" / "routing.json" | |
| if not path.exists(): | |
| _ROUTING_TABLE_CACHE = {} | |
| return _ROUTING_TABLE_CACHE | |
| with open(path, encoding="utf-8") as f: | |
| data = json.load(f) | |
| _ROUTING_TABLE_CACHE = data.get("circuits", {}) or {} | |
| return _ROUTING_TABLE_CACHE | |
| def _infer_from_routing_table( | |
| gate: str, reg: SignalRegistry, | |
| known_gate_suffixes: Optional[Set[str]] = None, | |
| ) -> Optional[List[int]]: | |
| """Resolve a gate's inputs by consulting routing.json. | |
| ``known_gate_suffixes`` is the set of gate-name suffixes that actually | |
| exist in the loaded safetensors (within the same circuit prefix). | |
| When the routing JSON names a producer like ``stage0.bit0.ha2.sum`` | |
| but that suffix isn't a real gate (only ``stage0.bit0.ha2.sum.layer2`` | |
| is), this set is consulted to redirect the reference to the canonical | |
| final-layer signal. Returns None when the gate's circuit isn't | |
| covered or its suffix isn't in the routing's ``internal`` map. | |
| """ | |
| table = _load_routing_table() | |
| if not table: | |
| return None | |
| # Find the longest circuit prefix that matches. | |
| best: Optional[str] = None | |
| for circuit in table: | |
| if gate == circuit or gate.startswith(circuit + "."): | |
| if best is None or len(circuit) > len(best): | |
| best = circuit | |
| if best is None: | |
| return None | |
| info = table[best] | |
| internal = info.get("internal") or {} | |
| suffix = gate[len(best):].lstrip(".") | |
| src_names = internal.get(suffix) | |
| if src_names is None: | |
| return None | |
| out: List[int] = [] | |
| for nm in src_names: | |
| if nm in ("#0", "#1"): | |
| out.append(reg.register(nm)) | |
| continue | |
| if nm.startswith("$"): | |
| out.append(reg.register(f"{best}.{nm}")) | |
| continue | |
| # Internal cross-gate reference. Some routing entries use a | |
| # short form (e.g. ``stage0.bit0.ha2.sum``) that resolves to a | |
| # multi-layer gate (``stage0.bit0.ha2.sum.layer2``). When the | |
| # short name isn't a real gate, redirect to its ``.layer2`` | |
| # final-output sibling, which is what threshold-network XOR | |
| # cells expose. | |
| resolved = nm | |
| if known_gate_suffixes is not None and nm not in known_gate_suffixes: | |
| for cand in (f"{nm}.layer2", f"{nm}.layer1", f"{nm}.out"): | |
| if cand in known_gate_suffixes: | |
| resolved = cand | |
| break | |
| out.append(reg.register(f"{best}.{resolved}")) | |
| return out | |
| def _gate_suffixes_under(prefix: str, tensors: Dict[str, torch.Tensor]) -> Set[str]: | |
| """Return the set of gate-name suffixes whose full name starts with `prefix`.""" | |
| out: Set[str] = set() | |
| for k in tensors: | |
| for suf in (".weight", ".bias", ".inputs"): | |
| if k.endswith(suf): | |
| gate = k[: -len(suf)] | |
| if gate == prefix or gate.startswith(prefix + "."): | |
| out.add(gate[len(prefix):].lstrip(".")) | |
| break | |
| return out | |
| def infer_inputs_for_gate(gate: str, reg: SignalRegistry, tensors: Dict[str, torch.Tensor]) -> List[int]: | |
| if gate.startswith('manifest.'): | |
| return [] | |
| # routing.json holds explicit per-gate input lists for circuits where | |
| # naming alone is insufficient (multipliers, float pipelines, etc.). | |
| # Consult it first; family-specific inferrers below remain the | |
| # fallback for anything not covered there. | |
| table = _load_routing_table() | |
| matched_circuit: Optional[str] = None | |
| if table: | |
| for circuit in table: | |
| if gate == circuit or gate.startswith(circuit + "."): | |
| if matched_circuit is None or len(circuit) > len(matched_circuit): | |
| matched_circuit = circuit | |
| suffixes: Optional[Set[str]] = ( | |
| _gate_suffixes_under(matched_circuit, tensors) | |
| if matched_circuit else None | |
| ) | |
| rt = _infer_from_routing_table(gate, reg, known_gate_suffixes=suffixes) | |
| if rt is not None: | |
| return rt | |
| if gate.startswith('subleq'): | |
| sq = infer_subleq_inputs(gate, reg) | |
| if sq is not None: | |
| return sq | |
| if gate.startswith('rv32.'): | |
| rv = infer_rv32_inputs(gate, reg) | |
| if rv is not None: | |
| return rv | |
| # Self-contained float networks (complete wiring, raw-word inputs). | |
| m = re.match(r"^(float16|float32)\.(cmp|mul|div|add|fma)(\.|$)", gate) | |
| if m: | |
| family = m.group(1) | |
| e_bits, f_bits = (5, 10) if family == "float16" else (8, 23) | |
| infer_fn = {"cmp": infer_float_cmp_inputs, | |
| "mul": infer_float_mul_inputs, | |
| "div": infer_float_div_inputs, | |
| "add": infer_float_add_inputs, | |
| "fma": infer_float_fma_inputs}[m.group(2)] | |
| fc = infer_fn(gate, reg, family, e_bits, f_bits) | |
| if fc is not None: | |
| return fc | |
| # Bit-cascade comparators (integer + float + div) and ternary modular detectors. | |
| # These are the gates emitted by add_bit_cascade_compare and the ternary | |
| # modular rebuild in quantize.py; cover them up front so they don't fall | |
| # through to the generic per-family handlers below. | |
| bc = _infer_threshold_computer_bit_cascade(gate, reg) | |
| if bc is not None: | |
| return bc | |
| if gate.startswith('boolean.'): | |
| return infer_boolean_inputs(gate, reg) | |
| if gate.startswith('arithmetic.'): | |
| if 'halfadder' in gate: | |
| return infer_halfadder_inputs(gate, "arithmetic.halfadder", reg) | |
| if 'fulladder' in gate: | |
| return infer_fulladder_inputs(gate, "arithmetic.fulladder", reg) | |
| if 'ripplecarry2bit' in gate: | |
| return infer_ripplecarry_inputs(gate, "arithmetic.ripplecarry2bit", 2, reg) | |
| if 'ripplecarry4bit' in gate: | |
| return infer_ripplecarry_inputs(gate, "arithmetic.ripplecarry4bit", 4, reg) | |
| if 'ripplecarry8bit' in gate: | |
| return infer_ripplecarry_inputs(gate, "arithmetic.ripplecarry8bit", 8, reg) | |
| if 'add3_8bit' in gate: | |
| return infer_add3_inputs(gate, reg) | |
| if 'expr_add_mul' in gate and 'paren' not in gate: | |
| return infer_expr_add_mul_inputs(gate, reg) | |
| if 'expr_paren_add_mul' in gate: | |
| return infer_expr_paren_add_mul_inputs(gate, reg) | |
| if 'adc8bit' in gate: | |
| return infer_adcsbc_inputs(gate, "arithmetic.adc8bit", False, reg) | |
| if 'sbc8bit' in gate: | |
| return infer_adcsbc_inputs(gate, "arithmetic.sbc8bit", True, reg) | |
| if 'sub8bit' in gate: | |
| return infer_sub8bit_inputs(gate, reg) | |
| if any(cmp in gate for cmp in ['greaterthan8bit', 'lessthan8bit', 'greaterorequal8bit', 'lessorequal8bit']): | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| if 'equality8bit' in gate: | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| if 'layer1' in gate: | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| if 'layer2' in gate: | |
| return [reg.register("arithmetic.equality8bit.layer1.geq"), reg.register("arithmetic.equality8bit.layer1.leq")] | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] + [reg.get_id(f"$b[{i}]") for i in range(8)] | |
| for i in range(8): | |
| reg.register(f"$a[{i}]") | |
| reg.register(f"$b[{i}]") | |
| return [reg.get_id(f"$a[{i}]") for i in range(8)] | |
| if gate.startswith('threshold.'): | |
| return infer_threshold_inputs(gate, reg) | |
| if gate.startswith('modular.'): | |
| return infer_modular_inputs(gate, reg) | |
| if gate.startswith('control.'): | |
| if ('pcnext' not in gate and 'decode' not in gate | |
| and any(j in gate for j in ['jz', 'jc', 'jn', 'jv', 'jp', 'jnz', 'jnc', 'jnv', 'conditionaljump'])): | |
| prefix = gate.split('.bit')[0] if '.bit' in gate else gate.rsplit('.', 1)[0] | |
| return infer_control_jump_inputs(gate, prefix, reg) | |
| if any(b in gate for b in ['fetch', 'load', 'store', 'mem_addr']): | |
| return infer_buffer_inputs(gate, reg) | |
| if 'push.sp_dec' in gate or 'pop.sp_inc' in gate: | |
| for i in range(16): | |
| reg.register(f"$sp[{i}]") | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| op = 'push.sp_dec' if 'push' in gate else 'pop.sp_inc' | |
| prefix = f"control.{op}.bit{bit}" | |
| if 'layer1' in gate: | |
| if bit == 15: | |
| return [reg.get_id(f"$sp[{bit}]"), reg.get_id("#1")] | |
| else: | |
| carry_name = 'borrow' if 'push' in gate else 'carry' | |
| return [reg.get_id(f"$sp[{bit}]"), reg.register(f"control.{op}.bit{bit+1}.{carry_name}")] | |
| if 'layer2' in gate: | |
| return [reg.register(f"{prefix}.xor.layer1.or"), reg.register(f"{prefix}.xor.layer1.nand")] | |
| if '.borrow' in gate or '.carry' in gate: | |
| if bit == 15: | |
| return [reg.get_id(f"$sp[{bit}]"), reg.get_id("#1")] | |
| else: | |
| carry_name = 'borrow' if 'push' in gate else 'carry' | |
| return [reg.get_id(f"$sp[{bit}]"), reg.register(f"control.{op}.bit{bit+1}.{carry_name}")] | |
| return [reg.get_id(f"$sp[{i}]") for i in range(16)] | |
| if 'ret.addr' in gate: | |
| m = re.search(r'bit(\d+)', gate) | |
| if m: | |
| bit = int(m.group(1)) | |
| return [reg.register(f"$ret_addr[{bit}]")] | |
| return [reg.register(f"$ret_addr[{i}]") for i in range(16)] | |
| if 'decode.op' in gate: | |
| for i in range(4): | |
| reg.register(f"$comp_op[{i}]") | |
| return [reg.get_id(f"$comp_op[{i}]") for i in range(4)] | |
| if 'pcnext.' in gate: | |
| for i in range(16): | |
| reg.register(f"$comp_pc[{i}]") | |
| reg.register(f"$comp_addr[{i}]") | |
| reg.register(f"$comp_jcc[{i}]") | |
| for nm in ("$comp_use_pc4", "$comp_use_jcc", "$comp_use_addr"): | |
| reg.register(nm) | |
| def _inc_out(name, k): | |
| start = 1 if name == "pc2" else 2 | |
| return reg.register(f"control.pcnext.{name}.bit{k}" | |
| if k < start else f"control.pcnext.{name}.bit{k}.xor.layer2") | |
| mm = re.match(r"^control\.pcnext\.(pc2|pc4)\.bit(\d+)(?:\.(.+))?$", gate) | |
| if mm: | |
| name, k, sub = mm.group(1), int(mm.group(2)), mm.group(3) | |
| start = 1 if name == "pc2" else 2 | |
| if sub is None: | |
| return [reg.get_id(f"$comp_pc[{k}]")] | |
| cin = reg.get_id("#1") if k == start else reg.register(f"control.pcnext.{name}.bit{k - 1}.carry") | |
| pcb = reg.get_id(f"$comp_pc[{k}]") | |
| if sub in ("xor.layer1.or", "xor.layer1.nand", "carry"): | |
| return [pcb, cin] | |
| if sub == "xor.layer2": | |
| return [reg.register(f"control.pcnext.{name}.bit{k}.xor.layer1.or"), | |
| reg.register(f"control.pcnext.{name}.bit{k}.xor.layer1.nand")] | |
| mm = re.match(r"^control\.pcnext\.(m_pc4|m_jcc|m_addr)\.bit(\d+)\.(not_sel|and_a|and_b|or)$", gate) | |
| if mm: | |
| lvl, k, kind = mm.group(1), int(mm.group(2)), mm.group(3) | |
| sel = {"m_pc4": "$comp_use_pc4", "m_jcc": "$comp_use_jcc", "m_addr": "$comp_use_addr"}[lvl] | |
| if kind == "not_sel": | |
| return [reg.get_id(sel)] | |
| if kind == "and_a": | |
| if lvl == "m_pc4": | |
| a_src = _inc_out("pc2", k) | |
| else: | |
| a_src = reg.register(f"control.pcnext.{'m_pc4' if lvl == 'm_jcc' else 'm_jcc'}.bit{k}.or") | |
| return [a_src, reg.register(f"control.pcnext.{lvl}.bit{k}.not_sel")] | |
| if kind == "and_b": | |
| if lvl == "m_pc4": | |
| b_src = _inc_out("pc4", k) | |
| elif lvl == "m_jcc": | |
| b_src = reg.get_id(f"$comp_jcc[{k}]") | |
| else: | |
| b_src = reg.get_id(f"$comp_addr[{k}]") | |
| return [b_src, reg.get_id(sel)] | |
| return [reg.register(f"control.pcnext.{lvl}.bit{k}.and_a"), | |
| reg.register(f"control.pcnext.{lvl}.bit{k}.and_b")] | |
| return [reg.register("$ctrl")] | |
| if gate.startswith('memory.'): | |
| return infer_memory_inputs(gate, reg, tensors) | |
| if gate.startswith('alu.'): | |
| return infer_alu_inputs(gate, reg) | |
| if gate.startswith('pattern_recognition.'): | |
| return infer_pattern_inputs(gate, reg) | |
| if gate.startswith('error_detection.'): | |
| return infer_error_detection_inputs(gate, reg) | |
| if gate.startswith('combinational.'): | |
| return infer_combinational_inputs(gate, reg, tensors) | |
| weight_key = f"{gate}.weight" | |
| if weight_key in tensors: | |
| w = tensors[weight_key] | |
| n_inputs = w.shape[0] if w.dim() == 1 else w.shape[-1] | |
| for i in range(n_inputs): | |
| reg.register(f"$input[{i}]") | |
| return [reg.get_id(f"$input[{i}]") for i in range(n_inputs)] | |
| return [] | |
| def build_inputs(tensors: Dict[str, torch.Tensor]) -> tuple[Dict[str, torch.Tensor], SignalRegistry, dict]: | |
| reg = SignalRegistry() | |
| gates = get_all_gates(tensors) | |
| stats = {"added": 0, "skipped": 0, "empty": 0, "regenerated": 0} | |
| # Signal IDs are assigned freshly every run from a new SignalRegistry, so | |
| # any pre-existing .inputs tensors reference stale IDs from a prior build. | |
| # Drop them up front (except on packed multi-gate tensors, which use a | |
| # different convention) and regenerate everything below. | |
| preexisting_count = 0 | |
| for gate in list(gates): | |
| inputs_key = f"{gate}.inputs" | |
| weight_key = f"{gate}.weight" | |
| if inputs_key in tensors: | |
| weight = tensors.get(weight_key) | |
| if weight is not None and weight.dim() > 1: | |
| # Packed multi-gate tensor; don't touch. | |
| continue | |
| del tensors[inputs_key] | |
| preexisting_count += 1 | |
| stats["regenerated"] = preexisting_count | |
| for gate in sorted(gates): | |
| inputs_key = f"{gate}.inputs" | |
| weight_key = f"{gate}.weight" | |
| if inputs_key in tensors: | |
| stats["skipped"] += 1 | |
| continue | |
| inputs = infer_inputs_for_gate(gate, reg, tensors) | |
| # Make .inputs length match the gate's weight fan-in. Pattern matchers | |
| # in infer_inputs_for_gate cover most circuit families, but some | |
| # seed-file gate families fall through and produce wrong-length lists. | |
| # Pad with anonymous gate-local placeholder signals (or trim to the | |
| # weight count) so downstream tools see consistent metadata. | |
| weight = tensors.get(weight_key) | |
| if weight is not None and weight.dim() == 1: | |
| expected = weight.numel() | |
| if len(inputs) > expected: | |
| inputs = inputs[:expected] | |
| stats["regenerated"] = stats.get("regenerated", 0) + 1 | |
| elif len(inputs) < expected: | |
| for k in range(len(inputs), expected): | |
| inputs.append(reg.register(f"{gate}.in{k}")) | |
| stats["regenerated"] = stats.get("regenerated", 0) + 1 | |
| if inputs: | |
| # Store signal IDs in the smallest signed integer dtype that | |
| # holds them; int64 quadruples the metadata footprint for nothing. | |
| max_id = max(inputs) | |
| if max_id < (1 << 7): | |
| dtype = torch.int8 | |
| elif max_id < (1 << 15): | |
| dtype = torch.int16 | |
| else: | |
| dtype = torch.int32 | |
| tensors[inputs_key] = torch.tensor(inputs, dtype=dtype) | |
| stats["added"] += 1 | |
| else: | |
| stats["empty"] += 1 | |
| return tensors, reg, stats | |
| def resolve_memory_config(args) -> tuple: | |
| """Resolve memory configuration from args, returns (addr_bits, mem_bytes).""" | |
| if hasattr(args, 'memory_profile') and args.memory_profile: | |
| addr_bits = MEMORY_PROFILES[args.memory_profile] | |
| elif hasattr(args, 'addr_bits') and args.addr_bits is not None: | |
| addr_bits = args.addr_bits | |
| else: | |
| addr_bits = DEFAULT_ADDR_BITS | |
| mem_bytes = (1 << addr_bits) if addr_bits > 0 else 0 | |
| return addr_bits, mem_bytes | |
| def cmd_memory(args) -> None: | |
| addr_bits, mem_bytes = resolve_memory_config(args) | |
| print("=" * 60) | |
| print(" BUILD MEMORY CIRCUITS") | |
| print("=" * 60) | |
| print(f"\nMemory configuration:") | |
| print(f" Address bits: {addr_bits}") | |
| print(f" Memory bytes: {mem_bytes:,}") | |
| if addr_bits == 0: | |
| print(f" Mode: PURE ALU (no memory)") | |
| elif addr_bits <= 4: | |
| print(f" Mode: LLM registers") | |
| elif addr_bits <= 8: | |
| print(f" Mode: LLM scratchpad") | |
| elif addr_bits <= 12: | |
| print(f" Mode: Reduced CPU") | |
| else: | |
| print(f" Mode: Full CPU") | |
| print(f"\nLoading: {args.model}") | |
| tensors = load_tensors(args.model) | |
| metadata = load_file_metadata(args.model) | |
| print(f" Loaded {len(tensors)} tensors") | |
| print("\nDropping existing memory/control tensors...") | |
| drop_prefixes(tensors, [ | |
| "memory.addr_decode.", "memory.read.", "memory.write.", | |
| "control.fetch.ir.", "control.load.", "control.store.", "control.mem_addr.", | |
| "control.push.", "control.pop.", "control.ret.", | |
| "control.jz.", "control.jnz.", "control.jc.", "control.jnc.", | |
| "control.jp.", "control.jn.", "control.jv.", "control.jnv.", | |
| "flags.", | |
| ]) | |
| print(f" Now {len(tensors)} tensors") | |
| if addr_bits > 0: | |
| print("\nGenerating memory circuits...") | |
| add_decoder(tensors, addr_bits, mem_bytes) | |
| add_memory_read_mux(tensors, mem_bytes) | |
| add_memory_write_cells(tensors, mem_bytes) | |
| print(" Added decoder, read mux, write cells") | |
| print("\nGenerating buffer gates...") | |
| try: | |
| add_fetch_load_store_buffers(tensors, args.bits, addr_bits) | |
| print(f" Added fetch/load/store/mem_addr buffers ({args.bits}-bit data, {addr_bits}-bit addr)") | |
| except ValueError as e: | |
| print(f" Buffers already exist: {e}") | |
| print("\nGenerating stack operation circuits...") | |
| try: | |
| add_stack_ops(tensors, args.bits, addr_bits) | |
| sp_gates = addr_bits * 4 * 2 # SP inc/dec gates | |
| data_gates = args.bits * 2 # PUSH/POP data buffers | |
| ret_gates = addr_bits # RET address buffers | |
| total_gates = sp_gates + data_gates + ret_gates | |
| print(f" Added PUSH/POP/RET ({total_gates} gates: {args.bits}-bit data, {addr_bits}-bit SP)") | |
| except ValueError as e: | |
| print(f" Stack ops already exist: {e}") | |
| print("\nGenerating conditional jump circuits...") | |
| try: | |
| add_conditional_jumps(tensors, addr_bits) | |
| jump_gates = 8 * addr_bits * 4 # 8 jump types × addr_bits × 4 gates each | |
| print(f" Added JZ/JNZ/JC/JNC/JP/JN/JV/JNV ({jump_gates} gates: {addr_bits}-bit addresses)") | |
| except ValueError as e: | |
| print(f" Conditional jumps already exist: {e}") | |
| print("\nGenerating instruction-decode and PC-sequencing circuits...") | |
| try: | |
| add_computer_decode(tensors) | |
| add_computer_pcnext(tensors, addr_bits) | |
| print(f" Added opcode decoder (4->16) and next-PC mux ({addr_bits}-bit)") | |
| except ValueError as e: | |
| print(f" Decode/PC circuits already exist: {e}") | |
| print("\nGenerating status flag circuits...") | |
| try: | |
| add_status_flags(tensors, args.bits) | |
| print(f" Added Z/N/C/V flags ({args.bits}-bit aware)") | |
| except ValueError as e: | |
| print(f" Status flags already exist: {e}") | |
| else: | |
| print("\nSkipping memory circuits (addr_bits=0, pure ALU mode)") | |
| print("\nUpdating manifest...") | |
| update_manifest(tensors, args.bits, addr_bits, mem_bytes) | |
| print(f" data_bits={args.bits}, addr_bits={addr_bits}, memory_bytes={mem_bytes:,}") | |
| if args.apply: | |
| print(f"\nSaving: {args.model}") | |
| save_file(tensors, str(args.model), metadata=metadata or None) | |
| if args.manifest: | |
| write_manifest(MANIFEST_PATH, tensors) | |
| print(f" Wrote manifest: {MANIFEST_PATH}") | |
| print(" Done.") | |
| else: | |
| print("\n[DRY-RUN] Use --apply to save.") | |
| print(f"\nTotal: {len(tensors)} tensors") | |
| mem_params = sum(t.numel() for k, t in tensors.items() if k.startswith("memory.")) | |
| alu_params = sum(t.numel() for k, t in tensors.items() if not k.startswith("memory.") and not k.startswith("manifest.")) | |
| print(f" Memory params: {mem_params:,}") | |
| print(f" ALU/Logic params: {alu_params:,}") | |
| print("=" * 60) | |
| def cmd_inputs(args) -> None: | |
| print("=" * 60) | |
| print(" BUILD .inputs TENSORS") | |
| print("=" * 60) | |
| print(f"\nLoading: {args.model}") | |
| tensors = load_tensors(args.model) | |
| print(f" Loaded {len(tensors)} tensors") | |
| gates = get_all_gates(tensors) | |
| print(f" Found {len(gates)} gates") | |
| print("\nBuilding .inputs tensors...") | |
| tensors, reg, stats = build_inputs(tensors) | |
| print(f"\nResults:") | |
| print(f" Added: {stats['added']}") | |
| print(f" Skipped: {stats['skipped']}") | |
| print(f" Empty: {stats['empty']}") | |
| print(f" Signals: {len(reg.name_to_id)}") | |
| print(f" Total: {len(tensors)}") | |
| if args.apply: | |
| print(f"\nSaving: {args.model}") | |
| # Merge into the existing header metadata instead of replacing it, | |
| # so fields like weight_quantization survive an inputs rebuild. | |
| metadata = load_file_metadata(args.model) | |
| metadata["signal_registry"] = reg.to_metadata() | |
| save_file(tensors, str(args.model), metadata=metadata) | |
| print(" Done.") | |
| else: | |
| print("\n[DRY-RUN] Use --apply to save.") | |
| print("=" * 60) | |
| def cmd_alu(args) -> None: | |
| bits = getattr(args, 'bits', 8) or 8 | |
| print("=" * 60) | |
| print(f" BUILD ALU CIRCUITS ({bits}-bit)") | |
| print("=" * 60) | |
| print(f"\nLoading: {args.model}") | |
| tensors = load_tensors(args.model) | |
| metadata = load_file_metadata(args.model) | |
| print(f" Loaded {len(tensors)} tensors") | |
| drop_list = [ | |
| "alu.alu8bit.shl.", "alu.alu8bit.shr.", | |
| "alu.alu8bit.mul.", "alu.alu8bit.div.", | |
| "alu.alu8bit.inc.", "alu.alu8bit.dec.", | |
| "alu.alu8bit.neg.", "alu.alu8bit.rol.", "alu.alu8bit.ror.", | |
| "arithmetic.greaterthan8bit.", "arithmetic.lessthan8bit.", | |
| "arithmetic.greaterorequal8bit.", "arithmetic.lessorequal8bit.", | |
| "arithmetic.equality8bit.", "arithmetic.add3_8bit.", "arithmetic.expr_add_mul.", "arithmetic.expr_paren.", | |
| "arithmetic.cmp8bit.", # bit-cascade internals (replaces single-layer) | |
| "combinational.barrelshifter.", "combinational.priorityencoder.", | |
| "float16.", "float32.", | |
| ] | |
| if bits in [16, 32]: | |
| drop_list.extend([ | |
| f"alu.alu{bits}bit.", f"arithmetic.ripplecarry{bits}bit.", | |
| f"arithmetic.sub{bits}bit.", f"arithmetic.greaterthan{bits}bit.", | |
| f"arithmetic.lessthan{bits}bit.", f"arithmetic.greaterorequal{bits}bit.", | |
| f"arithmetic.lessorequal{bits}bit.", f"arithmetic.equality{bits}bit.", | |
| f"arithmetic.cmp{bits}bit.", # legacy byte-cascade (32-bit) and new bit-cascade | |
| ]) | |
| print("\nDropping existing ALU extension tensors...") | |
| drop_prefixes(tensors, drop_list) | |
| print(f" Now {len(tensors)} tensors") | |
| print("\nGenerating SHL/SHR circuits...") | |
| try: | |
| add_shl_shr(tensors) | |
| print(" Added SHL (8 gates), SHR (8 gates)") | |
| except ValueError as e: | |
| print(f" SHL/SHR already exist: {e}") | |
| print("\nGenerating MUL circuit...") | |
| try: | |
| add_mul(tensors) | |
| print(" Added MUL (64 partial product AND gates)") | |
| except ValueError as e: | |
| print(f" MUL already exists: {e}") | |
| print("\nGenerating DIV circuit...") | |
| try: | |
| add_div(tensors) | |
| print(" Added DIV (8 stages x comparison + mux)") | |
| except ValueError as e: | |
| print(f" DIV already exists: {e}") | |
| print("\nGenerating INC/DEC circuits...") | |
| try: | |
| add_inc_dec(tensors) | |
| print(" Added INC (32 gates), DEC (40 gates)") | |
| except ValueError as e: | |
| print(f" INC/DEC already exist: {e}") | |
| print("\nGenerating NEG circuit...") | |
| try: | |
| add_neg(tensors) | |
| print(" Added NEG (40 gates)") | |
| except ValueError as e: | |
| print(f" NEG already exists: {e}") | |
| print("\nGenerating ROL/ROR circuits...") | |
| try: | |
| add_rol_ror(tensors) | |
| print(" Added ROL (8 gates), ROR (8 gates)") | |
| except ValueError as e: | |
| print(f" ROL/ROR already exist: {e}") | |
| print("\nGenerating barrel shifter...") | |
| try: | |
| add_barrel_shifter(tensors) | |
| print(" Added barrel shifter (96 gates)") | |
| except ValueError as e: | |
| print(f" Barrel shifter already exists: {e}") | |
| print("\nGenerating priority encoder...") | |
| try: | |
| add_priority_encoder(tensors) | |
| print(" Added priority encoder (28 gates)") | |
| except ValueError as e: | |
| print(f" Priority encoder already exists: {e}") | |
| print("\nGenerating comparator circuits...") | |
| try: | |
| add_comparators(tensors) | |
| print(" Added GT, GE, LT, LE (single-layer), EQ (two-layer)") | |
| except ValueError as e: | |
| print(f" Comparators already exist: {e}") | |
| print("\nGenerating 3-operand adder circuit...") | |
| try: | |
| add_add3(tensors) | |
| print(" Added ADD3 (16 full adders = 144 gates)") | |
| except ValueError as e: | |
| print(f" ADD3 already exists: {e}") | |
| print("\nGenerating expression A + B × C circuit...") | |
| try: | |
| add_expr_add_mul(tensors) | |
| print(" Added EXPR_ADD_MUL (64 AND + 56 + 8 full adders = 640 gates)") | |
| except ValueError as e: | |
| print(f" EXPR_ADD_MUL already exists: {e}") | |
| print("\nGenerating expression (A + B) × C circuit...") | |
| try: | |
| add_expr_paren(tensors) | |
| print(" Added EXPR_PAREN (8 + 64 AND + 56 full adders = 640 gates)") | |
| except ValueError as e: | |
| print(f" EXPR_PAREN already exists: {e}") | |
| if bits in [16, 32]: | |
| print(f"\n{'=' * 60}") | |
| print(f" GENERATING {bits}-BIT CIRCUITS") | |
| print(f"{'=' * 60}") | |
| print(f"\nGenerating {bits}-bit ripple carry adder...") | |
| try: | |
| add_ripple_carry_nbits(tensors, bits) | |
| print(f" Added {bits}-bit adder ({bits} full adders = {bits * 9} gates)") | |
| except ValueError as e: | |
| print(f" {bits}-bit adder already exists: {e}") | |
| print(f"\nGenerating {bits}-bit subtractor...") | |
| try: | |
| add_sub_nbits(tensors, bits) | |
| print(f" Added {bits}-bit subtractor ({bits} NOT + {bits} full adders)") | |
| except ValueError as e: | |
| print(f" {bits}-bit subtractor already exists: {e}") | |
| print(f"\nGenerating {bits}-bit comparators...") | |
| try: | |
| add_comparators_nbits(tensors, bits) | |
| print(f" Added {bits}-bit GT, GE, LT, LE, EQ") | |
| except ValueError as e: | |
| print(f" {bits}-bit comparators already exist: {e}") | |
| print(f"\nGenerating {bits}-bit multiplication...") | |
| try: | |
| add_mul_nbits(tensors, bits) | |
| print(f" Added {bits}-bit MUL ({bits * bits} partial product AND gates)") | |
| except ValueError as e: | |
| print(f" {bits}-bit MUL already exists: {e}") | |
| print(f"\nGenerating {bits}-bit division...") | |
| try: | |
| add_div_nbits(tensors, bits) | |
| print(f" Added {bits}-bit DIV ({bits} stages)") | |
| except ValueError as e: | |
| print(f" {bits}-bit DIV already exists: {e}") | |
| print(f"\nGenerating {bits}-bit bitwise ops (AND, OR, XOR, NOT)...") | |
| try: | |
| add_bitwise_nbits(tensors, bits) | |
| print(f" Added {bits}-bit AND, OR, XOR, NOT") | |
| except ValueError as e: | |
| print(f" {bits}-bit bitwise ops already exist: {e}") | |
| print(f"\nGenerating {bits}-bit shift ops (SHL, SHR)...") | |
| try: | |
| add_shift_nbits(tensors, bits) | |
| print(f" Added {bits}-bit SHL, SHR") | |
| except ValueError as e: | |
| print(f" {bits}-bit shift ops already exist: {e}") | |
| print(f"\nGenerating {bits}-bit INC/DEC...") | |
| try: | |
| add_inc_dec_nbits(tensors, bits) | |
| print(f" Added {bits}-bit INC, DEC") | |
| except ValueError as e: | |
| print(f" {bits}-bit INC/DEC already exist: {e}") | |
| print(f"\nGenerating {bits}-bit NEG...") | |
| try: | |
| add_neg_nbits(tensors, bits) | |
| print(f" Added {bits}-bit NEG") | |
| except ValueError as e: | |
| print(f" {bits}-bit NEG already exists: {e}") | |
| print(f"\nGenerating {bits}-bit barrel shifter...") | |
| try: | |
| add_barrel_shifter_nbits(tensors, bits) | |
| import math | |
| num_layers = max(1, math.ceil(math.log2(bits))) | |
| print(f" Added {bits}-bit barrel shifter ({num_layers} layers x {bits} muxes)") | |
| except ValueError as e: | |
| print(f" {bits}-bit barrel shifter already exists: {e}") | |
| print(f"\nGenerating {bits}-bit priority encoder...") | |
| try: | |
| add_priority_encoder_nbits(tensors, bits) | |
| import math | |
| out_bits = max(1, math.ceil(math.log2(bits))) | |
| print(f" Added {bits}-bit priority encoder ({out_bits}-bit output)") | |
| except ValueError as e: | |
| print(f" {bits}-bit priority encoder already exists: {e}") | |
| print(f"\n{'=' * 60}") | |
| print(f" GENERATING FLOAT CIRCUITS") | |
| print(f"{'=' * 60}") | |
| print("\nGenerating float16 core circuits...") | |
| try: | |
| add_float16_core(tensors) | |
| print(" Added float16 unpack/pack/classify") | |
| except ValueError as e: | |
| print(f" float16 core already exists: {e}") | |
| print("\nGenerating float16 ADD circuit...") | |
| try: | |
| add_float16_add(tensors) | |
| print(" Added float16 addition (exp align + mantissa add/sub)") | |
| except ValueError as e: | |
| print(f" float16 ADD already exists: {e}") | |
| print("\nGenerating float16 MUL circuit...") | |
| try: | |
| add_float16_mul(tensors) | |
| print(" Added float16 multiplication (11x11 mantissa mul)") | |
| except ValueError as e: | |
| print(f" float16 MUL already exists: {e}") | |
| print("\nGenerating float16 DIV circuit...") | |
| try: | |
| add_float16_div(tensors) | |
| print(" Added float16 division (11-stage restoring div)") | |
| except ValueError as e: | |
| print(f" float16 DIV already exists: {e}") | |
| print("\nGenerating float16 FMA circuit...") | |
| try: | |
| add_float_fma(tensors, "float16", 5, 10) | |
| print(" Added float16 fused multiply-add (single rounding)") | |
| except ValueError as e: | |
| print(f" float16 FMA already exists: {e}") | |
| print("\nGenerating float16 CMP circuits...") | |
| try: | |
| add_float16_cmp(tensors) | |
| print(" Added float16 comparisons (EQ, LT, LE, GT, GE)") | |
| except ValueError as e: | |
| print(f" float16 CMP already exists: {e}") | |
| print("\nGenerating float32 core circuits...") | |
| try: | |
| add_float32_core(tensors) | |
| print(" Added float32 unpack/pack/classify") | |
| except ValueError as e: | |
| print(f" float32 core already exists: {e}") | |
| print("\nGenerating float32 ADD circuit...") | |
| try: | |
| add_float32_add(tensors) | |
| print(" Added float32 addition (exp align + mantissa add/sub)") | |
| except ValueError as e: | |
| print(f" float32 ADD already exists: {e}") | |
| print("\nGenerating float32 MUL circuit...") | |
| try: | |
| add_float32_mul(tensors) | |
| print(" Added float32 multiplication (24x24 mantissa mul)") | |
| except ValueError as e: | |
| print(f" float32 MUL already exists: {e}") | |
| print("\nGenerating float32 DIV circuit...") | |
| try: | |
| add_float32_div(tensors) | |
| print(" Added float32 division (24-stage restoring div)") | |
| except ValueError as e: | |
| print(f" float32 DIV already exists: {e}") | |
| print("\nGenerating float32 FMA circuit...") | |
| try: | |
| add_float_fma(tensors, "float32", 8, 23) | |
| print(" Added float32 fused multiply-add (single rounding)") | |
| except ValueError as e: | |
| print(f" float32 FMA already exists: {e}") | |
| print("\nGenerating float32 CMP circuits...") | |
| try: | |
| add_float32_cmp(tensors) | |
| print(" Added float32 comparisons (EQ, LT, LE, GT, GE)") | |
| except ValueError as e: | |
| print(f" float32 CMP already exists: {e}") | |
| if args.apply: | |
| print(f"\nSaving: {args.model}") | |
| save_file(tensors, str(args.model), metadata=metadata or None) | |
| print(" Done.") | |
| else: | |
| print("\n[DRY-RUN] Use --apply to save.") | |
| print(f"\nTotal: {len(tensors)} tensors") | |
| total_params = sum(t.numel() for t in tensors.values()) | |
| print(f"Total params: {total_params:,}") | |
| print("=" * 60) | |
| def cmd_subleq(args) -> None: | |
| """Build the standalone SUBLEQ-8 machine file from scratch: 256 B of | |
| packed memory circuits plus the one-instruction datapath, fully wired.""" | |
| out = args.model if args.model else MODEL_DIR / "variants" / "neural_subleq8.safetensors" | |
| print("=" * 60) | |
| print(" BUILD SUBLEQ-8 (one-instruction threshold machine)") | |
| print("=" * 60) | |
| tensors: Dict[str, torch.Tensor] = {} | |
| add_decoder(tensors, 8, 256) | |
| add_memory_read_mux(tensors, 256) | |
| add_memory_write_cells(tensors, 256) | |
| add_subleq_core(tensors) | |
| tensors["manifest.data_bits"] = torch.tensor([8.0], dtype=torch.float32) | |
| tensors["manifest.addr_bits"] = torch.tensor([8.0], dtype=torch.float32) | |
| tensors["manifest.memory_bytes"] = torch.tensor([256.0], dtype=torch.float32) | |
| tensors["manifest.pc_width"] = torch.tensor([8.0], dtype=torch.float32) | |
| tensors["manifest.version"] = torch.tensor([4.0], dtype=torch.float32) | |
| tensors, reg, stats = build_inputs(tensors) | |
| total = sum(t.numel() for t in tensors.values()) | |
| logic = sum(t.numel() for k, t in tensors.items() | |
| if k.startswith("subleq.") and not k.endswith(".inputs")) | |
| print(f" Gates wired: {stats['added']} signals: {len(reg.name_to_id)}") | |
| print(f" Logic params: {logic:,} total params: {total:,}") | |
| if args.apply: | |
| metadata = {"signal_registry": reg.to_metadata(), "machine": "subleq8"} | |
| save_file(tensors, str(out), metadata=metadata) | |
| print(f" Saved: {out}") | |
| else: | |
| print(" [DRY-RUN] Use --apply to save.") | |
| print("=" * 60) | |
| def add_rv32_extras(tensors: Dict[str, torch.Tensor]) -> None: | |
| """RV32-specific circuits beyond the shared families: the full 64-bit | |
| product accumulator for MULH*, arithmetic-right-shift select muxes, | |
| the NEUR neuron-evaluation datapath, and dual-issue hazard comparators. | |
| """ | |
| # 64-bit shift-add accumulator over the 32x32 partial products. | |
| for stage in range(31): | |
| for bit in range(64): | |
| add_full_adder(tensors, f"rv32.mulacc.s{stage}.fa{bit}") | |
| # SRA = sign ? NOT(SRL(NOT x)) : SRL(x); per-bit 2:1 mux on the sign. | |
| for k in range(32): | |
| add_gate(tensors, f"rv32.sra_mux.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"rv32.sra_mux.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.sra_mux.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.sra_mux.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| # NEUR: rd = H(popcount(x & pos) - popcount(x & neg) + bias). | |
| for i in range(8): | |
| add_gate(tensors, f"rv32.neur.pos.bit{i}", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.neur.neg.bit{i}", [1.0, 1.0], [-2.0]) | |
| for side in ("pcnt", "ncnt"): | |
| for t in range(7): | |
| add_full_adder(tensors, f"rv32.neur.{side}.fa{t}") | |
| for k in range(6): | |
| add_gate(tensors, f"rv32.neur.nnot.bit{k}", [-1.0], [0.0]) | |
| add_full_adder(tensors, f"rv32.neur.diff.fa{k}") | |
| add_full_adder(tensors, f"rv32.neur.act.fa{k}") | |
| add_gate(tensors, "rv32.neur.out", [-1.0], [0.0]) | |
| # Dual-issue hazard checks: 5-bit register-index equality, four pairs. | |
| for pair in ("rd1_rs1b", "rd1_rs2b", "rd1_rd2", "rd2_rs1b"): | |
| for k in range(5): | |
| add_gate(tensors, f"rv32.hazard.{pair}.bit{k}.eq.layer1.and", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.hazard.{pair}.bit{k}.eq.layer1.nor", [-1.0, -1.0], [0.0]) | |
| add_gate(tensors, f"rv32.hazard.{pair}.bit{k}.eq", [1.0, 1.0], [-1.0]) | |
| add_gate(tensors, f"rv32.hazard.{pair}.all", [1.0] * 5, [-5.0]) | |
| # Opcode -> class name for the instruction-decode network. | |
| RV32_OPCLASS = {0x33: "op", 0x13: "op_imm", 0x03: "load", 0x23: "store", | |
| 0x63: "branch", 0x37: "lui", 0x17: "auipc", 0x6F: "jal", | |
| 0x67: "jalr", 0x73: "system", 0x53: "fp", 0x07: "flw", 0x27: "fsw"} | |
| # Which opcode classes select each immediate format. | |
| RV32_USE = {"I": ["op_imm", "load", "jalr", "flw"], "S": ["store", "fsw"], | |
| "B": ["branch"], "U": ["lui", "auipc"], "J": ["jal"]} | |
| RV32_IMM_FMTS = ("I", "S", "B", "U", "J") | |
| def _rv32_imm_src(fmt: str, k: int): | |
| """Instruction bit that supplies immediate bit k for a format, or None (0).""" | |
| if fmt == "I": | |
| return (20 + k) if k <= 11 else 31 | |
| if fmt == "S": | |
| if k <= 4: | |
| return 7 + k | |
| return (20 + k) if k <= 11 else 31 | |
| if fmt == "B": | |
| if k == 0: | |
| return None | |
| if k <= 4: | |
| return 7 + k | |
| if k <= 10: | |
| return 20 + k | |
| if k == 11: | |
| return 7 | |
| return 31 | |
| if fmt == "U": | |
| return None if k < 12 else k | |
| if fmt == "J": | |
| if k == 0: | |
| return None | |
| if k <= 10: | |
| return 20 + k | |
| if k == 11: | |
| return 20 | |
| if k <= 19: | |
| return k | |
| return 31 | |
| return None | |
| def add_rv32_decode(tensors: Dict[str, torch.Tensor]) -> None: | |
| """Instruction decode as threshold gates: opcode-class one-hots (an exact | |
| 7-bit match on the instruction's opcode field) and the sign-extended | |
| immediate for the active format, muxed from the raw instruction word. The | |
| runtime reads these gate outputs instead of slicing the word in Python.""" | |
| for opc, name in RV32_OPCLASS.items(): | |
| w = [1.0 if (opc >> j) & 1 else -1.0 for j in range(7)] | |
| add_gate(tensors, f"rv32.decode.is_{name}", w, [-float(bin(opc).count("1"))]) | |
| # FMADD/FMSUB/FNMADD/FNMSUB share the mask 100xx11 (instr bits 6,1,0 set; 5,4 clear). | |
| add_gate(tensors, "rv32.decode.is_fma", [1.0, 1.0, -1.0, -1.0, 1.0], [-3.0]) | |
| for u, cls in RV32_USE.items(): | |
| add_gate(tensors, f"rv32.decode.use_{u}", [1.0] * len(cls), [-1.0]) | |
| for k in range(32): | |
| terms = [fmt for fmt in RV32_IMM_FMTS if _rv32_imm_src(fmt, k) is not None] | |
| for fmt in terms: | |
| add_gate(tensors, f"rv32.decode.imm.bit{k}.and_{fmt}", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.decode.imm.bit{k}", [1.0] * max(1, len(terms)), [-1.0]) | |
| def add_rv32_pcnext(tensors: Dict[str, torch.Tensor]) -> None: | |
| """PC sequencing as threshold gates: a two-level mux selecting the next PC | |
| among PC+4, PC+imm (branch/jal), and (rs1+imm)&~1 (jalr). The three | |
| candidates are the gate adder's outputs; this network chooses between them | |
| from the decode's control signals rather than a Python if/elif.""" | |
| for k in range(32): | |
| # m1 = sel_target ? pcimm : pc4 | |
| add_gate(tensors, f"rv32.pcnext.m1.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"rv32.pcnext.m1.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.pcnext.m1.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.pcnext.m1.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| # pcnext = sel_jalr ? jalr : m1 | |
| add_gate(tensors, f"rv32.pcnext.bit{k}.not_sel", [-1.0], [0.0]) | |
| add_gate(tensors, f"rv32.pcnext.bit{k}.and_a", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.pcnext.bit{k}.and_b", [1.0, 1.0], [-2.0]) | |
| add_gate(tensors, f"rv32.pcnext.bit{k}.or", [1.0, 1.0], [-1.0]) | |
| def cmd_rv32(args) -> None: | |
| """Build the standalone RV32 threshold processor file from scratch: | |
| 64 KB packed memory, the shared 32-bit circuit families, the composed | |
| float32 pipelines as the FPU, and the rv32-specific extras.""" | |
| out = args.model if args.model else MODEL_DIR / "variants" / "neural_rv32.safetensors" | |
| print("=" * 60) | |
| print(" BUILD NEURAL_RV32 (RV32IM + F-subset threshold processor)") | |
| print("=" * 60) | |
| tensors: Dict[str, torch.Tensor] = {} | |
| add_decoder(tensors, 16, 65536) | |
| add_memory_read_mux(tensors, 65536) | |
| add_memory_write_cells(tensors, 65536) | |
| add_ripple_carry_nbits(tensors, 32) | |
| add_sub_nbits(tensors, 32) | |
| add_comparators_nbits(tensors, 32) | |
| add_bitwise_nbits(tensors, 32) | |
| add_barrel_shifter_nbits(tensors, 32) | |
| add_priority_encoder_nbits(tensors, 32) # leading-one detect for FCVT.S.W | |
| add_mul_nbits(tensors, 32) | |
| add_div_nbits(tensors, 32) | |
| add_neg_nbits(tensors, 32) | |
| add_float32_add(tensors) | |
| add_float32_mul(tensors) | |
| add_float32_div(tensors) | |
| add_float_fma(tensors, "float32", 8, 23) | |
| add_float32_cmp(tensors) | |
| add_rv32_extras(tensors) | |
| add_rv32_decode(tensors) | |
| add_rv32_pcnext(tensors) | |
| tensors["manifest.data_bits"] = torch.tensor([32.0], dtype=torch.float32) | |
| tensors["manifest.addr_bits"] = torch.tensor([16.0], dtype=torch.float32) | |
| tensors["manifest.memory_bytes"] = torch.tensor([65536.0], dtype=torch.float32) | |
| tensors["manifest.pc_width"] = torch.tensor([16.0], dtype=torch.float32) | |
| tensors["manifest.registers"] = torch.tensor([32.0], dtype=torch.float32) | |
| tensors["manifest.version"] = torch.tensor([4.0], dtype=torch.float32) | |
| tensors, reg, stats = build_inputs(tensors) | |
| total = sum(t.numel() for t in tensors.values()) | |
| print(f" Gates wired: {stats['added']} signals: {len(reg.name_to_id)}") | |
| print(f" Total params: {total:,}") | |
| if args.apply: | |
| metadata = {"signal_registry": reg.to_metadata(), "machine": "rv32"} | |
| save_file(tensors, str(out), metadata=metadata) | |
| print(f" Saved: {out}") | |
| else: | |
| print(" [DRY-RUN] Use --apply to save.") | |
| print("=" * 60) | |
| def cmd_all(args) -> None: | |
| print("Running: memory") | |
| cmd_memory(args) | |
| print("\nRunning: alu") | |
| cmd_alu(args) | |
| print("\nRunning: inputs") | |
| cmd_inputs(args) | |
| def main() -> None: | |
| parser = argparse.ArgumentParser( | |
| description="Build tools for threshold computer safetensors", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Memory Profiles: | |
| full 64KB (16-bit addr) - Full CPU mode | |
| reduced 4KB (12-bit addr) - Reduced CPU | |
| small 1KB (10-bit addr) - 32-bit arithmetic scratch | |
| scratchpad 256B (8-bit addr) - LLM scratchpad | |
| registers 16B (4-bit addr) - LLM register file | |
| none 0B (no memory) - Pure ALU for LLM | |
| ALU Bit Widths: | |
| 8 Standard 8-bit ALU (default) | |
| 16 16-bit ALU (0-65535) | |
| 32 32-bit ALU (0-4294967295) | |
| Output Filenames (auto-generated from config): | |
| Format: neural_{alu|computer}{BITS}[_{MEMORY}].safetensors | |
| Memory suffix: | |
| -m full -> (none) | |
| -m reduced -> _reduced | |
| -m small -> _small | |
| -m scratchpad -> _scratchpad | |
| -m registers -> _registers | |
| -m none -> (uses "alu" instead of "computer") | |
| -a N -> _addrN | |
| Examples: | |
| neural_alu8.safetensors # 8-bit, no memory | |
| neural_alu16.safetensors # 16-bit, no memory | |
| neural_alu32.safetensors # 32-bit, no memory | |
| neural_computer8.safetensors # 8-bit, full memory | |
| neural_computer16.safetensors # 16-bit, full memory | |
| neural_computer32.safetensors # 32-bit, full memory | |
| neural_computer8_reduced.safetensors # 8-bit, reduced memory | |
| neural_computer32_reduced.safetensors# 32-bit, reduced memory | |
| neural_computer8_small.safetensors # 8-bit, small memory | |
| neural_computer32_small.safetensors # 32-bit, small memory | |
| neural_computer8_addr12.safetensors # 8-bit, custom 12-bit address | |
| neural_computer32_addr10.safetensors # 32-bit, custom 10-bit address | |
| Usage (note: options must come BEFORE subcommand): | |
| python build.py --apply all # -> neural_computer8.safetensors | |
| python build.py -m none --apply all # -> neural_alu8.safetensors | |
| python build.py -m reduced --apply all # -> neural_computer8_reduced.safetensors | |
| python build.py --bits 16 --apply all # -> neural_computer16.safetensors | |
| python build.py --bits 32 --apply all # -> neural_computer32.safetensors | |
| python build.py --bits 32 -m none --apply all # -> neural_alu32.safetensors | |
| python build.py --bits 32 -m small --apply all # -> neural_computer32_small.safetensors | |
| python build.py --bits 32 -a 10 --apply all # -> neural_computer32_addr10.safetensors | |
| """ | |
| ) | |
| parser.add_argument( | |
| "--model", type=Path, default=None, | |
| help="Output path. Auto-generated as neural_{alu|computer}{BITS}[_{MEMORY}].safetensors if not specified" | |
| ) | |
| parser.add_argument( | |
| "--apply", action="store_true", | |
| help="Apply changes to model file. Without this flag, runs in dry-run mode (no writes)" | |
| ) | |
| parser.add_argument( | |
| "--manifest", action="store_true", | |
| help="Write tensors.txt manifest listing all tensors (memory command only)" | |
| ) | |
| parser.add_argument( | |
| "--bits", "-b", | |
| type=int, | |
| choices=SUPPORTED_BITS, | |
| default=8, | |
| help="ALU bit width. 8=0-255 (default), 16=0-65535, 32=0-4294967295" | |
| ) | |
| mem_group = parser.add_mutually_exclusive_group() | |
| mem_group.add_argument( | |
| "--memory-profile", "-m", | |
| choices=list(MEMORY_PROFILES.keys()), | |
| help="""Memory profile: | |
| full=64KB/16-bit addr (suffix: none), | |
| reduced=4KB/12-bit (suffix: _reduced), | |
| small=1KB/10-bit (suffix: _small), | |
| scratchpad=256B/8-bit (suffix: _scratchpad), | |
| registers=16B/4-bit (suffix: _registers), | |
| none=0B/pure ALU (uses 'alu' in filename)""" | |
| ) | |
| mem_group.add_argument( | |
| "--addr-bits", "-a", | |
| type=int, | |
| choices=range(0, 17), | |
| metavar="N", | |
| help="Custom address bus width 0-16. Memory size=2^N bytes. 0=pure ALU. Suffix: _addrN" | |
| ) | |
| subparsers = parser.add_subparsers(dest="command", help="Subcommands") | |
| subparsers.add_parser("memory", help="Generate memory circuits (decoder, read mux, write cells)") | |
| subparsers.add_parser("alu", help="Generate N-bit ALU circuits (adder, sub, mul, div, cmp, bitwise, shift)") | |
| subparsers.add_parser("inputs", help="Add .inputs metadata tensors for gate routing") | |
| subparsers.add_parser("all", help="Run all: memory -> alu -> inputs") | |
| subparsers.add_parser("subleq", help="Build the standalone SUBLEQ-8 machine file") | |
| subparsers.add_parser("rv32", help="Build the standalone RV32 threshold processor file") | |
| args = parser.parse_args() | |
| if args.model is None and args.command in ("subleq", "rv32"): | |
| pass # cmd_subleq supplies its own default | |
| elif args.model is None: | |
| args.model = get_model_path( | |
| bits=args.bits, | |
| memory_profile=getattr(args, 'memory_profile', None), | |
| addr_bits=getattr(args, 'addr_bits', None) | |
| ) | |
| print(f"Auto-generated model path: {args.model}") | |
| if args.command == "memory": | |
| cmd_memory(args) | |
| elif args.command == "alu": | |
| cmd_alu(args) | |
| elif args.command == "inputs": | |
| cmd_inputs(args) | |
| elif args.command == "all": | |
| cmd_all(args) | |
| elif args.command == "subleq": | |
| cmd_subleq(args) | |
| elif args.command == "rv32": | |
| cmd_rv32(args) | |
| else: | |
| parser.print_help() | |
| if __name__ == "__main__": | |
| main() | |