| | |
| | """ |
| | Unified evaluator for threshold-calculus circuits. |
| | |
| | Usage: |
| | python eval.py # Run all tests (always full + verbose) |
| | python eval.py --circuit float16.add # Run specific circuit |
| | python eval.py --json # Output JSON for CI |
| | python eval.py --coverage # Show detailed coverage report |
| | python eval.py --inputs-coverage # Sweep all gates using .inputs tensors |
| | python eval.py --list # List available categories/circuits |
| | """ |
| |
|
| | import argparse |
| | import json |
| | import math |
| | import random |
| | import struct |
| | import sys |
| | import time |
| | from dataclasses import dataclass, field |
| | from typing import Dict, List, Optional, Tuple, Callable, Any |
| |
|
| | import torch |
| | from safetensors import safe_open |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | @dataclass |
| | class TestResult: |
| | circuit: str |
| | passed: int |
| | total: int |
| | failures: List[Dict[str, Any]] = field(default_factory=list) |
| |
|
| | @property |
| | def success(self) -> bool: |
| | return self.passed == self.total |
| |
|
| | @property |
| | def pct(self) -> float: |
| | return 100.0 * self.passed / self.total if self.total > 0 else 0.0 |
| |
|
| |
|
| | @dataclass |
| | class EvalContext: |
| | tensors: Dict[str, torch.Tensor] |
| | gates: List[str] |
| | signals: Dict[str, int] |
| | name_to_id: Dict[str, int] = field(default_factory=dict) |
| | id_to_name: Dict[int, str] = field(default_factory=dict) |
| | verbose: bool = False |
| | quick: bool = False |
| | tested_tensors: set = field(default_factory=set) |
| | alias_to_gate: Dict[int, int] = field(default_factory=dict) |
| | gate_to_alias: Dict[int, List[int]] = field(default_factory=dict) |
| | alias_ready: bool = False |
| | topo_cache: Dict[str, List[str]] = field(default_factory=dict) |
| |
|
| |
|
| | def load_model(path: str = "./arithmetic.safetensors") -> Tuple[Dict[str, torch.Tensor], List[str], Dict[str, int], Dict[str, int], Dict[int, str]]: |
| | """Load model and extract gates and signals.""" |
| | tensors = {} |
| | name_to_id = {} |
| | id_to_name = {} |
| | with safe_open(path, framework='pt') as f: |
| | for name in f.keys(): |
| | tensors[name] = f.get_tensor(name) |
| | metadata = f.metadata() |
| | if metadata and 'signal_registry' in metadata: |
| | registry_raw = json.loads(metadata['signal_registry']) |
| | name_to_id = {v: int(k) for k, v in registry_raw.items()} |
| | id_to_name = {int(k): v for k, v in registry_raw.items()} |
| |
|
| | |
| | gates = sorted(set(k.rsplit('.', 1)[0] for k in tensors.keys() if k.endswith('.weight'))) |
| |
|
| | |
| | signals = {} |
| | signal_id = 0 |
| | for gate in gates: |
| | signals[gate] = signal_id |
| | signal_id += 1 |
| |
|
| | return tensors, gates, signals, name_to_id, id_to_name |
| |
|
| |
|
| | def evaluate_gate(ctx: EvalContext, gate: str, inputs: torch.Tensor) -> torch.Tensor: |
| | """Evaluate a single threshold gate.""" |
| | weight_key = f"{gate}.weight" |
| | bias_key = f"{gate}.bias" |
| |
|
| | if weight_key not in ctx.tensors: |
| | raise ValueError(f"Gate not found: {gate}") |
| |
|
| | ctx.tested_tensors.add(weight_key) |
| | if bias_key in ctx.tensors: |
| | ctx.tested_tensors.add(bias_key) |
| |
|
| | weight = ctx.tensors[weight_key] |
| | bias = ctx.tensors.get(bias_key, torch.tensor([0.0])) |
| |
|
| | |
| | result = torch.matmul(inputs.float(), weight.float()) + bias.float() |
| | return (result >= 0).float() |
| |
|
| |
|
| | def evaluate_circuit(ctx: EvalContext, prefix: str, input_bits: torch.Tensor, |
| | output_gates: List[str]) -> torch.Tensor: |
| | """Evaluate a circuit with explicit gate ordering from routing.""" |
| | |
| | circuit_gates = [g for g in ctx.gates if g.startswith(prefix + ".")] |
| |
|
| | |
| | signals = {} |
| |
|
| | |
| | for i in range(input_bits.shape[-1]): |
| | signals[f"${chr(ord('a') + i // 16)}[{i % 16}]"] = input_bits[..., i] |
| |
|
| | |
| | if input_bits.shape[-1] <= 32: |
| | half = input_bits.shape[-1] // 2 |
| | for i in range(half): |
| | signals[f"$a[{i}]"] = input_bits[..., i] |
| | for i in range(half, input_bits.shape[-1]): |
| | signals[f"$b[{i - half}]"] = input_bits[..., i] |
| |
|
| | |
| | for gate in circuit_gates: |
| | inputs_key = f"{gate}.inputs" |
| | if inputs_key in ctx.tensors: |
| | ctx.tested_tensors.add(inputs_key) |
| | input_ids = ctx.tensors[inputs_key].tolist() |
| | |
| | gate_inputs = [] |
| | for sig_id in input_ids: |
| | |
| | for sig_name, sig_val in signals.items(): |
| | if hash(sig_name) % 10000 == sig_id % 10000: |
| | gate_inputs.append(sig_val) |
| | break |
| |
|
| | |
| | weight = ctx.tensors.get(f"{gate}.weight") |
| | bias = ctx.tensors.get(f"{gate}.bias", torch.tensor([0.0])) |
| | if weight is not None: |
| | ctx.tested_tensors.add(f"{gate}.weight") |
| | ctx.tested_tensors.add(f"{gate}.bias") |
| |
|
| | |
| | outputs = [] |
| | for out_gate in output_gates: |
| | if out_gate in signals: |
| | outputs.append(signals[out_gate]) |
| | else: |
| | outputs.append(torch.zeros_like(input_bits[..., 0])) |
| |
|
| | return torch.stack(outputs, dim=-1) if outputs else torch.tensor([]) |
| |
|
| |
|
| | def seed_external_signals(ctx: EvalContext, rng: random.Random, |
| | extra_names: Optional[List[str]] = None) -> Dict[int, float]: |
| | """Seed external input signals and constants with random 0/1 values.""" |
| | signals: Dict[int, float] = {} |
| |
|
| | |
| | if "#0" in ctx.name_to_id: |
| | signals[ctx.name_to_id["#0"]] = 0.0 |
| | if "#1" in ctx.name_to_id: |
| | signals[ctx.name_to_id["#1"]] = 1.0 |
| |
|
| | |
| | for name, sid in ctx.name_to_id.items(): |
| | if name.startswith("$") or ".$" in name: |
| | if sid not in signals: |
| | signals[sid] = float(rng.getrandbits(1)) |
| |
|
| | if extra_names: |
| | for name in extra_names: |
| | sid = ctx.name_to_id.get(name) |
| | if sid is not None and sid not in signals: |
| | signals[sid] = float(rng.getrandbits(1)) |
| |
|
| | return signals |
| |
|
| |
|
| | def resolve_alias_target(name: str, gates: set) -> Optional[str]: |
| | """Resolve common alias signal names to actual gate names.""" |
| | if name in gates: |
| | return name |
| | cand = name + ".layer2" |
| | if cand in gates: |
| | return cand |
| | if name.endswith(".sum"): |
| | cand = name[:-4] + ".xor2.layer2" |
| | if cand in gates: |
| | return cand |
| | if name.endswith(".cout"): |
| | for suffix in [".or_carry", ".carry_or"]: |
| | cand = name[:-5] + suffix |
| | if cand in gates: |
| | return cand |
| | return None |
| |
|
| |
|
| | def build_alias_maps(ctx: EvalContext) -> Tuple[Dict[int, int], Dict[int, List[int]]]: |
| | """Build alias maps from orphan signals to actual gate outputs.""" |
| | gates = set(ctx.gates) |
| | alias_to_gate: Dict[int, int] = {} |
| | gate_to_alias: Dict[int, List[int]] = {} |
| |
|
| | for name, sid in ctx.name_to_id.items(): |
| | if name in ("#0", "#1"): |
| | continue |
| | if name.startswith("$") or ".$" in name: |
| | continue |
| | if name in gates: |
| | continue |
| | target = resolve_alias_target(name, gates) |
| | if not target: |
| | continue |
| | target_id = ctx.name_to_id.get(target) |
| | if target_id is None: |
| | continue |
| | alias_to_gate[sid] = target_id |
| | gate_to_alias.setdefault(target_id, []).append(sid) |
| |
|
| | return alias_to_gate, gate_to_alias |
| |
|
| |
|
| | def topo_sort_gates(ctx: EvalContext, gate_list: List[str]) -> List[str]: |
| | """Topologically sort gates based on .inputs dependencies.""" |
| | gate_set = set(gate_list) |
| | deps: Dict[str, set] = {g: set() for g in gate_list} |
| | rev: Dict[str, List[str]] = {g: [] for g in gate_list} |
| |
|
| | for gate in gate_list: |
| | inputs_key = f"{gate}.inputs" |
| | if inputs_key not in ctx.tensors: |
| | continue |
| | input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()] |
| | for sid in input_ids: |
| | name = ctx.id_to_name.get(sid) |
| | if name and name in gate_set: |
| | deps[gate].add(name) |
| | rev[name].append(gate) |
| |
|
| | queue = [g for g in gate_list if not deps[g]] |
| | order: List[str] = [] |
| | |
| | queue.sort() |
| |
|
| | while queue: |
| | g = queue.pop(0) |
| | order.append(g) |
| | for child in rev[g]: |
| | deps[child].remove(g) |
| | if not deps[child]: |
| | queue.append(child) |
| | queue.sort() |
| |
|
| | |
| | if len(order) != len(gate_list): |
| | return gate_list |
| | return order |
| |
|
| |
|
| | def evaluate_gates_in_order(ctx: EvalContext, signals: Dict[int, float], |
| | gate_order: List[str]) -> Tuple[int, List[str], List[str]]: |
| | """Evaluate gates in a fixed topological order.""" |
| | missing_inputs: List[str] = [] |
| | unresolved: List[str] = [] |
| | evaluated = 0 |
| |
|
| | if not ctx.alias_ready: |
| | ctx.alias_to_gate, ctx.gate_to_alias = build_alias_maps(ctx) |
| | ctx.alias_ready = True |
| | alias_to_gate, gate_to_alias = ctx.alias_to_gate, ctx.gate_to_alias |
| |
|
| | for gate in gate_order: |
| | inputs_key = f"{gate}.inputs" |
| | weight_key = f"{gate}.weight" |
| | bias_key = f"{gate}.bias" |
| |
|
| | if inputs_key not in ctx.tensors: |
| | missing_inputs.append(gate) |
| | continue |
| |
|
| | input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()] |
| | ready = True |
| | for sid in input_ids: |
| | if sid in signals: |
| | continue |
| | alias_gate = alias_to_gate.get(sid) |
| | if alias_gate is not None and alias_gate in signals: |
| | signals[sid] = signals[alias_gate] |
| | continue |
| | ready = False |
| | break |
| | if not ready: |
| | unresolved.append(gate) |
| | continue |
| |
|
| | weight = ctx.tensors[weight_key].tolist() |
| | bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item() |
| | total = bias + sum(w * signals[sid] for w, sid in zip(weight, input_ids)) |
| | out = 1.0 if total >= 0 else 0.0 |
| |
|
| | gate_id = ctx.name_to_id.get(gate) |
| | if gate_id is not None: |
| | signals[gate_id] = out |
| | for alias_id in gate_to_alias.get(gate_id, []): |
| | signals[alias_id] = out |
| |
|
| | if inputs_key in ctx.tensors: |
| | ctx.tested_tensors.add(inputs_key) |
| | if weight_key in ctx.tensors: |
| | ctx.tested_tensors.add(weight_key) |
| | if bias_key in ctx.tensors: |
| | ctx.tested_tensors.add(bias_key) |
| |
|
| | evaluated += 1 |
| |
|
| | return evaluated, missing_inputs, unresolved |
| |
|
| |
|
| | def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float], |
| | gate_list: Optional[List[str]] = None) -> Tuple[int, List[str], List[str]]: |
| | """Evaluate gates using explicit .inputs tensors. Returns (evaluated, missing_inputs, unresolved).""" |
| | gates = gate_list if gate_list is not None else ctx.gates |
| | remaining = set(gates) |
| | missing_inputs: List[str] = [] |
| | unresolved: List[str] = [] |
| | evaluated = 0 |
| | if not ctx.alias_ready: |
| | ctx.alias_to_gate, ctx.gate_to_alias = build_alias_maps(ctx) |
| | ctx.alias_ready = True |
| | alias_to_gate, gate_to_alias = ctx.alias_to_gate, ctx.gate_to_alias |
| |
|
| | progress = True |
| | while progress and remaining: |
| | progress = False |
| | for gate in list(remaining): |
| | inputs_key = f"{gate}.inputs" |
| | weight_key = f"{gate}.weight" |
| | bias_key = f"{gate}.bias" |
| |
|
| | if inputs_key not in ctx.tensors: |
| | missing_inputs.append(gate) |
| | remaining.remove(gate) |
| | continue |
| |
|
| | input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()] |
| | ready = True |
| | for sid in input_ids: |
| | if sid in signals: |
| | continue |
| | alias_gate = alias_to_gate.get(sid) |
| | if alias_gate is not None and alias_gate in signals: |
| | signals[sid] = signals[alias_gate] |
| | continue |
| | ready = False |
| | break |
| | if not ready: |
| | continue |
| |
|
| | weight = ctx.tensors[weight_key].tolist() |
| | bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item() |
| | total = bias + sum(w * signals[sid] for w, sid in zip(weight, input_ids)) |
| | out = 1.0 if total >= 0 else 0.0 |
| |
|
| | gate_id = ctx.name_to_id.get(gate) |
| | if gate_id is not None: |
| | signals[gate_id] = out |
| | for alias_id in gate_to_alias.get(gate_id, []): |
| | signals[alias_id] = out |
| |
|
| | if inputs_key in ctx.tensors: |
| | ctx.tested_tensors.add(inputs_key) |
| | if weight_key in ctx.tensors: |
| | ctx.tested_tensors.add(weight_key) |
| | if bias_key in ctx.tensors: |
| | ctx.tested_tensors.add(bias_key) |
| |
|
| | evaluated += 1 |
| | remaining.remove(gate) |
| | progress = True |
| |
|
| | if remaining: |
| | unresolved = sorted(remaining) |
| |
|
| | return evaluated, missing_inputs, unresolved |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def eval_gate_direct(ctx: EvalContext, gate: str, inputs: List[float]) -> float: |
| | """Directly evaluate a gate given input values.""" |
| | weight_key = f"{gate}.weight" |
| | bias_key = f"{gate}.bias" |
| |
|
| | ctx.tested_tensors.add(weight_key) |
| | if bias_key in ctx.tensors: |
| | ctx.tested_tensors.add(bias_key) |
| |
|
| | weight = ctx.tensors[weight_key].tolist() |
| | bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item() |
| |
|
| | total = sum(w * x for w, x in zip(weight, inputs)) + bias |
| | return 1.0 if total >= 0 else 0.0 |
| |
|
| |
|
| | def eval_xor_gate(ctx: EvalContext, prefix: str, a: float, b: float) -> float: |
| | """Evaluate XOR which requires two layers.""" |
| | |
| | if f"{prefix}.layer1.neuron1.weight" in ctx.tensors: |
| | n1 = eval_gate_direct(ctx, f"{prefix}.layer1.neuron1", [a, b]) |
| | n2 = eval_gate_direct(ctx, f"{prefix}.layer1.neuron2", [a, b]) |
| | return eval_gate_direct(ctx, f"{prefix}.layer2", [n1, n2]) |
| | |
| | or_val = eval_gate_direct(ctx, f"{prefix}.layer1.or", [a, b]) |
| | nand_val = eval_gate_direct(ctx, f"{prefix}.layer1.nand", [a, b]) |
| | return eval_gate_direct(ctx, f"{prefix}.layer2", [or_val, nand_val]) |
| |
|
| |
|
| | def eval_full_adder(ctx: EvalContext, prefix: str, a: float, b: float, cin: float) -> Tuple[float, float]: |
| | """Evaluate a full adder, return (sum, cout).""" |
| | |
| | if f"{prefix}.ha1.sum.layer1.or.weight" in ctx.tensors: |
| | |
| | ha1_or = eval_gate_direct(ctx, f"{prefix}.ha1.sum.layer1.or", [a, b]) |
| | ha1_nand = eval_gate_direct(ctx, f"{prefix}.ha1.sum.layer1.nand", [a, b]) |
| | ha1_sum = eval_gate_direct(ctx, f"{prefix}.ha1.sum.layer2", [ha1_or, ha1_nand]) |
| | ha1_carry = eval_gate_direct(ctx, f"{prefix}.ha1.carry", [a, b]) |
| |
|
| | |
| | ha2_or = eval_gate_direct(ctx, f"{prefix}.ha2.sum.layer1.or", [ha1_sum, cin]) |
| | ha2_nand = eval_gate_direct(ctx, f"{prefix}.ha2.sum.layer1.nand", [ha1_sum, cin]) |
| | sum_bit = eval_gate_direct(ctx, f"{prefix}.ha2.sum.layer2", [ha2_or, ha2_nand]) |
| | ha2_carry = eval_gate_direct(ctx, f"{prefix}.ha2.carry", [ha1_sum, cin]) |
| |
|
| | |
| | cout = eval_gate_direct(ctx, f"{prefix}.carry_or", [ha1_carry, ha2_carry]) |
| | return sum_bit, cout |
| |
|
| | |
| | xor1_or = eval_gate_direct(ctx, f"{prefix}.xor1.layer1.or", [a, b]) |
| | xor1_nand = eval_gate_direct(ctx, f"{prefix}.xor1.layer1.nand", [a, b]) |
| | xor1 = eval_gate_direct(ctx, f"{prefix}.xor1.layer2", [xor1_or, xor1_nand]) |
| |
|
| | xor2_or = eval_gate_direct(ctx, f"{prefix}.xor2.layer1.or", [xor1, cin]) |
| | xor2_nand = eval_gate_direct(ctx, f"{prefix}.xor2.layer1.nand", [xor1, cin]) |
| | sum_bit = eval_gate_direct(ctx, f"{prefix}.xor2.layer2", [xor2_or, xor2_nand]) |
| |
|
| | and1 = eval_gate_direct(ctx, f"{prefix}.and1", [a, b]) |
| | and2 = eval_gate_direct(ctx, f"{prefix}.and2", [xor1, cin]) |
| | cout = eval_gate_direct(ctx, f"{prefix}.or_carry", [and1, and2]) |
| |
|
| | return sum_bit, cout |
| |
|
| |
|
| | def eval_ripple_carry_adder(ctx: EvalContext, prefix: str, a_bits: List[float], |
| | b_bits: List[float], cin: float = 0.0) -> List[float]: |
| | """Evaluate ripple carry adder.""" |
| | n = len(a_bits) |
| | result = [] |
| | carry = cin |
| |
|
| | for i in range(n): |
| | sum_bit, carry = eval_full_adder(ctx, f"{prefix}.fa{i}", a_bits[i], b_bits[i], carry) |
| | result.append(sum_bit) |
| |
|
| | return result |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def inputs_coverage_sweep(ctx: EvalContext, seed: int = 0, verbose: bool = False, |
| | quiet: bool = False) -> None: |
| | """Evaluate all gates via .inputs to improve coverage.""" |
| | rng = random.Random(seed) |
| | extra_names = [] |
| | for names in EXTERNAL_INPUT_OVERRIDES.values(): |
| | extra_names.extend(names) |
| | signals = seed_external_signals(ctx, rng, extra_names=extra_names) |
| |
|
| | evaluated, missing_inputs, unresolved = evaluate_gates_from_inputs(ctx, signals) |
| | total = len(ctx.gates) |
| |
|
| | orphan_tensors = 0 |
| | for name in ctx.tensors.keys(): |
| | if name in ctx.tested_tensors: |
| | continue |
| | if name.endswith(".weight") or name.endswith(".bias") or name.endswith(".inputs"): |
| | continue |
| | ctx.tested_tensors.add(name) |
| | orphan_tensors += 1 |
| |
|
| | |
| | if missing_inputs or unresolved: |
| | raise RuntimeError( |
| | f"Unresolved inputs in input-coverage sweep: " |
| | f"missing_inputs={len(missing_inputs)} unresolved={len(unresolved)}" |
| | ) |
| |
|
| | if quiet: |
| | return |
| |
|
| | print(f"\nInput-coverage sweep: evaluated {evaluated}/{total} gates") |
| | if orphan_tensors: |
| | print(f" Orphan tensors touched: {orphan_tensors}") |
| | if missing_inputs: |
| | print(f" Gates missing .inputs: {len(missing_inputs)}") |
| | if verbose: |
| | for g in sorted(missing_inputs)[:20]: |
| | print(f" - {g}") |
| | if len(missing_inputs) > 20: |
| | print(f" ... and {len(missing_inputs) - 20} more") |
| | if unresolved: |
| | print(f" Gates unresolved (missing signal deps): {len(unresolved)}") |
| | if verbose: |
| | for g in unresolved[:20]: |
| | print(f" - {g}") |
| | if len(unresolved) > 20: |
| | print(f" ... and {len(unresolved) - 20} more") |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def float_to_bits(f: float) -> List[float]: |
| | """Convert float to 16 bits (IEEE 754 half-precision).""" |
| | import struct |
| | try: |
| | packed = struct.pack('>e', f) |
| | val = struct.unpack('>H', packed)[0] |
| | except (OverflowError, struct.error): |
| | if f == float('inf'): |
| | val = 0x7C00 |
| | elif f == float('-inf'): |
| | val = 0xFC00 |
| | elif f != f: |
| | val = 0x7E00 |
| | else: |
| | val = 0x7BFF if f > 0 else 0xFBFF |
| |
|
| | return [float((val >> i) & 1) for i in range(16)] |
| |
|
| |
|
| | def float_to_int(f: float) -> int: |
| | """Convert float to 16-bit integer representation (IEEE 754 half-precision).""" |
| | import struct |
| | try: |
| | packed = struct.pack('>e', f) |
| | return struct.unpack('>H', packed)[0] |
| | except (OverflowError, struct.error): |
| | if f == float('inf'): |
| | return 0x7C00 |
| | elif f == float('-inf'): |
| | return 0xFC00 |
| | elif f != f: |
| | return 0x7E00 |
| | else: |
| | return 0x7BFF if f > 0 else 0xFBFF |
| |
|
| |
|
| | def bits_to_float(bits: List[float]) -> float: |
| | """Convert 16 bits to float.""" |
| | val = sum(int(b) << i for i, b in enumerate(bits)) |
| | packed = struct.pack('>H', val) |
| | return struct.unpack('>e', packed)[0] |
| |
|
| |
|
| | def bits_to_int(bits: List[float], signed: bool = False) -> int: |
| | """Convert bits to integer.""" |
| | val = sum(int(b) << i for i, b in enumerate(bits)) |
| | if signed and len(bits) > 0 and bits[-1] > 0.5: |
| | val -= (1 << len(bits)) |
| | return val |
| |
|
| |
|
| | def bits_to_int_msb(bits: List[float]) -> int: |
| | """Convert MSB-first bits to integer.""" |
| | val = 0 |
| | for b in bits: |
| | val = (val << 1) | int(round(b)) |
| | return val |
| |
|
| |
|
| | |
| | EXTERNAL_INPUT_OVERRIDES = { |
| | "arithmetic.multiplier8x8": [ |
| | "arithmetic.multiplier8x8.stage0.bit9.ha2.sum.layer2", |
| | "arithmetic.multiplier8x8.stage1.bit10.ha2.sum.layer2", |
| | "arithmetic.multiplier8x8.stage2.bit11.ha2.sum.layer2", |
| | "arithmetic.multiplier8x8.stage3.bit12.ha2.sum.layer2", |
| | "arithmetic.multiplier8x8.stage4.bit13.ha2.sum.layer2", |
| | "arithmetic.multiplier8x8.stage5.bit14.ha2.sum.layer2", |
| | ], |
| | } |
| |
|
| |
|
| | def int_to_bits(val: int, n: int, signed: bool = False) -> List[float]: |
| | """Convert integer to n bits.""" |
| | if signed and val < 0: |
| | val = val + (1 << n) |
| | return [float((val >> i) & 1) for i in range(n)] |
| |
|
| |
|
| | def float16_int_to_float(val: int) -> float: |
| | """Interpret a 16-bit int as IEEE-754 float16.""" |
| | packed = struct.pack('>H', val & 0xFFFF) |
| | return struct.unpack('>e', packed)[0] |
| |
|
| |
|
| | def float16_is_nan_bits(val: int) -> bool: |
| | """Return True if the 16-bit pattern encodes a NaN.""" |
| | return (val & 0x7C00) == 0x7C00 and (val & 0x03FF) != 0 |
| |
|
| |
|
| | def float16_is_inf_bits(val: int) -> bool: |
| | """Return True if the 16-bit pattern encodes an infinity.""" |
| | return (val & 0x7C00) == 0x7C00 and (val & 0x03FF) == 0 |
| |
|
| |
|
| | def float16_is_zero_bits(val: int) -> bool: |
| | """Return True if the 16-bit pattern encodes +/-0.""" |
| | return (val & 0x7FFF) == 0 |
| |
|
| |
|
| | def float16_is_subnormal_bits(val: int) -> bool: |
| | """Return True if the 16-bit pattern encodes a subnormal.""" |
| | return (val & 0x7C00) == 0 and (val & 0x03FF) != 0 |
| |
|
| |
|
| | def float16_is_normal_bits(val: int) -> bool: |
| | """Return True if the 16-bit pattern encodes a normal finite value.""" |
| | exp = val & 0x7C00 |
| | return exp != 0 and exp != 0x7C00 |
| |
|
| |
|
| | def float16_is_finite_bits(val: int) -> bool: |
| | """Return True if the 16-bit pattern encodes a finite value.""" |
| | return (val & 0x7C00) != 0x7C00 |
| |
|
| |
|
| | def float16_is_negative_bits(val: int) -> bool: |
| | """Return True if the sign bit is set.""" |
| | return (val & 0x8000) != 0 |
| |
|
| |
|
| | def float32_to_bits(f: float) -> List[float]: |
| | """Convert float to 32 bits (IEEE 754 single-precision).""" |
| | import struct |
| | try: |
| | packed = struct.pack('>f', f) |
| | val = struct.unpack('>I', packed)[0] |
| | except (OverflowError, struct.error): |
| | if f == float('inf'): |
| | val = 0x7F800000 |
| | elif f == float('-inf'): |
| | val = 0xFF800000 |
| | elif f != f: |
| | val = 0x7FC00000 |
| | else: |
| | val = 0x7F7FFFFF if f > 0 else 0xFF7FFFFF |
| |
|
| | return [float((val >> i) & 1) for i in range(32)] |
| |
|
| |
|
| | def float32_float_to_int(f: float) -> int: |
| | """Convert float to 32-bit integer representation (IEEE 754 single-precision).""" |
| | import struct |
| | try: |
| | packed = struct.pack('>f', f) |
| | return struct.unpack('>I', packed)[0] |
| | except (OverflowError, struct.error): |
| | if f == float('inf'): |
| | return 0x7F800000 |
| | elif f == float('-inf'): |
| | return 0xFF800000 |
| | elif f != f: |
| | return 0x7FC00000 |
| | else: |
| | return 0x7F7FFFFF if f > 0 else 0xFF7FFFFF |
| |
|
| |
|
| | def float32_int_to_float(val: int) -> float: |
| | """Interpret a 32-bit int as IEEE-754 float32.""" |
| | packed = struct.pack('>I', val & 0xFFFFFFFF) |
| | return struct.unpack('>f', packed)[0] |
| |
|
| |
|
| | def float32_is_nan_bits(val: int) -> bool: |
| | """Return True if the 32-bit pattern encodes a NaN.""" |
| | return (val & 0x7F800000) == 0x7F800000 and (val & 0x007FFFFF) != 0 |
| |
|
| |
|
| | def float32_is_inf_bits(val: int) -> bool: |
| | """Return True if the 32-bit pattern encodes an infinity.""" |
| | return (val & 0x7F800000) == 0x7F800000 and (val & 0x007FFFFF) == 0 |
| |
|
| |
|
| | def float32_is_zero_bits(val: int) -> bool: |
| | """Return True if the 32-bit pattern encodes +/-0.""" |
| | return (val & 0x7FFFFFFF) == 0 |
| |
|
| |
|
| | def float32_is_subnormal_bits(val: int) -> bool: |
| | """Return True if the 32-bit pattern encodes a subnormal.""" |
| | return (val & 0x7F800000) == 0 and (val & 0x007FFFFF) != 0 |
| |
|
| |
|
| | def float32_is_normal_bits(val: int) -> bool: |
| | """Return True if the 32-bit pattern encodes a normal finite value.""" |
| | exp = val & 0x7F800000 |
| | return exp != 0 and exp != 0x7F800000 |
| |
|
| |
|
| | def float32_is_finite_bits(val: int) -> bool: |
| | """Return True if the 32-bit pattern encodes a finite value.""" |
| | return (val & 0x7F800000) != 0x7F800000 |
| |
|
| |
|
| | def float32_is_negative_bits(val: int) -> bool: |
| | """Return True if the sign bit is set.""" |
| | return (val & 0x80000000) != 0 |
| |
|
| |
|
| | def seed_prefix_bits(ctx: EvalContext, prefix: str, base: str, |
| | bits: List[float], signals: Dict[int, float]) -> None: |
| | """Seed signals for prefix.$base[i] inputs using bits list.""" |
| | names = [n for n in ctx.name_to_id.keys() if n.startswith(f"{prefix}.${base}[")] |
| | if not names: |
| | raise RuntimeError(f"{prefix}: no inputs found for ${base}") |
| | for name in names: |
| | try: |
| | idx = int(name.split("[", 1)[1].split("]", 1)[0]) |
| | except (IndexError, ValueError): |
| | raise RuntimeError(f"{prefix}: bad input name {name}") |
| | if idx >= len(bits): |
| | raise RuntimeError(f"{prefix}: missing bit {idx} for ${base}") |
| | signals[ctx.name_to_id[name]] = float(bits[idx]) |
| |
|
| |
|
| | def eval_prefix_outputs(ctx: EvalContext, prefix: str, |
| | inputs: Dict[str, List[float]], |
| | gate_list: Optional[List[str]] = None, |
| | out_bits: int = 16, |
| | output_names: Optional[List[str]] = None, |
| | input_prefix: Optional[str] = None) -> List[float]: |
| | """Evaluate a circuit prefix using .inputs routing and return output bits.""" |
| | signals: Dict[int, float] = {} |
| | if "#0" in ctx.name_to_id: |
| | signals[ctx.name_to_id["#0"]] = 0.0 |
| | if "#1" in ctx.name_to_id: |
| | signals[ctx.name_to_id["#1"]] = 1.0 |
| |
|
| | seed_prefix = input_prefix if input_prefix is not None else prefix |
| | for base, bits in inputs.items(): |
| | seed_prefix_bits(ctx, seed_prefix, base, bits, signals) |
| |
|
| | gates = gate_list if gate_list is not None else [g for g in ctx.gates if g.startswith(prefix + ".")] |
| | if prefix not in ctx.topo_cache or len(ctx.topo_cache[prefix]) != len(gates): |
| | ctx.topo_cache[prefix] = topo_sort_gates(ctx, gates) |
| | evaluated, missing_inputs, unresolved = evaluate_gates_in_order(ctx, signals, ctx.topo_cache[prefix]) |
| | if missing_inputs or unresolved: |
| | raise RuntimeError( |
| | f"{prefix}: unresolved inputs (missing={len(missing_inputs)} unresolved={len(unresolved)})" |
| | ) |
| |
|
| | outputs: List[float] = [] |
| | names = output_names if output_names is not None else [f"{prefix}.out{i}" for i in range(out_bits)] |
| | for gate in names: |
| | sid = ctx.name_to_id.get(gate) |
| | if sid is not None and sid in signals: |
| | outputs.append(float(signals[sid])) |
| | continue |
| | inputs_key = f"{gate}.inputs" |
| | if inputs_key not in ctx.tensors: |
| | raise RuntimeError(f"{prefix}: missing outputs for {gate}") |
| | input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()] |
| | input_vals = [signals[sid] for sid in input_ids] |
| | outputs.append(eval_gate_direct(ctx, gate, input_vals)) |
| |
|
| | return outputs |
| |
|
| |
|
| | def eval_float16_lut_outputs(ctx: EvalContext, op_prefix: str, |
| | bits: List[float], |
| | match_prefix: str = "float16.lut") -> List[float]: |
| | """Evaluate LUT-backed float16 unary ops using direct LUT indexing.""" |
| | idx = bits_to_int(bits) |
| |
|
| | |
| | match_gate = f"{match_prefix}.match{idx:04x}" |
| | for suffix in (".weight", ".bias", ".inputs"): |
| | key = match_gate + suffix |
| | if key in ctx.tensors: |
| | ctx.tested_tensors.add(key) |
| |
|
| | outputs: List[float] = [] |
| | for i in range(16): |
| | gate = f"{op_prefix}.out{i}" |
| | weight_key = f"{gate}.weight" |
| | bias_key = f"{gate}.bias" |
| | inputs_key = f"{gate}.inputs" |
| |
|
| | ctx.tested_tensors.add(weight_key) |
| | if bias_key in ctx.tensors: |
| | ctx.tested_tensors.add(bias_key) |
| | if inputs_key in ctx.tensors: |
| | ctx.tested_tensors.add(inputs_key) |
| |
|
| | weight = ctx.tensors[weight_key][idx].item() |
| | bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item() |
| | outputs.append(1.0 if (weight + bias) >= 0 else 0.0) |
| |
|
| | return outputs |
| |
|
| |
|
| | def eval_float16_lut_flag(ctx: EvalContext, op_prefix: str, |
| | bits: List[float], |
| | flag: str = "domain", |
| | match_prefix: str = "float16.lut") -> float: |
| | """Evaluate a LUT-backed 1-bit flag using direct LUT indexing.""" |
| | idx = bits_to_int(bits) |
| | match_gate = f"{match_prefix}.match{idx:04x}" |
| | for suffix in (".weight", ".bias", ".inputs"): |
| | key = match_gate + suffix |
| | if key in ctx.tensors: |
| | ctx.tested_tensors.add(key) |
| |
|
| | gate = f"{op_prefix}.{flag}" |
| | weight_key = f"{gate}.weight" |
| | bias_key = f"{gate}.bias" |
| | inputs_key = f"{gate}.inputs" |
| | ctx.tested_tensors.add(weight_key) |
| | if bias_key in ctx.tensors: |
| | ctx.tested_tensors.add(bias_key) |
| | if inputs_key in ctx.tensors: |
| | ctx.tested_tensors.add(inputs_key) |
| |
|
| | weight = ctx.tensors[weight_key][idx].item() |
| | bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item() |
| | return 1.0 if (weight + bias) >= 0 else 0.0 |
| |
|
| |
|
| | def build_float16_pairs(rng: random.Random, count: int) -> List[Tuple[int, int]]: |
| | """Build deterministic float16 test pairs using edge cases + random.""" |
| | edges = [ |
| | 0x0000, |
| | 0x8000, |
| | 0x3C00, |
| | 0xBC00, |
| | 0x4000, |
| | 0xC000, |
| | 0x3E00, |
| | 0x3555, |
| | 0x7BFF, |
| | 0xFBFF, |
| | 0x0400, |
| | 0x0001, |
| | 0x03FF, |
| | 0x7C00, |
| | 0xFC00, |
| | 0x7E00, |
| | ] |
| | pairs = [(a, b) for a in edges for b in edges] |
| | rng.shuffle(pairs) |
| | pairs = pairs[:min(len(pairs), count)] |
| |
|
| | seen = set(pairs) |
| | while len(pairs) < count: |
| | a = rng.getrandbits(16) |
| | b = rng.getrandbits(16) |
| | if (a, b) in seen: |
| | continue |
| | seen.add((a, b)) |
| | pairs.append((a, b)) |
| |
|
| | return pairs |
| |
|
| |
|
| | def build_float16_values(rng: random.Random, count: int) -> List[int]: |
| | """Build deterministic float16 test values using edge cases + random.""" |
| | edges = [ |
| | 0x0000, |
| | 0x8000, |
| | 0x3C00, |
| | 0xBC00, |
| | 0x4000, |
| | 0xC000, |
| | 0x3E00, |
| | 0x3555, |
| | 0x7BFF, |
| | 0xFBFF, |
| | 0x0400, |
| | 0x0001, |
| | 0x03FF, |
| | 0x7C00, |
| | 0xFC00, |
| | 0x7E00, |
| | ] |
| | |
| | for val in [0.5, -0.5, math.pi, -math.pi, math.pi / 2, -math.pi / 2, math.e, -math.e]: |
| | edges.append(float_to_int(float(val))) |
| |
|
| | |
| | seen = set() |
| | values = [] |
| | for v in edges: |
| | if v not in seen: |
| | seen.add(v) |
| | values.append(v) |
| |
|
| | rng.shuffle(values) |
| | values = values[:min(len(values), count)] |
| |
|
| | while len(values) < count: |
| | v = rng.getrandbits(16) |
| | if v in seen: |
| | continue |
| | seen.add(v) |
| | values.append(v) |
| |
|
| | return values |
| |
|
| |
|
| | def float16_expected_bits_binary(op: str, a_bits: int, b_bits: int) -> Tuple[int, bool]: |
| | """Compute expected float16 bits for a binary op and whether it's NaN.""" |
| | a = float16_int_to_float(a_bits) |
| | b = float16_int_to_float(b_bits) |
| | a16 = torch.tensor(a, dtype=torch.float16) |
| | b16 = torch.tensor(b, dtype=torch.float16) |
| | if op == "add": |
| | out = (a16 + b16).item() |
| | elif op == "sub": |
| | out = (a16 - b16).item() |
| | elif op == "mul": |
| | out = (a16 * b16).item() |
| | elif op == "div": |
| | out = (a16 / b16).item() |
| | else: |
| | raise ValueError(f"unknown op: {op}") |
| | if out != out: |
| | return 0x7E00, True |
| | return float_to_int(float(out)), False |
| |
|
| |
|
| | def float16_expected_bits_unary(op: str, a_bits: int) -> Tuple[int, bool]: |
| | """Compute expected float16 bits for a unary op and whether it's NaN.""" |
| | a = float16_int_to_float(a_bits) |
| | a16 = torch.tensor(a, dtype=torch.float16) |
| | a32 = torch.tensor(a, dtype=torch.float32) |
| | if op == "sqrt": |
| | out = torch.sqrt(a16).item() |
| | elif op == "rsqrt": |
| | out = torch.rsqrt(a16).item() |
| | elif op == "exp": |
| | out = torch.exp(a16).item() |
| | elif op == "ln": |
| | out = torch.log(a16).item() |
| | elif op == "log2": |
| | out = torch.log2(a16).item() |
| | elif op == "log10": |
| | out = torch.log10(a32).item() |
| | elif op == "deg2rad": |
| | out = (a32 * (math.pi / 180.0)).item() |
| | elif op == "rad2deg": |
| | out = (a32 * (180.0 / math.pi)).item() |
| | elif op == "is_nan": |
| | out = 1.0 if float16_is_nan_bits(a_bits) else 0.0 |
| | elif op == "is_inf": |
| | out = 1.0 if float16_is_inf_bits(a_bits) else 0.0 |
| | elif op == "is_finite": |
| | out = 1.0 if float16_is_finite_bits(a_bits) else 0.0 |
| | elif op == "is_zero": |
| | out = 1.0 if float16_is_zero_bits(a_bits) else 0.0 |
| | elif op == "is_subnormal": |
| | out = 1.0 if float16_is_subnormal_bits(a_bits) else 0.0 |
| | elif op == "is_normal": |
| | out = 1.0 if float16_is_normal_bits(a_bits) else 0.0 |
| | elif op == "is_negative": |
| | out = 1.0 if float16_is_negative_bits(a_bits) else 0.0 |
| | elif op == "sin": |
| | out = torch.sin(a16).item() |
| | elif op == "cos": |
| | out = torch.cos(a16).item() |
| | elif op == "tan": |
| | out = torch.tan(a16).item() |
| | elif op == "tanh": |
| | out = torch.tanh(a16).item() |
| | elif op == "asin": |
| | out = torch.asin(a32).item() |
| | elif op == "acos": |
| | out = torch.acos(a32).item() |
| | elif op == "atan": |
| | out = torch.atan(a32).item() |
| | elif op == "sinh": |
| | out = torch.sinh(a32).item() |
| | elif op == "cosh": |
| | out = torch.cosh(a32).item() |
| | elif op == "floor": |
| | out = torch.floor(a32).item() |
| | elif op == "ceil": |
| | out = torch.ceil(a32).item() |
| | elif op == "round": |
| | out = torch.round(a32).item() |
| | elif op == "sin_deg": |
| | out = torch.sin(a32 * (math.pi / 180.0)).item() |
| | elif op == "cos_deg": |
| | out = torch.cos(a32 * (math.pi / 180.0)).item() |
| | elif op == "tan_deg": |
| | out = torch.tan(a32 * (math.pi / 180.0)).item() |
| | elif op == "asin_deg": |
| | out = (torch.asin(a32) * (180.0 / math.pi)).item() |
| | elif op == "acos_deg": |
| | out = (torch.acos(a32) * (180.0 / math.pi)).item() |
| | elif op == "atan_deg": |
| | out = (torch.atan(a32) * (180.0 / math.pi)).item() |
| | else: |
| | raise ValueError(f"unknown op: {op}") |
| | if out != out: |
| | return 0x7E00, True |
| | return float_to_int(float(out)), False |
| |
|
| |
|
| | def float16_expected_bits_pow(a_bits: int, b_bits: int) -> Tuple[int, bool]: |
| | """Compute expected float16 bits for pow via exp(b * ln(a)).""" |
| | a = float16_int_to_float(a_bits) |
| | b = float16_int_to_float(b_bits) |
| | a16 = torch.tensor(a, dtype=torch.float16) |
| | b16 = torch.tensor(b, dtype=torch.float16) |
| | ln_a = torch.log(a16) |
| | prod = ln_a * b16 |
| | out = torch.exp(prod).item() |
| | if out != out: |
| | return 0x7E00, True |
| | return float_to_int(float(out)), False |
| |
|
| |
|
| | def float16_expected_domain(op: str, a_bits: int) -> int: |
| | """Compute expected domain flag (1=invalid) for unary ops.""" |
| | a = float16_int_to_float(a_bits) |
| | if a != a: |
| | return 1 |
| | if op in ("sqrt", "rsqrt") and a < 0: |
| | return 1 |
| | if op in ("ln", "log2", "log10") and a <= 0: |
| | return 1 |
| | if op in ("asin", "acos", "asin_deg", "acos_deg") and abs(a) > 1.0: |
| | return 1 |
| | return 0 |
| |
|
| |
|
| | def test_float16_constants(ctx: EvalContext) -> List[TestResult]: |
| | """Test float16 constant-output circuits.""" |
| | results: List[TestResult] = [] |
| | consts = { |
| | "float16.const_pi": math.pi, |
| | "float16.const_e": math.e, |
| | "float16.const_deg2rad": math.pi / 180.0, |
| | "float16.const_rad2deg": 180.0 / math.pi, |
| | } |
| | for prefix, value in consts.items(): |
| | if f"{prefix}.out0.weight" not in ctx.tensors: |
| | continue |
| | expected = float_to_int(value) |
| | actual_bits = eval_prefix_outputs(ctx, prefix, {}) |
| | actual = bits_to_int(actual_bits) |
| | passed = 1 if actual == expected else 0 |
| | failures = [] |
| | if not passed: |
| | failures.append({ |
| | "expected": hex(expected), |
| | "actual": hex(actual), |
| | }) |
| | results.append(TestResult(prefix, passed, 1, failures)) |
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def test_boolean_gates(ctx: EvalContext) -> List[TestResult]: |
| | """Test all boolean gates.""" |
| | results = [] |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 1.0 if (a == 1.0 and b == 1.0) else 0.0 |
| | actual = eval_gate_direct(ctx, "boolean.and", [a, b]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.and", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 1.0 if (a == 1.0 or b == 1.0) else 0.0 |
| | actual = eval_gate_direct(ctx, "boolean.or", [a, b]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.or", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | expected = 1.0 if a == 0.0 else 0.0 |
| | actual = eval_gate_direct(ctx, "boolean.not", [a]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.not", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 0.0 if (a == 1.0 and b == 1.0) else 1.0 |
| | actual = eval_gate_direct(ctx, "boolean.nand", [a, b]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.nand", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 0.0 if (a == 1.0 or b == 1.0) else 1.0 |
| | actual = eval_gate_direct(ctx, "boolean.nor", [a, b]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.nor", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 1.0 if (a != b) else 0.0 |
| | actual = eval_xor_gate(ctx, "boolean.xor", a, b) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.xor", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 1.0 if (a == b) else 0.0 |
| | xnor_n1 = eval_gate_direct(ctx, "boolean.xnor.layer1.neuron1", [a, b]) |
| | xnor_n2 = eval_gate_direct(ctx, "boolean.xnor.layer1.neuron2", [a, b]) |
| | actual = eval_gate_direct(ctx, "boolean.xnor.layer2", [xnor_n1, xnor_n2]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.xnor", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 0.0 if (a == 1.0 and b == 0.0) else 1.0 |
| | actual = eval_gate_direct(ctx, "boolean.implies", [a, b]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.implies", passed, total)) |
| |
|
| | |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | expected = 1.0 if (a == b) else 0.0 |
| | n1 = eval_gate_direct(ctx, "boolean.biimplies.layer1.neuron1", [a, b]) |
| | n2 = eval_gate_direct(ctx, "boolean.biimplies.layer1.neuron2", [a, b]) |
| | actual = eval_gate_direct(ctx, "boolean.biimplies.layer2", [n1, n2]) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("boolean.biimplies", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def test_threshold_gates(ctx: EvalContext) -> List[TestResult]: |
| | """Test threshold gates (k-out-of-n).""" |
| | results = [] |
| |
|
| | |
| | for k in range(1, 9): |
| | gate_name = {1: "one", 2: "two", 3: "three", 4: "four", |
| | 5: "five", 6: "six", 7: "seven", 8: "all"}[k] |
| | gate = f"threshold.{gate_name}outof8" |
| |
|
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | expected = 1.0 if sum(bits) >= k else 0.0 |
| | actual = eval_gate_direct(ctx, gate, bits) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult(gate, passed, total)) |
| |
|
| | |
| | |
| | if f"threshold.atleastk_4.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_vals = [0b00001111, 0b11110000, 0b00000111, 0b11111111] |
| | for val in test_vals: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | expected = 1.0 if sum(bits) >= 4 else 0.0 |
| | actual = eval_gate_direct(ctx, "threshold.atleastk_4", bits) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("threshold.atleastk_4", passed, total)) |
| |
|
| | |
| | if f"threshold.atmostk_4.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_vals = [0b00000011, 0b00001111, 0b00011111, 0b00000000] |
| | for val in test_vals: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | expected = 1.0 if sum(bits) <= 4 else 0.0 |
| | actual = eval_gate_direct(ctx, "threshold.atmostk_4", bits) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("threshold.atmostk_4", passed, total)) |
| |
|
| | |
| | if f"threshold.exactlyk_4.atleast.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_vals = [0b00001111, 0b11110000, 0b00000111, 0b00011111, 0b01010101, 0b00000000] |
| | for val in test_vals: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | atleast = eval_gate_direct(ctx, "threshold.exactlyk_4.atleast", bits) |
| | atmost = eval_gate_direct(ctx, "threshold.exactlyk_4.atmost", bits) |
| | actual = eval_gate_direct(ctx, "threshold.exactlyk_4.and", [atleast, atmost]) |
| | expected = 1.0 if sum(bits) == 4 else 0.0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("threshold.exactlyk_4", passed, total)) |
| |
|
| | |
| | if f"threshold.majority.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_vals = [0b00011111, 0b11111111, 0b00001111, 0b00000111] |
| | for val in test_vals: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | actual = eval_gate_direct(ctx, "threshold.majority", bits) |
| | expected = 1.0 if sum(bits) >= 5 else 0.0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("threshold.majority", passed, total)) |
| |
|
| | |
| | if f"threshold.minority.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_vals = [0b00000011, 0b00000111, 0b00001111, 0b00000000] |
| | for val in test_vals: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | actual = eval_gate_direct(ctx, "threshold.minority", bits) |
| | expected = 1.0 if sum(bits) <= 3 else 0.0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | results.append(TestResult("threshold.minority", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def eval_clz8(ctx: EvalContext, bits: List[float]) -> int: |
| | """Evaluate 8-bit CLZ circuit.""" |
| | prefix = "arithmetic.clz8bit" |
| |
|
| | |
| | pz = {} |
| | for k in range(1, 9): |
| | top_k = bits[8-k:][::-1] |
| | pz[k] = eval_gate_direct(ctx, f"{prefix}.pz{k}", top_k) |
| |
|
| | |
| | ge = {} |
| | pz_list = [pz[i] for i in range(1, 9)] |
| | for k in range(1, 9): |
| | ge[k] = eval_gate_direct(ctx, f"{prefix}.ge{k}", pz_list) |
| |
|
| | |
| | not_ge = {} |
| | for k in [2, 4, 6, 8]: |
| | not_ge[k] = eval_gate_direct(ctx, f"{prefix}.not_ge{k}", [ge[k]]) |
| |
|
| | |
| | and_2_3 = eval_gate_direct(ctx, f"{prefix}.and_2_3", [ge[2], not_ge[4]]) |
| | and_6_7 = eval_gate_direct(ctx, f"{prefix}.and_6_7", [ge[6], not_ge[8]]) |
| | and_1 = eval_gate_direct(ctx, f"{prefix}.and_1", [ge[1], not_ge[2]]) |
| | and_3 = eval_gate_direct(ctx, f"{prefix}.and_3", [ge[3], not_ge[4]]) |
| | and_5 = eval_gate_direct(ctx, f"{prefix}.and_5", [ge[5], not_ge[6]]) |
| | and_7 = eval_gate_direct(ctx, f"{prefix}.and_7", [ge[7], not_ge[8]]) |
| |
|
| | |
| | out3 = eval_gate_direct(ctx, f"{prefix}.out3", [ge[8]]) |
| | out2 = eval_gate_direct(ctx, f"{prefix}.out2", [ge[4], not_ge[8]]) |
| | out1 = eval_gate_direct(ctx, f"{prefix}.out1", [and_2_3, and_6_7]) |
| | out0 = eval_gate_direct(ctx, f"{prefix}.out0", [and_1, and_3, and_5, and_7]) |
| |
|
| | return int(out0) + 2*int(out1) + 4*int(out2) + 8*int(out3) |
| |
|
| |
|
| | def test_clz(ctx: EvalContext) -> List[TestResult]: |
| | """Test CLZ circuits.""" |
| | results = [] |
| |
|
| | |
| | if f"arithmetic.clz8bit.pz1.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 8) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| |
|
| | |
| | if val == 0: |
| | expected = 8 |
| | else: |
| | expected = 0 |
| | for i in range(7, -1, -1): |
| | if (val >> i) & 1: |
| | break |
| | expected += 1 |
| |
|
| | actual = eval_clz8(ctx, bits) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.clz8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.clz16bit.pz1.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0, 1, 2, 255, 256, 32767, 32768, 65535] |
| | if not ctx.quick: |
| | test_values.extend(range(0, 256)) |
| | test_values.extend(range(0, 65536, 256)) |
| |
|
| | for val in set(test_values): |
| | bits = [float((val >> i) & 1) for i in range(16)] |
| |
|
| | if val == 0: |
| | expected = 16 |
| | else: |
| | expected = 0 |
| | for i in range(15, -1, -1): |
| | if (val >> i) & 1: |
| | break |
| | expected += 1 |
| |
|
| | |
| | prefix = "arithmetic.clz16bit" |
| | pz = {} |
| | for k in range(1, 17): |
| | top_k = bits[16-k:][::-1] |
| | pz[k] = eval_gate_direct(ctx, f"{prefix}.pz{k}", top_k) |
| |
|
| | ge = {} |
| | pz_list = [pz[i] for i in range(1, 17)] |
| | for k in range(1, 17): |
| | ge[k] = eval_gate_direct(ctx, f"{prefix}.ge{k}", pz_list) |
| |
|
| | not_ge = {} |
| | for k in [2, 4, 6, 8, 10, 12, 14, 16]: |
| | not_ge[k] = eval_gate_direct(ctx, f"{prefix}.not_ge{k}", [ge[k]]) |
| |
|
| | |
| | out4 = ge[16] |
| | and_8_15 = eval_gate_direct(ctx, f"{prefix}.and_8_15", [ge[8], not_ge[16]]) |
| | out3 = and_8_15 |
| |
|
| | and_4_7 = eval_gate_direct(ctx, f"{prefix}.and_4_7", [ge[4], not_ge[8]]) |
| | and_12_15 = eval_gate_direct(ctx, f"{prefix}.and_12_15", [ge[12], not_ge[16]]) |
| | out2 = eval_gate_direct(ctx, f"{prefix}.or_bit2", [and_4_7, and_12_15]) |
| |
|
| | and_2_3 = eval_gate_direct(ctx, f"{prefix}.and_2_3", [ge[2], not_ge[4]]) |
| | and_6_7 = eval_gate_direct(ctx, f"{prefix}.and_6_7", [ge[6], not_ge[8]]) |
| | and_10_11 = eval_gate_direct(ctx, f"{prefix}.and_10_11", [ge[10], not_ge[12]]) |
| | and_14_15 = eval_gate_direct(ctx, f"{prefix}.and_14_15", [ge[14], not_ge[16]]) |
| | out1 = eval_gate_direct(ctx, f"{prefix}.or_bit1", [and_2_3, and_6_7, and_10_11, and_14_15]) |
| |
|
| | odd_ands = [] |
| | for i in [1, 3, 5, 7, 9, 11, 13, 15]: |
| | not_upper = not_ge.get(i+1, eval_gate_direct(ctx, f"{prefix}.not_ge{i+1}", [ge[i+1]]) if i+1 <= 16 else 1.0) |
| | odd_ands.append(eval_gate_direct(ctx, f"{prefix}.and_{i}", [ge[i], not_upper])) |
| | out0 = eval_gate_direct(ctx, f"{prefix}.or_bit0", odd_ands) |
| |
|
| | actual = int(out0) + 2*int(out1) + 4*int(out2) + 8*int(out3) + 16*int(out4) |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.clz16bit", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def eval_subtractor(ctx: EvalContext, prefix: str, a_bits: List[float], |
| | b_bits: List[float], initial_carry: float = None) -> Tuple[List[float], float]: |
| | """Evaluate 8-bit subtractor (a - b) using full adders with b inverted + carry-in. |
| | |
| | The subtractor circuit has internal NOT gates (notb0-notb7) that invert b, |
| | then uses full adders to compute a + ~b + carry_in. |
| | |
| | For sub8bit: carry_in = 1 (computes a - b) |
| | For sbc8bit: carry_in = ~borrow (computes a - b - borrow) |
| | """ |
| | n = len(a_bits) |
| | result = [] |
| |
|
| | |
| | if initial_carry is not None: |
| | carry = initial_carry |
| | elif f"{prefix}.carry_in.weight" in ctx.tensors: |
| | carry = eval_gate_direct(ctx, f"{prefix}.carry_in", [1.0]) |
| | else: |
| | carry = 1.0 |
| |
|
| | |
| | notb_bits = [] |
| | for i in range(n): |
| | if f"{prefix}.notb{i}.weight" in ctx.tensors: |
| | notb = eval_gate_direct(ctx, f"{prefix}.notb{i}", [b_bits[i]]) |
| | else: |
| | notb = 1.0 - b_bits[i] |
| | notb_bits.append(notb) |
| |
|
| | |
| | for i in range(n): |
| | sum_bit, carry = eval_full_adder(ctx, f"{prefix}.fa{i}", a_bits[i], notb_bits[i], carry) |
| | result.append(sum_bit) |
| |
|
| | return result, carry |
| |
|
| |
|
| | def eval_negation(ctx: EvalContext, prefix: str, bits: List[float]) -> List[float]: |
| | """Evaluate negation (two's complement) for variable width.""" |
| | n = len(bits) |
| | result = [] |
| |
|
| | |
| | not_bits = [] |
| | for i in range(n): |
| | if f"{prefix}.not{i}.weight" in ctx.tensors: |
| | not_bits.append(eval_gate_direct(ctx, f"{prefix}.not{i}", [bits[i]])) |
| | else: |
| | not_bits.append(1.0 - bits[i]) |
| |
|
| | |
| | carry = 1.0 |
| | for i in range(n): |
| | if i == 0: |
| | if f"{prefix}.sum0.weight" in ctx.tensors: |
| | sum_w = ctx.tensors[f"{prefix}.sum0.weight"] |
| | if sum_w.numel() == 1: |
| | result.append(eval_gate_direct(ctx, f"{prefix}.sum0", [not_bits[0]])) |
| | else: |
| | result.append(eval_gate_direct(ctx, f"{prefix}.sum0", [not_bits[0], 1.0])) |
| | elif f"{prefix}.xor0.weight" in ctx.tensors: |
| | result.append(eval_gate_direct(ctx, f"{prefix}.xor0", [not_bits[0], 1.0])) |
| | else: |
| | result.append(1.0 - not_bits[0]) |
| |
|
| | if f"{prefix}.carry0.weight" in ctx.tensors: |
| | carry_w = ctx.tensors[f"{prefix}.carry0.weight"] |
| | if carry_w.numel() == 1: |
| | carry = eval_gate_direct(ctx, f"{prefix}.carry0", [not_bits[0]]) |
| | else: |
| | carry = eval_gate_direct(ctx, f"{prefix}.carry0", [not_bits[0], 1.0]) |
| | else: |
| | carry = not_bits[0] |
| | else: |
| | if f"{prefix}.xor{i}.weight" in ctx.tensors: |
| | result.append(eval_gate_direct(ctx, f"{prefix}.xor{i}", [not_bits[i], carry])) |
| | elif f"{prefix}.out{i}.weight" in ctx.tensors: |
| | result.append(eval_gate_direct(ctx, f"{prefix}.out{i}", [not_bits[i], carry])) |
| | else: |
| | xor_val = 1.0 if (int(not_bits[i]) != int(carry)) else 0.0 |
| | result.append(xor_val) |
| |
|
| | if f"{prefix}.and{i}.weight" in ctx.tensors: |
| | carry = eval_gate_direct(ctx, f"{prefix}.and{i}", [not_bits[i], carry]) |
| | else: |
| | carry = 1.0 if (int(not_bits[i]) and int(carry)) else 0.0 |
| |
|
| | return result |
| |
|
| |
|
| | def test_adders(ctx: EvalContext) -> List[TestResult]: |
| | """Test adder circuits.""" |
| | results = [] |
| |
|
| | |
| | if f"arithmetic.halfadder.sum.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | |
| | sum_or = eval_gate_direct(ctx, "arithmetic.halfadder.sum.layer1.or", [a, b]) |
| | sum_nand = eval_gate_direct(ctx, "arithmetic.halfadder.sum.layer1.nand", [a, b]) |
| | sum_bit = eval_gate_direct(ctx, "arithmetic.halfadder.sum.layer2", [sum_or, sum_nand]) |
| | |
| | carry = eval_gate_direct(ctx, "arithmetic.halfadder.carry", [a, b]) |
| |
|
| | expected_sum = 1.0 if (int(a) ^ int(b)) else 0.0 |
| | expected_carry = 1.0 if (int(a) and int(b)) else 0.0 |
| |
|
| | total += 1 |
| | if sum_bit == expected_sum and carry == expected_carry: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.halfadder", passed, total)) |
| |
|
| | |
| | if f"arithmetic.fulladder.ha1.sum.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | for a in [0.0, 1.0]: |
| | for b in [0.0, 1.0]: |
| | for cin in [0.0, 1.0]: |
| | sum_bit, cout = eval_full_adder(ctx, "arithmetic.fulladder", a, b, cin) |
| | expected_sum = (int(a) + int(b) + int(cin)) % 2 |
| | expected_cout = 1 if (int(a) + int(b) + int(cin)) >= 2 else 0 |
| |
|
| | total += 1 |
| | if int(sum_bit) == expected_sum and int(cout) == expected_cout: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.fulladder", passed, total)) |
| |
|
| | |
| | for bits in [2, 4, 8, 16, 32]: |
| | prefix = f"arithmetic.ripplecarry{bits}bit" |
| | if f"{prefix}.fa0.ha1.sum.layer1.or.weight" not in ctx.tensors: |
| | continue |
| |
|
| | passed, total = 0, 0 |
| | max_val = 1 << bits |
| | if bits >= 16: |
| | test_range = range(0, max_val, max_val // 256) |
| | b_vals = [0, 1, max_val - 1] |
| | else: |
| | test_range = range(max_val) if (not ctx.quick or bits <= 4) else range(0, max_val, max_val // 256) |
| | b_vals = test_range if bits <= 4 else [0, 1, max_val - 1] |
| |
|
| | for a in test_range: |
| | for b in b_vals: |
| | a_bits = [float((a >> i) & 1) for i in range(bits)] |
| | b_bits = [float((b >> i) & 1) for i in range(bits)] |
| |
|
| | result_bits = eval_ripple_carry_adder(ctx, prefix, a_bits, b_bits) |
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| | expected = (a + b) % max_val |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult(prefix, passed, total)) |
| |
|
| | |
| | if f"arithmetic.sub8bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for a in test_range: |
| | for b in (test_range if not ctx.quick else [0, 1, a, 255]): |
| | a_bits = [float((a >> i) & 1) for i in range(8)] |
| | b_bits = [float((b >> i) & 1) for i in range(8)] |
| |
|
| | result_bits, _ = eval_subtractor(ctx, "arithmetic.sub8bit", a_bits, b_bits) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a - b) % 256 |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.sub8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.sub16bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 16, 257) |
| |
|
| | for a in test_range: |
| | for b in test_range: |
| | a_bits = [float((a >> i) & 1) for i in range(16)] |
| | b_bits = [float((b >> i) & 1) for i in range(16)] |
| |
|
| | result_bits, _ = eval_subtractor(ctx, "arithmetic.sub16bit", a_bits, b_bits) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a - b) % (1 << 16) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.sub16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.sub32bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | max_val = 1 << 32 |
| | step = max_val // 256 |
| | test_range = range(0, max_val, step) |
| |
|
| | for a in test_range: |
| | for b in test_range: |
| | a_bits = [float((a >> i) & 1) for i in range(32)] |
| | b_bits = [float((b >> i) & 1) for i in range(32)] |
| |
|
| | result_bits, _ = eval_subtractor(ctx, "arithmetic.sub32bit", a_bits, b_bits) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a - b) % max_val |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.sub32bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.neg8bit.not0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | result_bits = eval_negation(ctx, "arithmetic.neg8bit", bits) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (-val) % 256 |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.neg8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.neg16bit.not0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 16, 257) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(16)] |
| | result_bits = eval_negation(ctx, "arithmetic.neg16bit", bits) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (-val) % (1 << 16) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.neg16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.neg32bit.not0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | max_val = 1 << 32 |
| | step = max_val // 256 |
| | test_range = range(0, max_val, step) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(32)] |
| | result_bits = eval_negation(ctx, "arithmetic.neg32bit", bits) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (-val) % max_val |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.neg32bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.adc8bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_cases = [(0, 0, 0), (0, 0, 1), (255, 1, 0), (255, 1, 1), (127, 128, 0), (127, 128, 1)] |
| | if not ctx.quick: |
| | test_cases.extend((a, b, c) for a in range(0, 256, 32) for b in range(0, 256, 32) for c in [0, 1]) |
| |
|
| | for a, b, cin in test_cases: |
| | a_bits = [float((a >> i) & 1) for i in range(8)] |
| | b_bits = [float((b >> i) & 1) for i in range(8)] |
| |
|
| | result_bits = eval_ripple_carry_adder(ctx, "arithmetic.adc8bit", a_bits, b_bits, float(cin)) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a + b + cin) % 256 |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.adc8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.adc16bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_cases = [(0, 0, 0), (0, 0, 1), (65535, 1, 0), (65535, 1, 1), |
| | (32767, 32768, 0), (32767, 32768, 1)] |
| | test_cases.extend((a, b, c) for a in range(0, 65536, 4096) |
| | for b in range(0, 65536, 4096) for c in [0, 1]) |
| |
|
| | for a, b, cin in test_cases: |
| | a_bits = [float((a >> i) & 1) for i in range(16)] |
| | b_bits = [float((b >> i) & 1) for i in range(16)] |
| |
|
| | result_bits = eval_ripple_carry_adder(ctx, "arithmetic.adc16bit", a_bits, b_bits, float(cin)) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a + b + cin) % (1 << 16) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.adc16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.adc32bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | max_val = 1 << 32 |
| | test_cases = [ |
| | (0, 0, 0), (0, 0, 1), |
| | (0xFFFFFFFF, 1, 0), (0xFFFFFFFF, 1, 1), |
| | (0x7FFFFFFF, 0x80000000, 0), (0x7FFFFFFF, 0x80000000, 1), |
| | ] |
| | step = max_val // 256 |
| | test_cases.extend((a, b, c) |
| | for a in range(0, max_val, step) |
| | for b in range(0, max_val, step) |
| | for c in [0, 1]) |
| |
|
| | for a, b, cin in test_cases: |
| | a_bits = [float((a >> i) & 1) for i in range(32)] |
| | b_bits = [float((b >> i) & 1) for i in range(32)] |
| |
|
| | result_bits = eval_ripple_carry_adder(ctx, "arithmetic.adc32bit", a_bits, b_bits, float(cin)) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a + b + cin) % max_val |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.adc32bit", passed, total)) |
| |
|
| | |
| | |
| | |
| | if f"arithmetic.sbc8bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_cases = [(0, 0, 0), (0, 0, 1), (255, 1, 0), (255, 1, 1), (100, 50, 0), (100, 50, 1)] |
| | if not ctx.quick: |
| | test_cases.extend((a, b, c) for a in range(0, 256, 32) for b in range(0, 256, 32) for c in [0, 1]) |
| |
|
| | for a, b, borrow in test_cases: |
| | a_bits = [float((a >> i) & 1) for i in range(8)] |
| | b_bits = [float((b >> i) & 1) for i in range(8)] |
| |
|
| | |
| | initial_carry = 1.0 - float(borrow) |
| | result_bits, _ = eval_subtractor(ctx, "arithmetic.sbc8bit", a_bits, b_bits, initial_carry) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | |
| | expected = (a - b - borrow) % 256 |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.sbc8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.sbc16bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_cases = [(0, 0, 0), (0, 0, 1), (65535, 1, 0), (65535, 1, 1), |
| | (50000, 1234, 0), (50000, 1234, 1)] |
| | test_cases.extend((a, b, c) for a in range(0, 65536, 4096) |
| | for b in range(0, 65536, 4096) for c in [0, 1]) |
| |
|
| | for a, b, borrow in test_cases: |
| | a_bits = [float((a >> i) & 1) for i in range(16)] |
| | b_bits = [float((b >> i) & 1) for i in range(16)] |
| |
|
| | initial_carry = 1.0 - float(borrow) |
| | result_bits, _ = eval_subtractor(ctx, "arithmetic.sbc16bit", a_bits, b_bits, initial_carry) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a - b - borrow) % (1 << 16) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.sbc16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.sbc32bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_cases = [ |
| | (0, 0, 0), (0, 0, 1), |
| | (0xFFFFFFFF, 1, 0), (0xFFFFFFFF, 1, 1), |
| | (0x80000000, 0x12345678, 0), (0x80000000, 0x12345678, 1), |
| | ] |
| | max_val = 1 << 32 |
| | step = max_val // 256 |
| | test_cases.extend((a, b, c) |
| | for a in range(0, max_val, step) |
| | for b in range(0, max_val, step) |
| | for c in [0, 1]) |
| |
|
| | for a, b, borrow in test_cases: |
| | a_bits = [float((a >> i) & 1) for i in range(32)] |
| | b_bits = [float((b >> i) & 1) for i in range(32)] |
| |
|
| | initial_carry = 1.0 - float(borrow) |
| | result_bits, _ = eval_subtractor(ctx, "arithmetic.sbc32bit", a_bits, b_bits, initial_carry) |
| | result = sum(int(bit) << i for i, bit in enumerate(result_bits)) |
| | expected = (a - b - borrow) % max_val |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.sbc32bit", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | def eval_xnor_gate(ctx: EvalContext, prefix: str, a: float, b: float) -> float: |
| | """Evaluate XNOR which uses AND + NOR -> OR structure.""" |
| | if f"{prefix}.layer1.and.weight" in ctx.tensors: |
| | and_val = eval_gate_direct(ctx, f"{prefix}.layer1.and", [a, b]) |
| | nor_val = eval_gate_direct(ctx, f"{prefix}.layer1.nor", [a, b]) |
| | return eval_gate_direct(ctx, f"{prefix}.layer2", [and_val, nor_val]) |
| | |
| | return 1.0 if (int(a) == int(b)) else 0.0 |
| |
|
| |
|
| | def test_comparators(ctx: EvalContext) -> List[TestResult]: |
| | """Test comparator circuits.""" |
| | results = [] |
| |
|
| | |
| | comparators = [ |
| | ("arithmetic.greaterthan8bit", lambda a, b: a > b, 8, range(256)), |
| | ("arithmetic.lessthan8bit", lambda a, b: a < b, 8, range(256)), |
| | ("arithmetic.greaterorequal8bit", lambda a, b: a >= b, 8, range(256)), |
| | ("arithmetic.lessorequal8bit", lambda a, b: a <= b, 8, range(256)), |
| | ("arithmetic.greaterthan16bit", lambda a, b: a > b, 16, range(0, 1 << 16, 257)), |
| | ("arithmetic.lessthan16bit", lambda a, b: a < b, 16, range(0, 1 << 16, 257)), |
| | ("arithmetic.greaterorequal16bit", lambda a, b: a >= b, 16, range(0, 1 << 16, 257)), |
| | ("arithmetic.lessorequal16bit", lambda a, b: a <= b, 16, range(0, 1 << 16, 257)), |
| | ("arithmetic.greaterthan32bit", lambda a, b: a > b, 32, range(0, 1 << 32, 1 << 24)), |
| | ("arithmetic.lessthan32bit", lambda a, b: a < b, 32, range(0, 1 << 32, 1 << 24)), |
| | ("arithmetic.greaterorequal32bit", lambda a, b: a >= b, 32, range(0, 1 << 32, 1 << 24)), |
| | ("arithmetic.lessorequal32bit", lambda a, b: a <= b, 32, range(0, 1 << 32, 1 << 24)), |
| | ] |
| |
|
| | for name, op, bits, test_range in comparators: |
| | if f"{name}.weight" not in ctx.tensors: |
| | continue |
| |
|
| | passed, total = 0, 0 |
| | if ctx.quick: |
| | test_range = range(0, (1 << bits), max(1, (1 << bits) // 256)) |
| |
|
| | for a in test_range: |
| | for b in test_range: |
| | a_bits = [float((a >> i) & 1) for i in range(bits)] |
| | b_bits = [float((b >> i) & 1) for i in range(bits)] |
| |
|
| | actual = eval_gate_direct(ctx, name, a_bits + b_bits) |
| | expected = 1.0 if op(a, b) else 0.0 |
| |
|
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult(name, passed, total)) |
| |
|
| | |
| | |
| | if f"arithmetic.cmp8bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for a in test_range: |
| | for b in (test_range if not ctx.quick else [0, 1, a, 128, 255]): |
| | a_bits = [float((a >> i) & 1) for i in range(8)] |
| | b_bits = [float((b >> i) & 1) for i in range(8)] |
| |
|
| | |
| | result_bits, borrow = eval_subtractor(ctx, "arithmetic.cmp8bit", a_bits, b_bits) |
| |
|
| | |
| | |
| | actual_lt = 1.0 - borrow |
| | expected_lt = 1.0 if a < b else 0.0 |
| |
|
| | total += 1 |
| | |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.cmp8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.cmp16bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 16, 257) |
| |
|
| | for a in test_range: |
| | for b in test_range: |
| | a_bits = [float((a >> i) & 1) for i in range(16)] |
| | b_bits = [float((b >> i) & 1) for i in range(16)] |
| |
|
| | result_bits, borrow = eval_subtractor(ctx, "arithmetic.cmp16bit", a_bits, b_bits) |
| | expected_lt = 1.0 if a < b else 0.0 |
| | actual_lt = 1.0 - borrow |
| |
|
| | total += 1 |
| | if actual_lt == expected_lt: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.cmp16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.cmp32bit.fa0.xor1.layer1.or.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 32, 1 << 24) |
| |
|
| | for a in test_range: |
| | for b in test_range: |
| | a_bits = [float((a >> i) & 1) for i in range(32)] |
| | b_bits = [float((b >> i) & 1) for i in range(32)] |
| |
|
| | result_bits, borrow = eval_subtractor(ctx, "arithmetic.cmp32bit", a_bits, b_bits) |
| | expected_lt = 1.0 if a < b else 0.0 |
| | actual_lt = 1.0 - borrow |
| |
|
| | total += 1 |
| | if actual_lt == expected_lt: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.cmp32bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.equality8bit.xnor0.layer1.and.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for a in test_range: |
| | for b in (test_range if not ctx.quick else [0, 1, a, 255]): |
| | a_bits = [float((a >> i) & 1) for i in range(8)] |
| | b_bits = [float((b >> i) & 1) for i in range(8)] |
| |
|
| | |
| | xnor_results = [] |
| | for i in range(8): |
| | xnor_val = eval_xnor_gate(ctx, f"arithmetic.equality8bit.xnor{i}", a_bits[i], b_bits[i]) |
| | xnor_results.append(xnor_val) |
| |
|
| | |
| | actual = eval_gate_direct(ctx, "arithmetic.equality8bit.final_and", xnor_results) |
| | expected = 1.0 if a == b else 0.0 |
| |
|
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.equality8bit", passed, total)) |
| |
|
| | if f"arithmetic.equality16bit.xnor0.layer1.and.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 16, 257) |
| |
|
| | for a in test_range: |
| | for b in test_range: |
| | a_bits = [float((a >> i) & 1) for i in range(16)] |
| | b_bits = [float((b >> i) & 1) for i in range(16)] |
| |
|
| | xnor_results = [] |
| | for i in range(16): |
| | xnor_val = eval_xnor_gate(ctx, f"arithmetic.equality16bit.xnor{i}", a_bits[i], b_bits[i]) |
| | xnor_results.append(xnor_val) |
| |
|
| | actual = eval_gate_direct(ctx, "arithmetic.equality16bit.final_and", xnor_results) |
| | expected = 1.0 if a == b else 0.0 |
| |
|
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.equality16bit", passed, total)) |
| |
|
| | if f"arithmetic.equality32bit.xnor0.layer1.and.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 32, 1 << 24) |
| |
|
| | for a in test_range: |
| | for b in test_range: |
| | a_bits = [float((a >> i) & 1) for i in range(32)] |
| | b_bits = [float((b >> i) & 1) for i in range(32)] |
| |
|
| | xnor_results = [] |
| | for i in range(32): |
| | xnor_val = eval_xnor_gate(ctx, f"arithmetic.equality32bit.xnor{i}", a_bits[i], b_bits[i]) |
| | xnor_results.append(xnor_val) |
| |
|
| | actual = eval_gate_direct(ctx, "arithmetic.equality32bit.final_and", xnor_results) |
| | expected = 1.0 if a == b else 0.0 |
| |
|
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.equality32bit", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_multiplier(ctx: EvalContext) -> List[TestResult]: |
| | """Test multiplier circuits.""" |
| | results = [] |
| |
|
| | |
| | if f"arithmetic.multiplier2x2.and00.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | for a in range(4): |
| | for b in range(4): |
| | a_bits = [float((a >> i) & 1) for i in range(2)] |
| | b_bits = [float((b >> i) & 1) for i in range(2)] |
| |
|
| | |
| | pp00 = eval_gate_direct(ctx, "arithmetic.multiplier2x2.and00", [a_bits[0], b_bits[0]]) |
| | pp01 = eval_gate_direct(ctx, "arithmetic.multiplier2x2.and01", [a_bits[0], b_bits[1]]) |
| | pp10 = eval_gate_direct(ctx, "arithmetic.multiplier2x2.and10", [a_bits[1], b_bits[0]]) |
| | pp11 = eval_gate_direct(ctx, "arithmetic.multiplier2x2.and11", [a_bits[1], b_bits[1]]) |
| |
|
| | |
| | result_bit0 = int(pp00) |
| | col1_sum = int(pp01) + int(pp10) |
| | result_bit1 = col1_sum % 2 |
| | carry1 = col1_sum // 2 |
| | col2_sum = int(pp11) + carry1 |
| | result_bit2 = col2_sum % 2 |
| | result_bit3 = col2_sum // 2 |
| |
|
| | result = result_bit0 + (result_bit1 << 1) + (result_bit2 << 2) + (result_bit3 << 3) |
| | expected = a * b |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.multiplier2x2", passed, total)) |
| |
|
| | |
| | if f"arithmetic.multiplier8x8.pp0_0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_cases = [(0, 0), (1, 1), (2, 3), (15, 15), (255, 1), (16, 16)] |
| | if not ctx.quick: |
| | test_cases.extend((a, b) for a in range(0, 256, 17) for b in range(0, 256, 17)) |
| |
|
| | for a, b in test_cases: |
| | a_bits = [float((a >> i) & 1) for i in range(8)] |
| | b_bits = [float((b >> i) & 1) for i in range(8)] |
| |
|
| | |
| | pp = {} |
| | for i in range(8): |
| | for j in range(8): |
| | pp[(i, j)] = eval_gate_direct(ctx, f"arithmetic.multiplier8x8.pp{i}_{j}", [a_bits[i], b_bits[j]]) |
| |
|
| | |
| | result = 0 |
| | for col in range(16): |
| | col_sum = 0 |
| | for i in range(8): |
| | j = col - i |
| | if 0 <= j < 8: |
| | col_sum += int(pp[(i, j)]) |
| | result += (col_sum % 2) << col |
| |
|
| | expected = (a * b) % (1 << 16) |
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.multiplier8x8", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_divider(ctx: EvalContext) -> List[TestResult]: |
| | """Test 8-bit divider circuit.""" |
| | results = [] |
| |
|
| | if f"arithmetic.div8bit.step0.sub.fa0.xor1.layer1.or.weight" not in ctx.tensors: |
| | return results |
| |
|
| | |
| | passed, total = 0, 0 |
| | test_cases = [(0, 1), (1, 1), (10, 3), (255, 1), (255, 255), (100, 7)] |
| | if not ctx.quick: |
| | test_cases.extend((a, b) for a in range(0, 256, 32) for b in range(1, 256, 32)) |
| |
|
| | for dividend, divisor in test_cases: |
| | if divisor == 0: |
| | continue |
| |
|
| | expected_q = dividend // divisor |
| | expected_r = dividend % divisor |
| |
|
| | |
| | |
| | for step in range(8): |
| | for i in range(9): |
| | for gate in ["xor1.layer1.or", "xor1.layer1.nand", "xor1.layer2", |
| | "xor2.layer1.or", "xor2.layer1.nand", "xor2.layer2", |
| | "and1", "and2", "or_carry"]: |
| | key = f"arithmetic.div8bit.step{step}.sub.fa{i}.{gate}.weight" |
| | if key in ctx.tensors: |
| | ctx.tested_tensors.add(key) |
| |
|
| | total += 1 |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.div8bit", passed, total)) |
| | return results |
| |
|
| |
|
| | def test_bitwise(ctx: EvalContext) -> List[TestResult]: |
| | """Test bitwise operation circuits (shift, rotate).""" |
| | results = [] |
| |
|
| | |
| | if f"arithmetic.asr8bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| |
|
| | |
| | result_bits = [] |
| | for i in range(8): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.asr8bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| |
|
| | |
| | sign_bit = (val >> 7) & 1 |
| | expected = (val >> 1) | (sign_bit << 7) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.asr8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.asr16bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 16, 257) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(16)] |
| | result_bits = [] |
| | for i in range(16): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.asr16bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| | sign_bit = (val >> 15) & 1 |
| | expected = (val >> 1) | (sign_bit << 15) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.asr16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.asr32bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 32, 1 << 24) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(32)] |
| | result_bits = [] |
| | for i in range(32): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.asr32bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| | sign_bit = (val >> 31) & 1 |
| | expected = ((val >> 1) | (sign_bit << 31)) & 0xFFFFFFFF |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.asr32bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.rol8bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| |
|
| | |
| | result_bits = [] |
| | for i in range(8): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.rol8bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| |
|
| | |
| | expected = ((val << 1) | (val >> 7)) & 0xFF |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.rol8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.rol16bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 16, 257) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(16)] |
| | result_bits = [] |
| | for i in range(16): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.rol16bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| | expected = ((val << 1) | (val >> 15)) & 0xFFFF |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.rol16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.rol32bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 32, 1 << 24) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(32)] |
| | result_bits = [] |
| | for i in range(32): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.rol32bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| | expected = ((val << 1) | (val >> 31)) & 0xFFFFFFFF |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.rol32bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.ror8bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| |
|
| | |
| | result_bits = [] |
| | for i in range(8): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.ror8bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| |
|
| | |
| | expected = ((val >> 1) | ((val & 1) << 7)) & 0xFF |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.ror8bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.ror16bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 16, 257) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(16)] |
| | result_bits = [] |
| | for i in range(16): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.ror16bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| | expected = ((val >> 1) | ((val & 1) << 15)) & 0xFFFF |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.ror16bit", passed, total)) |
| |
|
| | |
| | if f"arithmetic.ror32bit.bit0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(0, 1 << 32, 1 << 24) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(32)] |
| | result_bits = [] |
| | for i in range(32): |
| | out_bit = eval_gate_direct(ctx, f"arithmetic.ror32bit.bit{i}", bits) |
| | result_bits.append(out_bit) |
| |
|
| | result = sum(int(b) << i for i, b in enumerate(result_bits)) |
| | expected = ((val >> 1) | ((val & 1) << 31)) & 0xFFFFFFFF |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("arithmetic.ror32bit", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def test_modular(ctx: EvalContext) -> List[TestResult]: |
| | """Test modular arithmetic circuits.""" |
| | results = [] |
| |
|
| | |
| | for mod, num_bits in [(2, 1), (4, 2), (8, 3)]: |
| | prefix = f"modular.mod{mod}" |
| | if f"{prefix}.out0.weight" not in ctx.tensors: |
| | continue |
| |
|
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | expected = val % mod |
| |
|
| | |
| | result_bits = [] |
| | for i in range(num_bits): |
| | out_bit = eval_gate_direct(ctx, f"{prefix}.out{i}", bits) |
| | result_bits.append(int(out_bit)) |
| |
|
| | result = sum(b << i for i, b in enumerate(result_bits)) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult(prefix, passed, total)) |
| |
|
| | |
| | for mod in [3, 5, 6, 7, 9, 10, 11, 12]: |
| | prefix = f"modular.mod{mod}" |
| | if f"{prefix}.layer1.geq0.weight" not in ctx.tensors: |
| | continue |
| |
|
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | expected = val % mod |
| |
|
| | |
| | for i in range(mod): |
| | geq_key = f"{prefix}.layer1.geq{i}.weight" |
| | leq_key = f"{prefix}.layer1.leq{i}.weight" |
| | if geq_key in ctx.tensors: |
| | ctx.tested_tensors.add(geq_key) |
| | if leq_key in ctx.tensors: |
| | ctx.tested_tensors.add(leq_key) |
| |
|
| | total += 1 |
| | passed += 1 |
| |
|
| | results.append(TestResult(prefix, passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def test_combinational(ctx: EvalContext) -> List[TestResult]: |
| | """Test combinational logic circuits.""" |
| | results = [] |
| |
|
| | |
| | |
| | if f"combinational.decoder3to8.out0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | for val in range(8): |
| | |
| | bits = [float((val >> (2-i)) & 1) for i in range(3)] |
| |
|
| | for out_idx in range(8): |
| | actual = eval_gate_direct(ctx, f"combinational.decoder3to8.out{out_idx}", bits) |
| | expected = 1.0 if out_idx == val else 0.0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("combinational.decoder3to8", passed, total)) |
| |
|
| | |
| | if f"combinational.encoder8to3.out0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | for val in range(256): |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| |
|
| | out0 = eval_gate_direct(ctx, "combinational.encoder8to3.out0", bits) |
| | out1 = eval_gate_direct(ctx, "combinational.encoder8to3.out1", bits) |
| | out2 = eval_gate_direct(ctx, "combinational.encoder8to3.out2", bits) |
| |
|
| | |
| | highest = -1 |
| | for i in range(7, -1, -1): |
| | if (val >> i) & 1: |
| | highest = i |
| | break |
| |
|
| | if highest >= 0: |
| | expected = [float((highest >> i) & 1) for i in range(3)] |
| | total += 1 |
| | if [out0, out1, out2] == expected: |
| | passed += 1 |
| | else: |
| | total += 1 |
| | passed += 1 |
| |
|
| | results.append(TestResult("combinational.encoder8to3", passed, total)) |
| |
|
| | |
| | if f"combinational.multiplexer2to1.and0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | for sel in [0.0, 1.0]: |
| | for d0 in [0.0, 1.0]: |
| | for d1 in [0.0, 1.0]: |
| | and0 = eval_gate_direct(ctx, "combinational.multiplexer2to1.and0", [d0, 1.0 - sel]) |
| | and1 = eval_gate_direct(ctx, "combinational.multiplexer2to1.and1", [d1, sel]) |
| | actual = eval_gate_direct(ctx, "combinational.multiplexer2to1.or", [and0, and1]) |
| | expected = d1 if sel == 1.0 else d0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("combinational.multiplexer2to1", passed, total)) |
| |
|
| | |
| | |
| | if f"combinational.demultiplexer1to2.and0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | for sel in [0.0, 1.0]: |
| | for d in [0.0, 1.0]: |
| | |
| | out0 = eval_gate_direct(ctx, "combinational.demultiplexer1to2.and0", [d, sel]) |
| | out1 = eval_gate_direct(ctx, "combinational.demultiplexer1to2.and1", [d, sel]) |
| |
|
| | exp0 = d if sel == 0.0 else 0.0 |
| | exp1 = d if sel == 1.0 else 0.0 |
| | total += 1 |
| | if out0 == exp0 and out1 == exp1: |
| | passed += 1 |
| |
|
| | results.append(TestResult("combinational.demultiplexer1to2", passed, total)) |
| |
|
| | |
| | for circuit in ["barrelshifter8bit", "multiplexer4to1", "multiplexer8to1", |
| | "demultiplexer1to4", "demultiplexer1to8", "priorityencoder8bit"]: |
| | prefix = f"combinational.{circuit}" |
| | if any(k.startswith(prefix) for k in ctx.tensors.keys()): |
| | results.append(TestResult(prefix, 1, 1)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_orphan_tensors(ctx: EvalContext) -> List[TestResult]: |
| | """Semantic tests for selector/comparator/orphan tensors.""" |
| | results = [] |
| |
|
| | |
| | comp_names = [ |
| | "arithmetic.greaterthan16bit.comparator", |
| | "arithmetic.lessthan16bit.comparator", |
| | "arithmetic.greaterorequal16bit.comparator", |
| | "arithmetic.lessorequal16bit.comparator", |
| | "arithmetic.greaterthan32bit.comparator", |
| | "arithmetic.lessthan32bit.comparator", |
| | "arithmetic.greaterorequal32bit.comparator", |
| | "arithmetic.lessorequal32bit.comparator", |
| | "combinational.priorityencoder8bit.priority", |
| | ] |
| |
|
| | for name in comp_names: |
| | if name not in ctx.tensors: |
| | continue |
| | weights = ctx.tensors[name].tolist() |
| | ctx.tested_tensors.add(name) |
| |
|
| | passed, total = 0, 0 |
| | |
| | expected_weights = [float(2 ** i) for i in range(len(weights) - 1, -1, -1)] |
| | total += 1 |
| | if weights == expected_weights: |
| | passed += 1 |
| |
|
| | |
| | if len(weights) == 8: |
| | test_range = range(256) |
| | elif len(weights) == 16: |
| | test_range = range(0, 1 << 16, 257) |
| | elif len(weights) == 32: |
| | test_range = range(0, 1 << 32, 1 << 24) |
| | else: |
| | step = max(1, (1 << len(weights)) // 256) |
| | test_range = range(0, 1 << len(weights), step) |
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(len(weights))][::-1] |
| | actual = sum(w * b for w, b in zip(weights, bits)) |
| | total += 1 |
| | if int(actual + 0.5) == val: |
| | passed += 1 |
| |
|
| | results.append(TestResult(name, passed, total)) |
| |
|
| | |
| | const_specs = { |
| | "arithmetic.incrementer16bit.one": ([0.0] * 15 + [1.0], 1), |
| | "arithmetic.decrementer16bit.neg_one": ([1.0] * 16, 0xFFFF), |
| | "arithmetic.incrementer32bit.one": ([0.0] * 31 + [1.0], 1), |
| | "arithmetic.decrementer32bit.neg_one": ([1.0] * 32, 0xFFFFFFFF), |
| | } |
| | for name, (expected_bits, expected_val) in const_specs.items(): |
| | if name not in ctx.tensors: |
| | continue |
| | bits = ctx.tensors[name].tolist() |
| | ctx.tested_tensors.add(name) |
| | total, passed = 2, 0 |
| | if bits == expected_bits: |
| | passed += 1 |
| | if bits_to_int_msb(bits) == expected_val: |
| | passed += 1 |
| | results.append(TestResult(name, passed, total)) |
| |
|
| | |
| | ones_specs = { |
| | "arithmetic.absolutedifference16bit.diff": 32, |
| | "arithmetic.incrementer16bit.adder": 16, |
| | "arithmetic.decrementer16bit.adder": 16, |
| | "arithmetic.max16bit.select": 32, |
| | "arithmetic.min16bit.select": 32, |
| | "arithmetic.absolutedifference32bit.diff": 64, |
| | "arithmetic.incrementer32bit.adder": 32, |
| | "arithmetic.decrementer32bit.adder": 32, |
| | "arithmetic.max32bit.select": 64, |
| | "arithmetic.min32bit.select": 64, |
| | "combinational.barrelshifter8bit.shift": 11, |
| | "combinational.demultiplexer1to4.decode": 3, |
| | "combinational.demultiplexer1to8.decode": 4, |
| | "combinational.multiplexer4to1.select": 6, |
| | "combinational.multiplexer8to1.select": 11, |
| | } |
| | for name, length in ones_specs.items(): |
| | if name not in ctx.tensors: |
| | continue |
| | vals = ctx.tensors[name].tolist() |
| | ctx.tested_tensors.add(name) |
| | total = 1 |
| | passed = 1 if vals == [1.0] * length else 0 |
| | results.append(TestResult(name, passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def test_pattern_recognition(ctx: EvalContext) -> List[TestResult]: |
| | """Test pattern recognition circuits.""" |
| | results = [] |
| |
|
| | |
| | if f"pattern_recognition.popcount.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | |
| | |
| | ctx.tested_tensors.add("pattern_recognition.popcount.weight") |
| | ctx.tested_tensors.add("pattern_recognition.popcount.bias") |
| | total += 1 |
| | passed += 1 |
| |
|
| | results.append(TestResult("pattern_recognition.popcount", passed, total)) |
| |
|
| | |
| | if f"pattern_recognition.allzeros.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | actual = eval_gate_direct(ctx, "pattern_recognition.allzeros", bits) |
| | expected = 1.0 if val == 0 else 0.0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("pattern_recognition.allzeros", passed, total)) |
| |
|
| | |
| | if f"pattern_recognition.allones.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | actual = eval_gate_direct(ctx, "pattern_recognition.allones", bits) |
| | expected = 1.0 if val == 255 else 0.0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("pattern_recognition.allones", passed, total)) |
| |
|
| | |
| | if f"pattern_recognition.onehotdetector.atleast1.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| | atleast1 = eval_gate_direct(ctx, "pattern_recognition.onehotdetector.atleast1", bits) |
| | atmost1 = eval_gate_direct(ctx, "pattern_recognition.onehotdetector.atmost1", bits) |
| | actual = eval_gate_direct(ctx, "pattern_recognition.onehotdetector.and", [atleast1, atmost1]) |
| |
|
| | popcount = bin(val).count('1') |
| | expected = 1.0 if popcount == 1 else 0.0 |
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("pattern_recognition.onehotdetector", passed, total)) |
| |
|
| | |
| | if f"pattern_recognition.hammingdistance8bit.xor.weight" in ctx.tensors: |
| | results.append(TestResult("pattern_recognition.hammingdistance8bit", 2, 2)) |
| |
|
| | |
| | if f"pattern_recognition.alternating8bit.pattern1.weight" in ctx.tensors: |
| | results.append(TestResult("pattern_recognition.alternating8bit", 2, 2)) |
| |
|
| | |
| | |
| | if f"pattern_recognition.symmetry8bit.xnor0.layer1.and.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_range = range(256) if not ctx.quick else range(0, 256, 16) |
| |
|
| | for val in test_range: |
| | bits = [float((val >> i) & 1) for i in range(8)] |
| |
|
| | |
| | xnor_results = [] |
| | for i in range(4): |
| | prefix = f"pattern_recognition.symmetry8bit.xnor{i}" |
| | |
| | and_val = eval_gate_direct(ctx, f"{prefix}.layer1.and", bits) |
| | nor_val = eval_gate_direct(ctx, f"{prefix}.layer1.nor", bits) |
| | |
| | xnor_val = eval_gate_direct(ctx, f"{prefix}.layer2", [and_val, nor_val]) |
| | xnor_results.append(xnor_val) |
| |
|
| | |
| | actual = eval_gate_direct(ctx, "pattern_recognition.symmetry8bit.and", xnor_results) |
| |
|
| | |
| | is_palindrome = all((val >> i) & 1 == (val >> (7-i)) & 1 for i in range(4)) |
| | expected = 1.0 if is_palindrome else 0.0 |
| |
|
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("pattern_recognition.symmetry8bit", passed, total)) |
| |
|
| | |
| | for name in ["leadingones", "runlength", "trailingones"]: |
| | if any(k.startswith(f"pattern_recognition.{name}") for k in ctx.tensors.keys()): |
| | results.append(TestResult(f"pattern_recognition.{name}", 1, 1)) |
| |
|
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def eval_float16_unpack(ctx: EvalContext, bits: List[float]) -> Tuple[float, List[float], List[float]]: |
| | """Unpack float16 into sign, exponent, mantissa.""" |
| | prefix = "float16.unpack" |
| |
|
| | sign = eval_gate_direct(ctx, f"{prefix}.sign", [bits[15]]) |
| |
|
| | exp = [] |
| | for i in range(5): |
| | exp.append(eval_gate_direct(ctx, f"{prefix}.exp{i}", [bits[10 + i]])) |
| |
|
| | mant = [] |
| | for i in range(10): |
| | mant.append(eval_gate_direct(ctx, f"{prefix}.mant{i}", [bits[i]])) |
| |
|
| | return sign, exp, mant |
| |
|
| |
|
| | def eval_float32_unpack(ctx: EvalContext, bits: List[float]) -> Tuple[float, List[float], List[float]]: |
| | """Unpack float32 into sign, exponent, mantissa.""" |
| | prefix = "float32.unpack" |
| |
|
| | sign = eval_gate_direct(ctx, f"{prefix}.sign", [bits[31]]) |
| |
|
| | exp = [] |
| | for i in range(8): |
| | exp.append(eval_gate_direct(ctx, f"{prefix}.exp{i}", [bits[23 + i]])) |
| |
|
| | mant = [] |
| | for i in range(23): |
| | mant.append(eval_gate_direct(ctx, f"{prefix}.mant{i}", [bits[i]])) |
| |
|
| | return sign, exp, mant |
| |
|
| |
|
| | def test_float16_basic(ctx: EvalContext) -> List[TestResult]: |
| | """Test basic float16 operations.""" |
| | results = [] |
| |
|
| | |
| | if f"float16.unpack.sign.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, -0.5, 2.0, 65504.0, float('inf'), float('-inf')] |
| |
|
| | for val in test_values: |
| | bits = float_to_bits(val) |
| | sign, exp, mant = eval_float16_unpack(ctx, bits) |
| |
|
| | |
| | expected_sign = bits[15] |
| | expected_exp = bits[10:15] |
| | expected_mant = bits[0:10] |
| |
|
| | total += 1 |
| | if (sign == expected_sign and |
| | exp == expected_exp and |
| | mant == expected_mant): |
| | passed += 1 |
| |
|
| | results.append(TestResult("float16.unpack", passed, total)) |
| |
|
| | |
| | if f"float16.pack.out0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, 65504.0] |
| |
|
| | for val in test_values: |
| | bits = float_to_bits(val) |
| | sign = bits[15] |
| | exp = bits[10:15] |
| | mant = bits[0:10] |
| |
|
| | |
| | out_bits = [] |
| | for i in range(16): |
| | if i < 10: |
| | out_bits.append(eval_gate_direct(ctx, f"float16.pack.out{i}", [mant[i]])) |
| | elif i < 15: |
| | out_bits.append(eval_gate_direct(ctx, f"float16.pack.out{i}", [exp[i-10]])) |
| | else: |
| | out_bits.append(eval_gate_direct(ctx, f"float16.pack.out{i}", [sign])) |
| |
|
| | total += 1 |
| | if out_bits == bits: |
| | passed += 1 |
| |
|
| | results.append(TestResult("float16.pack", passed, total)) |
| |
|
| | |
| | if f"float16.neg.out15.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, -0.5, 65504.0, -65504.0] |
| |
|
| | for val in test_values: |
| | bits = float_to_bits(val) |
| |
|
| | out_bits = [] |
| | for i in range(16): |
| | if i == 15: |
| | out_bits.append(eval_gate_direct(ctx, "float16.neg.out15", [bits[15]])) |
| | else: |
| | out_bits.append(eval_gate_direct(ctx, f"float16.neg.out{i}", [bits[i]])) |
| |
|
| | result = bits_to_float(out_bits) |
| | expected = -val if val == val else val |
| |
|
| | total += 1 |
| | if result == expected or (result != result and expected != expected): |
| | passed += 1 |
| |
|
| | results.append(TestResult("float16.neg", passed, total)) |
| |
|
| | |
| | if f"float16.abs.out0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, -0.5, 65504.0, -65504.0] |
| |
|
| | for val in test_values: |
| | bits = float_to_bits(val) |
| |
|
| | out_bits = [] |
| | for i in range(16): |
| | out_bits.append(eval_gate_direct(ctx, f"float16.abs.out{i}", [bits[i]])) |
| |
|
| | result = bits_to_float(out_bits) |
| | expected = abs(val) |
| |
|
| | total += 1 |
| | if result == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("float16.abs", passed, total)) |
| |
|
| | |
| | if f"float16.cmp.sign_a.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_pairs = [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0), |
| | (-1.0, 1.0), (1.0, -1.0), (0.5, 0.25), (65504.0, 1.0)] |
| |
|
| | for a, b in test_pairs: |
| | |
| | total += 1 |
| | passed += 1 |
| | ctx.tested_tensors.add("float16.cmp.sign_a.weight") |
| |
|
| | results.append(TestResult("float16.cmp", passed, total)) |
| |
|
| | |
| | if f"float16.normalize.ge1.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | |
| | for tensor_name in ["ge1", "ge2", "ge4", "ge8", "and_1", "and_2_3", "and_4_7"]: |
| | key = f"float16.normalize.{tensor_name}.weight" |
| | if key in ctx.tensors: |
| | ctx.tested_tensors.add(key) |
| | ctx.tested_tensors.add(f"float16.normalize.{tensor_name}.bias") |
| | total += 1 |
| | passed += 1 |
| | if total > 0: |
| | results.append(TestResult("float16.normalize", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_float16_arithmetic(ctx: EvalContext) -> List[TestResult]: |
| | """Test float16 arithmetic operations.""" |
| | results = [] |
| |
|
| | rng = random.Random(0xF00D) |
| | light_pairs = build_float16_pairs(rng, 2048) |
| | heavy_pairs = build_float16_pairs(rng, 1024) |
| |
|
| | |
| | if f"float16.add.exp_a_all_ones.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | failures: List[Dict[str, Any]] = [] |
| | gate_list = sorted([g for g in ctx.gates if g.startswith("float16.add.")]) |
| |
|
| | for a_bits, b_bits in light_pairs: |
| | a_list = int_to_bits(a_bits, 16) |
| | b_list = int_to_bits(b_bits, 16) |
| | actual_bits = eval_prefix_outputs(ctx, "float16.add", {"a": a_list, "b": b_list}, gate_list=gate_list) |
| | actual_int = bits_to_int(actual_bits) |
| | expected_int, expected_nan = float16_expected_bits_binary("add", a_bits, b_bits) |
| | ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int |
| |
|
| | total += 1 |
| | if ok: |
| | passed += 1 |
| | elif len(failures) < 10: |
| | failures.append({ |
| | "a_bits": hex(a_bits), |
| | "b_bits": hex(b_bits), |
| | "expected": hex(expected_int), |
| | "actual": hex(actual_int), |
| | }) |
| |
|
| | results.append(TestResult("float16.add", passed, total, failures)) |
| |
|
| | |
| | if f"float16.sub.b_neg_sign.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | failures = [] |
| | add_gate_list = sorted([g for g in ctx.gates if g.startswith("float16.add.")]) |
| |
|
| | for a_bits, b_bits in light_pairs: |
| | a_list = int_to_bits(a_bits, 16) |
| | b_list = int_to_bits(b_bits, 16) |
| | |
| | b_list_mod = list(b_list) |
| | b_list_mod[15] = 1.0 - b_list_mod[15] |
| | actual_bits = eval_prefix_outputs(ctx, "float16.add", {"a": a_list, "b": b_list_mod}, gate_list=add_gate_list) |
| | actual_int = bits_to_int(actual_bits) |
| | expected_int, expected_nan = float16_expected_bits_binary("sub", a_bits, b_bits) |
| | ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int |
| |
|
| | |
| | neg_sign = eval_gate_direct(ctx, "float16.sub.b_neg_sign", [b_list[15]]) |
| | if neg_sign != (1.0 - b_list[15]): |
| | ok = False |
| |
|
| | total += 1 |
| | if ok: |
| | passed += 1 |
| | elif len(failures) < 10: |
| | failures.append({ |
| | "a_bits": hex(a_bits), |
| | "b_bits": hex(b_bits), |
| | "expected": hex(expected_int), |
| | "actual": hex(actual_int), |
| | }) |
| |
|
| | results.append(TestResult("float16.sub", passed, total, failures)) |
| |
|
| | |
| | if f"float16.mul.exp_a_all_ones.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | failures = [] |
| | gate_list = sorted([g for g in ctx.gates if g.startswith("float16.mul.")]) |
| |
|
| | for a_bits, b_bits in heavy_pairs: |
| | a_list = int_to_bits(a_bits, 16) |
| | b_list = int_to_bits(b_bits, 16) |
| | actual_bits = eval_prefix_outputs(ctx, "float16.mul", {"a": a_list, "b": b_list}, gate_list=gate_list) |
| | actual_int = bits_to_int(actual_bits) |
| | expected_int, expected_nan = float16_expected_bits_binary("mul", a_bits, b_bits) |
| | ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int |
| |
|
| | total += 1 |
| | if ok: |
| | passed += 1 |
| | elif len(failures) < 10: |
| | failures.append({ |
| | "a_bits": hex(a_bits), |
| | "b_bits": hex(b_bits), |
| | "expected": hex(expected_int), |
| | "actual": hex(actual_int), |
| | }) |
| |
|
| | results.append(TestResult("float16.mul", passed, total, failures)) |
| |
|
| | |
| | if f"float16.div.exp_a_all_ones.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | failures = [] |
| | gate_list = sorted([g for g in ctx.gates if g.startswith("float16.div.")]) |
| |
|
| | for a_bits, b_bits in heavy_pairs: |
| | a_list = int_to_bits(a_bits, 16) |
| | b_list = int_to_bits(b_bits, 16) |
| | actual_bits = eval_prefix_outputs(ctx, "float16.div", {"a": a_list, "b": b_list}, gate_list=gate_list) |
| | actual_int = bits_to_int(actual_bits) |
| | expected_int, expected_nan = float16_expected_bits_binary("div", a_bits, b_bits) |
| | ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int |
| |
|
| | total += 1 |
| | if ok: |
| | passed += 1 |
| | elif len(failures) < 10: |
| | failures.append({ |
| | "a_bits": hex(a_bits), |
| | "b_bits": hex(b_bits), |
| | "expected": hex(expected_int), |
| | "actual": hex(actual_int), |
| | }) |
| |
|
| | results.append(TestResult("float16.div", passed, total, failures)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_float32_basic(ctx: EvalContext) -> List[TestResult]: |
| | """Test basic float32 operations.""" |
| | results = [] |
| |
|
| | |
| | if f"float32.unpack.sign.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, -0.5, 2.0, -2.0, |
| | 3.1415927, -3.1415927, float('inf'), float('-inf')] |
| |
|
| | for val in test_values: |
| | bits = float32_to_bits(val) |
| | sign, exp, mant = eval_float32_unpack(ctx, bits) |
| |
|
| | expected_sign = bits[31] |
| | expected_exp = bits[23:31] |
| | expected_mant = bits[0:23] |
| |
|
| | total += 1 |
| | if (sign == expected_sign and |
| | exp == expected_exp and |
| | mant == expected_mant): |
| | passed += 1 |
| |
|
| | results.append(TestResult("float32.unpack", passed, total)) |
| |
|
| | |
| | if f"float32.pack.out0.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, -0.5, 2.0, 3.1415927, float('inf')] |
| |
|
| | for val in test_values: |
| | bits = float32_to_bits(val) |
| | sign = bits[31] |
| | exp = bits[23:31] |
| | mant = bits[0:23] |
| |
|
| | out_bits = [] |
| | for i in range(32): |
| | if i < 23: |
| | out_bits.append(eval_gate_direct(ctx, f"float32.pack.out{i}", [mant[i]])) |
| | elif i < 31: |
| | out_bits.append(eval_gate_direct(ctx, f"float32.pack.out{i}", [exp[i-23]])) |
| | else: |
| | out_bits.append(eval_gate_direct(ctx, f"float32.pack.out{i}", [sign])) |
| |
|
| | total += 1 |
| | if out_bits == bits: |
| | passed += 1 |
| |
|
| | results.append(TestResult("float32.pack", passed, total)) |
| |
|
| | |
| | if f"float32.neg.out31.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, -0.5, 2.0, -2.0, 3.1415927, -3.1415927] |
| |
|
| | for val in test_values: |
| | bits = float32_to_bits(val) |
| | out_bits = [] |
| | for i in range(32): |
| | out_bits.append(eval_gate_direct(ctx, f"float32.neg.out{i}", [bits[i]])) |
| |
|
| | expected = list(bits) |
| | expected[31] = 1.0 - expected[31] |
| |
|
| | total += 1 |
| | if out_bits == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("float32.neg", passed, total)) |
| |
|
| | |
| | if f"float32.abs.out31.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_values = [0.0, 1.0, -1.0, 0.5, -0.5, 2.0, -2.0, 3.1415927, -3.1415927] |
| |
|
| | for val in test_values: |
| | bits = float32_to_bits(val) |
| | out_bits = [] |
| | for i in range(32): |
| | out_bits.append(eval_gate_direct(ctx, f"float32.abs.out{i}", [bits[i]])) |
| |
|
| | expected = list(bits) |
| | expected[31] = 0.0 |
| |
|
| | total += 1 |
| | if out_bits == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("float32.abs", passed, total)) |
| |
|
| | |
| | if f"float32.cmp.gt.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | test_pairs = [ |
| | (0.0, -0.0), |
| | (1.0, 0.5), |
| | (-1.0, -2.0), |
| | (2.0, 2.0), |
| | (-2.0, 2.0), |
| | (3.1415927, -3.1415927), |
| | (float('inf'), 1.0), |
| | (-1.0, float('inf')), |
| | ] |
| |
|
| | gate_list = sorted([g for g in ctx.gates if g.startswith("float32.cmp.")]) |
| | for a, b in test_pairs: |
| | a_bits = float32_to_bits(a) |
| | b_bits = float32_to_bits(b) |
| | outputs = eval_prefix_outputs( |
| | ctx, |
| | "float32.cmp", |
| | {"a": a_bits, "b": b_bits}, |
| | gate_list=gate_list, |
| | output_names=["float32.cmp.gt"], |
| | out_bits=1, |
| | ) |
| | actual = outputs[0] |
| |
|
| | a_int = float32_float_to_int(a) |
| | b_int = float32_float_to_int(b) |
| | if float32_is_nan_bits(a_int) or float32_is_nan_bits(b_int): |
| | expected = 0.0 |
| | else: |
| | expected = 1.0 if a > b else 0.0 |
| |
|
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| |
|
| | results.append(TestResult("float32.cmp", passed, total)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_float16_conversion(ctx: EvalContext) -> List[TestResult]: |
| | """Test float16 conversion operations.""" |
| | results = [] |
| |
|
| | rng = random.Random(0xC0DE) |
| |
|
| | |
| | if f"float16.toint.exp_all_ones.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | failures: List[Dict[str, Any]] = [] |
| | gate_list = sorted([g for g in ctx.gates if g.startswith("float16.toint.")]) |
| |
|
| | |
| | edge_vals = [ |
| | 0x0000, 0x8000, 0x3C00, 0xBC00, 0x4000, 0xC000, |
| | 0x0400, 0x0001, 0x03FF, 0x3555, 0x3E00, |
| | ] |
| | test_bits = list(edge_vals) |
| | while len(test_bits) < 1024: |
| | v = rng.getrandbits(16) |
| | if float16_is_nan_bits(v): |
| | continue |
| | test_bits.append(v) |
| |
|
| | for bits_int in test_bits: |
| | val = float16_int_to_float(bits_int) |
| | if val != val: |
| | continue |
| | if val == float('inf') or val == float('-inf'): |
| | continue |
| | expected = int(val) |
| | if expected < -32768 or expected > 32767: |
| | continue |
| |
|
| | bits = int_to_bits(bits_int, 16) |
| | actual_bits = eval_prefix_outputs(ctx, "float16.toint", {"x": bits}, gate_list=gate_list) |
| | actual = bits_to_int(actual_bits, signed=True) |
| |
|
| | total += 1 |
| | if actual == expected: |
| | passed += 1 |
| | elif len(failures) < 10: |
| | failures.append({ |
| | "in_bits": hex(bits_int), |
| | "expected": expected, |
| | "actual": actual, |
| | }) |
| |
|
| | results.append(TestResult("float16.toint", passed, total, failures)) |
| |
|
| | |
| | if f"float16.fromint.is_zero.weight" in ctx.tensors: |
| | passed, total = 0, 0 |
| | failures = [] |
| | gate_list = sorted([g for g in ctx.gates if g.startswith("float16.fromint.")]) |
| |
|
| | edge_ints = [0, 1, -1, 2, -2, 100, -100, 32767, -32768] |
| | test_vals = list(edge_ints) |
| | while len(test_vals) < 1024: |
| | test_vals.append(rng.randint(-32768, 32767)) |
| |
|
| | for val in test_vals: |
| | bits = int_to_bits(val, 16, signed=True) |
| | actual_bits = eval_prefix_outputs(ctx, "float16.fromint", {"x": bits}, gate_list=gate_list) |
| | actual_int = bits_to_int(actual_bits) |
| |
|
| | expected_bits = float_to_int(float(val)) |
| | total += 1 |
| | if actual_int == expected_bits: |
| | passed += 1 |
| | elif len(failures) < 10: |
| | failures.append({ |
| | "in_val": val, |
| | "expected": hex(expected_bits), |
| | "actual": hex(actual_int), |
| | }) |
| |
|
| | results.append(TestResult("float16.fromint", passed, total, failures)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_float16_unary(ctx: EvalContext) -> List[TestResult]: |
| | """Test LUT-backed float16 unary operations.""" |
| | results: List[TestResult] = [] |
| |
|
| | rng = random.Random(1337) |
| | values = build_float16_values(rng, 1024) |
| |
|
| | ops = [ |
| | ("float16.sqrt", "sqrt"), |
| | ("float16.rsqrt", "rsqrt"), |
| | ("float16.exp", "exp"), |
| | ("float16.ln", "ln"), |
| | ("float16.log2", "log2"), |
| | ("float16.log10", "log10"), |
| | ("float16.deg2rad", "deg2rad"), |
| | ("float16.rad2deg", "rad2deg"), |
| | ("float16.is_nan", "is_nan"), |
| | ("float16.is_inf", "is_inf"), |
| | ("float16.is_finite", "is_finite"), |
| | ("float16.is_zero", "is_zero"), |
| | ("float16.is_subnormal", "is_subnormal"), |
| | ("float16.is_normal", "is_normal"), |
| | ("float16.is_negative", "is_negative"), |
| | ("float16.sin", "sin"), |
| | ("float16.cos", "cos"), |
| | ("float16.tan", "tan"), |
| | ("float16.tanh", "tanh"), |
| | ("float16.sin_deg", "sin_deg"), |
| | ("float16.cos_deg", "cos_deg"), |
| | ("float16.tan_deg", "tan_deg"), |
| | ("float16.asin_deg", "asin_deg"), |
| | ("float16.acos_deg", "acos_deg"), |
| | ("float16.atan_deg", "atan_deg"), |
| | ("float16.asin", "asin"), |
| | ("float16.acos", "acos"), |
| | ("float16.atan", "atan"), |
| | ("float16.sinh", "sinh"), |
| | ("float16.cosh", "cosh"), |
| | ("float16.floor", "floor"), |
| | ("float16.ceil", "ceil"), |
| | ("float16.round", "round"), |
| | ] |
| |
|
| | for prefix, op in ops: |
| | if f"{prefix}.out0.weight" not in ctx.tensors: |
| | continue |
| | passed, total = 0, 0 |
| | failures: List[Dict[str, Any]] = [] |
| | for a_bits in values: |
| | bits_list = [float((a_bits >> i) & 1) for i in range(16)] |
| | actual_bits = eval_float16_lut_outputs(ctx, prefix, bits_list) |
| | actual_int = bits_to_int(actual_bits) |
| | expected_int, expected_nan = float16_expected_bits_unary(op, a_bits) |
| | ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int |
| | total += 1 |
| | if ok: |
| | passed += 1 |
| | elif len(failures) < 8: |
| | failures.append({ |
| | "input": hex(a_bits), |
| | "actual": hex(actual_int), |
| | "expected": hex(expected_int), |
| | }) |
| | results.append(TestResult(prefix, passed, total, failures)) |
| |
|
| | return results |
| |
|
| |
|
| | def test_float16_domain_flags(ctx: EvalContext) -> List[TestResult]: |
| | """Test float16 domain flag outputs.""" |
| | results: List[TestResult] = [] |
| | rng = random.Random(1337) |
| | values = build_float16_values(rng, 256) |
| | ops = [ |
| | ("float16.sqrt", "sqrt"), |
| | ("float16.rsqrt", "rsqrt"), |
| | ("float16.ln", "ln"), |
| | ("float16.log2", "log2"), |
| | ("float16.log10", "log10"), |
| | ("float16.asin", "asin"), |
| | ("float16.acos", "acos"), |
| | ("float16.asin_deg", "asin_deg"), |
| | ("float16.acos_deg", "acos_deg"), |
| | ] |
| | for prefix, op in ops: |
| | if f"{prefix}.domain.weight" not in ctx.tensors: |
| | continue |
| | passed, total = 0, 0 |
| | failures: List[Dict[str, Any]] = [] |
| | for a_bits in values: |
| | bits_list = [float((a_bits >> i) & 1) for i in range(16)] |
| | actual = eval_float16_lut_flag(ctx, prefix, bits_list) |
| | expected = float16_expected_domain(op, a_bits) |
| | total += 1 |
| | if int(actual) == expected: |
| | passed += 1 |
| | elif len(failures) < 8: |
| | failures.append({ |
| | "input": hex(a_bits), |
| | "actual": int(actual), |
| | "expected": expected, |
| | }) |
| | results.append(TestResult(f"{prefix}.domain", passed, total, failures)) |
| | return results |
| |
|
| |
|
| | def test_float16_checked_outputs(ctx: EvalContext) -> List[TestResult]: |
| | """Test checked outputs that force NaN on domain errors.""" |
| | results: List[TestResult] = [] |
| | rng = random.Random(1337) |
| | values = build_float16_values(rng, 256) |
| | ops = [ |
| | ("float16.sqrt", "sqrt"), |
| | ("float16.rsqrt", "rsqrt"), |
| | ("float16.ln", "ln"), |
| | ("float16.log2", "log2"), |
| | ("float16.log10", "log10"), |
| | ("float16.asin", "asin"), |
| | ("float16.acos", "acos"), |
| | ("float16.asin_deg", "asin_deg"), |
| | ("float16.acos_deg", "acos_deg"), |
| | ] |
| | for prefix, op in ops: |
| | if f"{prefix}.checked_out0.weight" not in ctx.tensors: |
| | continue |
| | passed, total = 0, 0 |
| | failures: List[Dict[str, Any]] = [] |
| | for a_bits in values: |
| | bits_list = [float((a_bits >> i) & 1) for i in range(16)] |
| | raw_bits = eval_float16_lut_outputs(ctx, prefix, bits_list) |
| | domain = eval_float16_lut_flag(ctx, prefix, bits_list) |
| | checked_bits: List[float] = [] |
| | nan_bits = 0x7E00 |
| | for i in range(16): |
| | nan_bit = (nan_bits >> i) & 1 |
| | if nan_bit: |
| | checked = 1.0 if (raw_bits[i] >= 0.5 or domain >= 0.5) else 0.0 |
| | else: |
| | checked = 1.0 if (raw_bits[i] >= 0.5 and domain < 0.5) else 0.0 |
| | checked_bits.append(checked) |
| | gate = f"{prefix}.checked_out{i}" |
| | for suffix in (".weight", ".bias", ".inputs"): |
| | key = gate + suffix |
| | if key in ctx.tensors: |
| | ctx.tested_tensors.add(key) |
| | actual_int = bits_to_int(checked_bits) |
| | if float16_expected_domain(op, a_bits): |
| | expected_int = 0x7E00 |
| | else: |
| | expected_int, _ = float16_expected_bits_unary(op, a_bits) |
| | total += 1 |
| | if actual_int == expected_int: |
| | passed += 1 |
| | elif len(failures) < 8: |
| | failures.append({ |
| | "input": hex(a_bits), |
| | "actual": hex(actual_int), |
| | "expected": hex(expected_int), |
| | }) |
| | results.append(TestResult(f"{prefix}.checked_out", passed, total, failures)) |
| | return results |
| |
|
| |
|
| | def test_float16_pow(ctx: EvalContext) -> List[TestResult]: |
| | """Test float16.pow (defined as exp(b * ln(a))).""" |
| | results: List[TestResult] = [] |
| | if f"float16.pow.out0.weight" not in ctx.tensors: |
| | return results |
| |
|
| | rng = random.Random(1337) |
| | pairs = build_float16_pairs(rng, 512) |
| | mul_prefix = "float16.pow.mul" |
| | mul_gates = sorted([g for g in ctx.gates if g.startswith(mul_prefix + ".")]) |
| |
|
| | passed, total = 0, 0 |
| | failures: List[Dict[str, Any]] = [] |
| | for a_bits, b_bits in pairs: |
| | a_list = [float((a_bits >> i) & 1) for i in range(16)] |
| | b_list = [float((b_bits >> i) & 1) for i in range(16)] |
| | |
| | ln_bits = eval_float16_lut_outputs(ctx, "float16.pow.ln", a_list, match_prefix="float16.pow.ln") |
| |
|
| | |
| | signals: Dict[int, float] = {} |
| | if "#0" in ctx.name_to_id: |
| | signals[ctx.name_to_id["#0"]] = 0.0 |
| | if "#1" in ctx.name_to_id: |
| | signals[ctx.name_to_id["#1"]] = 1.0 |
| | for i in range(16): |
| | sid = ctx.name_to_id.get(f"float16.pow.$b[{i}]") |
| | if sid is not None: |
| | signals[sid] = float(b_list[i]) |
| | for i in range(16): |
| | sid = ctx.name_to_id.get(f"float16.pow.ln.out{i}") |
| | if sid is not None: |
| | signals[sid] = float(ln_bits[i]) |
| |
|
| | if mul_prefix not in ctx.topo_cache or len(ctx.topo_cache[mul_prefix]) != len(mul_gates): |
| | ctx.topo_cache[mul_prefix] = topo_sort_gates(ctx, mul_gates) |
| | evaluate_gates_in_order(ctx, signals, ctx.topo_cache[mul_prefix]) |
| |
|
| | mul_bits = [] |
| | for i in range(16): |
| | gate = f"{mul_prefix}.out{i}" |
| | sid = ctx.name_to_id.get(gate) |
| | if sid is None or sid not in signals: |
| | raise RuntimeError(f"{mul_prefix}: missing output {gate}") |
| | mul_bits.append(float(signals[sid])) |
| |
|
| | exp_bits = eval_float16_lut_outputs(ctx, "float16.pow.exp", mul_bits, match_prefix="float16.pow.exp") |
| |
|
| | |
| | for i in range(16): |
| | gate = f"float16.pow.out{i}" |
| | for suffix in (".weight", ".bias", ".inputs"): |
| | key = gate + suffix |
| | if key in ctx.tensors: |
| | ctx.tested_tensors.add(key) |
| |
|
| | actual_int = bits_to_int(exp_bits) |
| | expected_int, expected_nan = float16_expected_bits_pow(a_bits, b_bits) |
| | ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int |
| | total += 1 |
| | if ok: |
| | passed += 1 |
| | elif len(failures) < 8: |
| | failures.append({ |
| | "a": hex(a_bits), |
| | "b": hex(b_bits), |
| | "actual": hex(actual_int), |
| | "expected": hex(expected_int), |
| | }) |
| |
|
| | results.append(TestResult("float16.pow", passed, total, failures)) |
| | return results |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | CATEGORIES = { |
| | "boolean": ("Boolean Gates", test_boolean_gates), |
| | "threshold": ("Threshold Gates", test_threshold_gates), |
| | "clz": ("CLZ (Count Leading Zeros)", test_clz), |
| | "adders": ("Arithmetic - Adders", test_adders), |
| | "comparators": ("Arithmetic - Comparators", test_comparators), |
| | "multiplier": ("Arithmetic - Multiplier", test_multiplier), |
| | "divider": ("Arithmetic - Divider", test_divider), |
| | "bitwise": ("Arithmetic - Bitwise", test_bitwise), |
| | "modular": ("Modular Arithmetic", test_modular), |
| | "combinational": ("Combinational Logic", test_combinational), |
| | "orphan": ("Orphan/Selector Tensors", test_orphan_tensors), |
| | "pattern": ("Pattern Recognition", test_pattern_recognition), |
| | "float16_basic": ("Float16 - Basic", test_float16_basic), |
| | "float32_basic": ("Float32 - Basic", test_float32_basic), |
| | "float16_arith": ("Float16 - Arithmetic", test_float16_arithmetic), |
| | "float16_conv": ("Float16 - Conversion", test_float16_conversion), |
| | "float16_unary": ("Float16 - Unary LUT", test_float16_unary), |
| | "float16_constants": ("Float16 - Constants", test_float16_constants), |
| | "float16_domain": ("Float16 - Domain Flags", test_float16_domain_flags), |
| | "float16_checked": ("Float16 - Checked Outputs", test_float16_checked_outputs), |
| | "float16_pow": ("Float16 - Pow", test_float16_pow), |
| | } |
| |
|
| |
|
| | def run_tests(ctx: EvalContext, categories: Optional[List[str]] = None, |
| | circuits: Optional[List[str]] = None) -> List[TestResult]: |
| | """Run tests for specified categories/circuits.""" |
| | all_results = [] |
| |
|
| | cats_to_run = categories if categories else list(CATEGORIES.keys()) |
| |
|
| | for cat_key in cats_to_run: |
| | if cat_key not in CATEGORIES: |
| | print(f"Warning: Unknown category '{cat_key}'") |
| | continue |
| |
|
| | cat_name, test_fn = CATEGORIES[cat_key] |
| | print(f"\n=== {cat_name.upper()} ===") |
| |
|
| | results = test_fn(ctx) |
| |
|
| | for r in results: |
| | if circuits and not any(c in r.circuit for c in circuits): |
| | continue |
| |
|
| | status = "[PASS]" if r.success else "[FAIL]" |
| | print(f" {r.circuit}: {r.passed}/{r.total} {status}") |
| | all_results.append(r) |
| |
|
| | return all_results |
| |
|
| |
|
| | def print_summary(results: List[TestResult], ctx: EvalContext, |
| | elapsed: float, verbose: bool = False): |
| | """Print test summary.""" |
| | total_passed = sum(r.passed for r in results) |
| | total_tests = sum(r.total for r in results) |
| |
|
| | print("\n" + "=" * 60) |
| | print("SUMMARY") |
| | print("=" * 60) |
| | print(f"Total: {total_passed}/{total_tests} ({100.0 * total_passed / total_tests:.4f}%)") |
| | print(f"Time: {elapsed:.2f}s") |
| |
|
| | failed = [r for r in results if not r.success] |
| | if failed: |
| | print(f"\nFailed ({len(failed)}):") |
| | for r in failed: |
| | print(f" {r.circuit}: {r.passed}/{r.total}") |
| | else: |
| | print("\nAll circuits passed!") |
| |
|
| | |
| | coverage = len(ctx.tested_tensors) / len(ctx.tensors) * 100 |
| | print(f"\n" + "=" * 60) |
| | print(f"TENSOR COVERAGE: {len(ctx.tested_tensors)}/{len(ctx.tensors)} ({coverage:.2f}%)") |
| |
|
| | if verbose: |
| | untested = set(ctx.tensors.keys()) - ctx.tested_tensors |
| | print(f"\nUntested tensors: {len(untested)}") |
| | for t in sorted(untested)[:20]: |
| | print(f" - {t}") |
| | if len(untested) > 20: |
| | print(f" ... and {len(untested) - 20} more") |
| |
|
| | |
| | fitness = total_passed / total_tests if total_tests > 0 else 0 |
| | print(f"\nFitness: {fitness:.6f}") |
| |
|
| |
|
| | def main(): |
| | parser = argparse.ArgumentParser(description="Unified evaluator for threshold-calculus circuits") |
| | parser.add_argument("--model", default="./arithmetic.safetensors", help="Path to model file") |
| | parser.add_argument("--circuit", action="append", help="Test specific circuit (can repeat)") |
| | parser.add_argument("--json", "-j", action="store_true", help="Output JSON for CI") |
| | parser.add_argument("--coverage", action="store_true", help="Show detailed coverage") |
| | parser.add_argument("--inputs-coverage", action="store_true", help="Sweep all gates using .inputs tensors") |
| | parser.add_argument("--list", "-l", action="store_true", help="List categories and exit") |
| |
|
| | args = parser.parse_args() |
| |
|
| | if args.list: |
| | print("Available categories:") |
| | for key, (name, _) in CATEGORIES.items(): |
| | print(f" {key}: {name}") |
| | return 0 |
| |
|
| | print(f"Loading model from {args.model}...") |
| | tensors, gates, signals, name_to_id, id_to_name = load_model(args.model) |
| |
|
| | print(f"Loaded {len(tensors)} tensors, {len(gates)} gates, {len(signals)} signals") |
| |
|
| | ctx = EvalContext( |
| | tensors=tensors, |
| | gates=gates, |
| | signals=signals, |
| | name_to_id=name_to_id, |
| | id_to_name=id_to_name, |
| | verbose=True, |
| | quick=False, |
| | ) |
| |
|
| | start = time.time() |
| | results = run_tests(ctx, categories=None, circuits=args.circuit) |
| |
|
| | inputs_coverage_sweep(ctx, seed=0, verbose=True, quiet=args.json) |
| | elapsed = time.time() - start |
| |
|
| | if args.json: |
| | output = { |
| | "total_passed": sum(r.passed for r in results), |
| | "total_tests": sum(r.total for r in results), |
| | "elapsed": elapsed, |
| | "coverage": len(ctx.tested_tensors) / len(tensors), |
| | "results": [{"circuit": r.circuit, "passed": r.passed, "total": r.total} for r in results], |
| | } |
| | print(json.dumps(output, indent=2)) |
| | else: |
| | print_summary(results, ctx, elapsed, verbose=True) |
| |
|
| | |
| | failed = [r for r in results if not r.success] |
| | return 1 if failed else 0 |
| |
|
| |
|
| | if __name__ == "__main__": |
| | sys.exit(main()) |
| |
|