CharlesCNorton commited on
Commit ·
8260d68
1
Parent(s): 22baf26
float add: full IEEE subnormal support (subnormal operands and gradual-underflow results) via effective exponent, implicit-bit selection, and a clamped normalize shift; oracle validated bit-exact against numpy float32/float16
Browse files- src/build.py +67 -16
- src/eval.py +48 -34
src/build.py
CHANGED
|
@@ -1097,9 +1097,9 @@ def add_float_add(tensors: Dict[str, torch.Tensor], family: str,
|
|
| 1097 |
Self-contained circuit; external inputs are the raw operand words
|
| 1098 |
$a[0..W-1] / $b[0..W-1] (MSB-first). Contract: exact IEEE specials
|
| 1099 |
(NaN, infinities, signed zeros, opposite-sign infinities -> NaN, exact
|
| 1100 |
-
cancellation -> +0),
|
| 1101 |
-
round-to-nearest-even for the mantissa. A zero operand passes the
|
| 1102 |
-
operand through verbatim.
|
| 1103 |
|
| 1104 |
Datapath: payload comparison picks the larger-magnitude operand, swap
|
| 1105 |
muxes route it to L; the smaller mantissa is aligned right through a
|
|
@@ -1132,6 +1132,13 @@ def add_float_add(tensors: Dict[str, torch.Tensor], family: str,
|
|
| 1132 |
add_gate(tensors, f"{prefix}.{op}.frac_zero", [-1.0] * F, [0.0])
|
| 1133 |
add_gate(tensors, f"{prefix}.{op}.is_nan", [1.0, 1.0], [-2.0])
|
| 1134 |
add_gate(tensors, f"{prefix}.{op}.is_inf", [1.0, 1.0], [-2.0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1135 |
|
| 1136 |
# Payload magnitude comparison (chooses the larger-magnitude operand).
|
| 1137 |
add_bit_cascade_compare(
|
|
@@ -1148,7 +1155,7 @@ def add_float_add(tensors: Dict[str, torch.Tensor], family: str,
|
|
| 1148 |
# Swap muxes: sel = pl.lt (a smaller in magnitude -> L = b).
|
| 1149 |
add_gate(tensors, f"{prefix}.swap.not_sel", [-1.0], [0.0])
|
| 1150 |
for name, width in (("sign_l", 1), ("exp_l", E), ("exp_s", E),
|
| 1151 |
-
("mant_l", F), ("mant_s", F)):
|
| 1152 |
for k in range(width):
|
| 1153 |
g = f"{prefix}.swap.{name}.bit{k}" if width > 1 else f"{prefix}.swap.{name}"
|
| 1154 |
add_gate(tensors, f"{g}.and_a", [1.0, 1.0], [-2.0])
|
|
@@ -1248,6 +1255,20 @@ def add_float_add(tensors: Dict[str, torch.Tensor], family: str,
|
|
| 1248 |
add_gate(tensors, f"{prefix}.exp_round.bit{k}.xor.layer2", [1.0, 1.0], [-2.0])
|
| 1249 |
add_gate(tensors, f"{prefix}.exp_round.bit{k}.carry", [1.0, 1.0], [-2.0])
|
| 1250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1251 |
add_gate(tensors, f"{prefix}.exp_r.zero", [-1.0] * (E + 2), [0.0])
|
| 1252 |
add_gate(tensors, f"{prefix}.exp_r.underflow", [1.0, 1.0], [-1.0])
|
| 1253 |
add_gate(tensors, f"{prefix}.exp_r.not_neg", [-1.0], [0.0])
|
|
@@ -1269,7 +1290,7 @@ def add_float_add(tensors: Dict[str, torch.Tensor], family: str,
|
|
| 1269 |
add_gate(tensors, f"{prefix}.sel.a_zero_pass", [1.0, 1.0, 1.0, 1.0], [-4.0])
|
| 1270 |
add_gate(tensors, f"{prefix}.sel.b_zero_pass", [1.0, 1.0, 1.0, 1.0], [-4.0])
|
| 1271 |
add_gate(tensors, f"{prefix}.sel.dp_reach", [1.0, 1.0, 1.0, 1.0], [-4.0])
|
| 1272 |
-
add_gate(tensors, f"{prefix}.sel.dp_zero_c", [1.0
|
| 1273 |
add_gate(tensors, f"{prefix}.sel.not_dp_zero_c", [-1.0], [0.0])
|
| 1274 |
add_gate(tensors, f"{prefix}.sel.not_ovf", [-1.0], [0.0])
|
| 1275 |
add_gate(tensors, f"{prefix}.sel.dp_zero", [1.0, 1.0], [-2.0])
|
|
@@ -3444,14 +3465,14 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3444 |
# bit F+2 implicit 1.
|
| 3445 |
def l_ext(k: int) -> int:
|
| 3446 |
if k == F + 2:
|
| 3447 |
-
return
|
| 3448 |
if 2 <= k <= F + 1:
|
| 3449 |
return mant_l(k - 2)
|
| 3450 |
return zero
|
| 3451 |
|
| 3452 |
def s_frame_in(k: int) -> int:
|
| 3453 |
if k == F + 2:
|
| 3454 |
-
return
|
| 3455 |
if 2 <= k <= F + 1:
|
| 3456 |
return mant_s(k - 2)
|
| 3457 |
return zero
|
|
@@ -3489,11 +3510,17 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3489 |
"a.frac_nz": frac_a_field, "a.frac_zero": frac_a_field,
|
| 3490 |
"a.is_nan": [R("a.exp_max"), R("a.frac_nz")],
|
| 3491 |
"a.is_inf": [R("a.exp_max"), R("a.frac_zero")],
|
|
|
|
|
|
|
|
|
|
| 3492 |
"b.exp_zero": exp_b_field, "b.exp_nzero": exp_b_field,
|
| 3493 |
"b.exp_max": exp_b_field,
|
| 3494 |
"b.frac_nz": frac_b_field, "b.frac_zero": frac_b_field,
|
| 3495 |
"b.is_nan": [R("b.exp_max"), R("b.frac_nz")],
|
| 3496 |
"b.is_inf": [R("b.exp_max"), R("b.frac_zero")],
|
|
|
|
|
|
|
|
|
|
| 3497 |
"swap.not_sel": [R("pl.lt")],
|
| 3498 |
"align.not_sat": [R("align.sat")],
|
| 3499 |
"align.sticky_final": [R(f"align.s{num_stages - 1}.sticky"), R("align.sat")],
|
|
@@ -3506,6 +3533,9 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3506 |
"round.tail": [shifted(1), shifted(0), R("align.sticky_final")],
|
| 3507 |
"round.rsl": [R("round.tail"), trunc(0)],
|
| 3508 |
"round.up": [shifted(2), R("round.rsl")],
|
|
|
|
|
|
|
|
|
|
| 3509 |
"exp_r.zero": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E + 2)],
|
| 3510 |
"exp_r.underflow": [R(f"exp_round.bit{E + 1}.xor.layer2"), R("exp_r.zero")],
|
| 3511 |
"exp_r.not_neg": [R(f"exp_round.bit{E + 1}.xor.layer2")],
|
|
@@ -3520,14 +3550,14 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3520 |
"sel.not_inf_ops": [R("sel.inf_ops")],
|
| 3521 |
"sel.inf_operand": [R("sel.not_nan"), R("sel.inf_ops")],
|
| 3522 |
"sel.both_zero": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3523 |
-
R("a.
|
| 3524 |
"sel.a_zero_pass": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3525 |
-
R("a.
|
| 3526 |
"sel.b_zero_pass": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3527 |
-
R("b.
|
| 3528 |
"sel.dp_reach": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3529 |
-
R("a.
|
| 3530 |
-
"sel.dp_zero_c": [R("lzc.not_nz")
|
| 3531 |
"sel.not_dp_zero_c": [R("sel.dp_zero_c")],
|
| 3532 |
"sel.not_ovf": [R("exp_r.overflow")],
|
| 3533 |
"sel.dp_zero": [R("sel.dp_reach"), R("sel.dp_zero_c")],
|
|
@@ -3585,7 +3615,7 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3585 |
return [R("pl.le.not_gt")]
|
| 3586 |
|
| 3587 |
# Swap muxes: sel = pl.lt; L side takes b when sel, S side takes a.
|
| 3588 |
-
m = re.match(r"^swap\.(sign_l|exp_l|exp_s|mant_l|mant_s)(?:\.bit(\d+))?\.(and_a|and_b|or)$", suffix)
|
| 3589 |
if m:
|
| 3590 |
name = m.group(1)
|
| 3591 |
k = int(m.group(2)) if m.group(2) is not None else 0
|
|
@@ -3594,7 +3624,11 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3594 |
if name == "sign_l":
|
| 3595 |
a_side, b_side = a(0), b(0)
|
| 3596 |
elif name in ("exp_l", "exp_s"):
|
| 3597 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3598 |
else:
|
| 3599 |
a_side, b_side = a(W - 1 - k), b(W - 1 - k)
|
| 3600 |
l_side = name.endswith("_l") or name == "sign_l"
|
|
@@ -3702,7 +3736,7 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3702 |
bbit = int(m.group(1))
|
| 3703 |
k = int(m.group(2))
|
| 3704 |
kind = m.group(3)
|
| 3705 |
-
sel = R(f"
|
| 3706 |
|
| 3707 |
def nin(kk: int) -> int:
|
| 3708 |
if kk < 0 or kk >= RES:
|
|
@@ -3737,7 +3771,7 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3737 |
k = int(m.group(1))
|
| 3738 |
rest = m.group(2)
|
| 3739 |
cin = R(f"rnd.bit{F - 1}.carry") if k == 0 else R(f"exp_round.bit{k - 1}.carry")
|
| 3740 |
-
e_bit = R(f"
|
| 3741 |
if rest == "xor.layer1.or" or rest == "xor.layer1.nand":
|
| 3742 |
return [e_bit, cin]
|
| 3743 |
if rest == "xor.layer2":
|
|
@@ -3749,6 +3783,23 @@ def infer_float_add_inputs(gate: str, reg: SignalRegistry, family: str,
|
|
| 3749 |
if m:
|
| 3750 |
return [R(f"lzc.nlz.bit{int(m.group(1))}")]
|
| 3751 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3752 |
m = re.match(r"^exp_s1\.fa(\d+)\.(.+)$", suffix)
|
| 3753 |
if m:
|
| 3754 |
k = int(m.group(1))
|
|
|
|
| 1097 |
Self-contained circuit; external inputs are the raw operand words
|
| 1098 |
$a[0..W-1] / $b[0..W-1] (MSB-first). Contract: exact IEEE specials
|
| 1099 |
(NaN, infinities, signed zeros, opposite-sign infinities -> NaN, exact
|
| 1100 |
+
cancellation -> +0), subnormal operands and gradual-underflow subnormal
|
| 1101 |
+
results, round-to-nearest-even for the mantissa. A zero operand passes the
|
| 1102 |
+
other operand through verbatim.
|
| 1103 |
|
| 1104 |
Datapath: payload comparison picks the larger-magnitude operand, swap
|
| 1105 |
muxes route it to L; the smaller mantissa is aligned right through a
|
|
|
|
| 1132 |
add_gate(tensors, f"{prefix}.{op}.frac_zero", [-1.0] * F, [0.0])
|
| 1133 |
add_gate(tensors, f"{prefix}.{op}.is_nan", [1.0, 1.0], [-2.0])
|
| 1134 |
add_gate(tensors, f"{prefix}.{op}.is_inf", [1.0, 1.0], [-2.0])
|
| 1135 |
+
# True zero is exp==0 AND frac==0; a subnormal (exp==0, frac!=0) is a
|
| 1136 |
+
# nonzero datapath operand, not a zero.
|
| 1137 |
+
add_gate(tensors, f"{prefix}.{op}.is_zero", [1.0, 1.0], [-2.0])
|
| 1138 |
+
add_gate(tensors, f"{prefix}.{op}.nonzero", [-1.0], [0.0])
|
| 1139 |
+
# Effective exponent LSB for subnormals: a subnormal (exp==0) acts at
|
| 1140 |
+
# the smallest normal exponent (biased 1), so force the LSB when exp==0.
|
| 1141 |
+
add_gate(tensors, f"{prefix}.{op}.eexp_lsb", [1.0, 1.0], [-1.0])
|
| 1142 |
|
| 1143 |
# Payload magnitude comparison (chooses the larger-magnitude operand).
|
| 1144 |
add_bit_cascade_compare(
|
|
|
|
| 1155 |
# Swap muxes: sel = pl.lt (a smaller in magnitude -> L = b).
|
| 1156 |
add_gate(tensors, f"{prefix}.swap.not_sel", [-1.0], [0.0])
|
| 1157 |
for name, width in (("sign_l", 1), ("exp_l", E), ("exp_s", E),
|
| 1158 |
+
("mant_l", F), ("mant_s", F), ("impl_l", 1), ("impl_s", 1)):
|
| 1159 |
for k in range(width):
|
| 1160 |
g = f"{prefix}.swap.{name}.bit{k}" if width > 1 else f"{prefix}.swap.{name}"
|
| 1161 |
add_gate(tensors, f"{g}.and_a", [1.0, 1.0], [-2.0])
|
|
|
|
| 1255 |
add_gate(tensors, f"{prefix}.exp_round.bit{k}.xor.layer2", [1.0, 1.0], [-2.0])
|
| 1256 |
add_gate(tensors, f"{prefix}.exp_round.bit{k}.carry", [1.0, 1.0], [-2.0])
|
| 1257 |
|
| 1258 |
+
# Gradual underflow. exp_r <= 0 (pre-round) means the result is subnormal:
|
| 1259 |
+
# clamp the normalize shift to exp_L so it stays denormalized, and clamp the
|
| 1260 |
+
# exponent field to 0. The datapath's normal path then emits the subnormal.
|
| 1261 |
+
add_gate(tensors, f"{prefix}.er.zero", [-1.0] * (E + 2), [0.0])
|
| 1262 |
+
add_gate(tensors, f"{prefix}.er.underflow", [1.0, 1.0], [-1.0]) # OR(sign, zero)
|
| 1263 |
+
add_gate(tensors, f"{prefix}.er.not_underflow", [-1.0], [0.0])
|
| 1264 |
+
for k in range(E + 2):
|
| 1265 |
+
add_gate(tensors, f"{prefix}.exprc.bit{k}", [1.0, 1.0], [-2.0]) # AND(exp_r, not_uf)
|
| 1266 |
+
for bbit in range(nlz_bits): # nsh = min(nlz, exp_L)
|
| 1267 |
+
add_gate(tensors, f"{prefix}.nsh.bit{bbit}.not_sel", [-1.0], [0.0])
|
| 1268 |
+
add_gate(tensors, f"{prefix}.nsh.bit{bbit}.and_a", [1.0, 1.0], [-2.0])
|
| 1269 |
+
add_gate(tensors, f"{prefix}.nsh.bit{bbit}.and_b", [1.0, 1.0], [-2.0])
|
| 1270 |
+
add_gate(tensors, f"{prefix}.nsh.bit{bbit}.or", [1.0, 1.0], [-1.0])
|
| 1271 |
+
|
| 1272 |
add_gate(tensors, f"{prefix}.exp_r.zero", [-1.0] * (E + 2), [0.0])
|
| 1273 |
add_gate(tensors, f"{prefix}.exp_r.underflow", [1.0, 1.0], [-1.0])
|
| 1274 |
add_gate(tensors, f"{prefix}.exp_r.not_neg", [-1.0], [0.0])
|
|
|
|
| 1290 |
add_gate(tensors, f"{prefix}.sel.a_zero_pass", [1.0, 1.0, 1.0, 1.0], [-4.0])
|
| 1291 |
add_gate(tensors, f"{prefix}.sel.b_zero_pass", [1.0, 1.0, 1.0, 1.0], [-4.0])
|
| 1292 |
add_gate(tensors, f"{prefix}.sel.dp_reach", [1.0, 1.0, 1.0, 1.0], [-4.0])
|
| 1293 |
+
add_gate(tensors, f"{prefix}.sel.dp_zero_c", [1.0], [-1.0]) # true-zero result only
|
| 1294 |
add_gate(tensors, f"{prefix}.sel.not_dp_zero_c", [-1.0], [0.0])
|
| 1295 |
add_gate(tensors, f"{prefix}.sel.not_ovf", [-1.0], [0.0])
|
| 1296 |
add_gate(tensors, f"{prefix}.sel.dp_zero", [1.0, 1.0], [-2.0])
|
|
|
|
| 3465 |
# bit F+2 implicit 1.
|
| 3466 |
def l_ext(k: int) -> int:
|
| 3467 |
if k == F + 2:
|
| 3468 |
+
return R("swap.impl_l.or") # implicit bit: 1 normal, 0 subnormal
|
| 3469 |
if 2 <= k <= F + 1:
|
| 3470 |
return mant_l(k - 2)
|
| 3471 |
return zero
|
| 3472 |
|
| 3473 |
def s_frame_in(k: int) -> int:
|
| 3474 |
if k == F + 2:
|
| 3475 |
+
return R("swap.impl_s.or")
|
| 3476 |
if 2 <= k <= F + 1:
|
| 3477 |
return mant_s(k - 2)
|
| 3478 |
return zero
|
|
|
|
| 3510 |
"a.frac_nz": frac_a_field, "a.frac_zero": frac_a_field,
|
| 3511 |
"a.is_nan": [R("a.exp_max"), R("a.frac_nz")],
|
| 3512 |
"a.is_inf": [R("a.exp_max"), R("a.frac_zero")],
|
| 3513 |
+
"a.eexp_lsb": [a(E), R("a.exp_zero")],
|
| 3514 |
+
"a.is_zero": [R("a.exp_zero"), R("a.frac_zero")],
|
| 3515 |
+
"a.nonzero": [R("a.is_zero")],
|
| 3516 |
"b.exp_zero": exp_b_field, "b.exp_nzero": exp_b_field,
|
| 3517 |
"b.exp_max": exp_b_field,
|
| 3518 |
"b.frac_nz": frac_b_field, "b.frac_zero": frac_b_field,
|
| 3519 |
"b.is_nan": [R("b.exp_max"), R("b.frac_nz")],
|
| 3520 |
"b.is_inf": [R("b.exp_max"), R("b.frac_zero")],
|
| 3521 |
+
"b.eexp_lsb": [b(E), R("b.exp_zero")],
|
| 3522 |
+
"b.is_zero": [R("b.exp_zero"), R("b.frac_zero")],
|
| 3523 |
+
"b.nonzero": [R("b.is_zero")],
|
| 3524 |
"swap.not_sel": [R("pl.lt")],
|
| 3525 |
"align.not_sat": [R("align.sat")],
|
| 3526 |
"align.sticky_final": [R(f"align.s{num_stages - 1}.sticky"), R("align.sat")],
|
|
|
|
| 3533 |
"round.tail": [shifted(1), shifted(0), R("align.sticky_final")],
|
| 3534 |
"round.rsl": [R("round.tail"), trunc(0)],
|
| 3535 |
"round.up": [shifted(2), R("round.rsl")],
|
| 3536 |
+
"er.zero": [R(f"exp_r.fa{k}.ha2.sum.layer2") for k in range(E + 2)],
|
| 3537 |
+
"er.underflow": [R(f"exp_r.fa{E + 1}.ha2.sum.layer2"), R("er.zero")],
|
| 3538 |
+
"er.not_underflow": [R("er.underflow")],
|
| 3539 |
"exp_r.zero": [R(f"exp_round.bit{k}.xor.layer2") for k in range(E + 2)],
|
| 3540 |
"exp_r.underflow": [R(f"exp_round.bit{E + 1}.xor.layer2"), R("exp_r.zero")],
|
| 3541 |
"exp_r.not_neg": [R(f"exp_round.bit{E + 1}.xor.layer2")],
|
|
|
|
| 3550 |
"sel.not_inf_ops": [R("sel.inf_ops")],
|
| 3551 |
"sel.inf_operand": [R("sel.not_nan"), R("sel.inf_ops")],
|
| 3552 |
"sel.both_zero": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3553 |
+
R("a.is_zero"), R("b.is_zero")],
|
| 3554 |
"sel.a_zero_pass": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3555 |
+
R("a.is_zero"), R("b.nonzero")],
|
| 3556 |
"sel.b_zero_pass": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3557 |
+
R("b.is_zero"), R("a.nonzero")],
|
| 3558 |
"sel.dp_reach": [R("sel.not_nan"), R("sel.not_inf_ops"),
|
| 3559 |
+
R("a.nonzero"), R("b.nonzero")],
|
| 3560 |
+
"sel.dp_zero_c": [R("lzc.not_nz")],
|
| 3561 |
"sel.not_dp_zero_c": [R("sel.dp_zero_c")],
|
| 3562 |
"sel.not_ovf": [R("exp_r.overflow")],
|
| 3563 |
"sel.dp_zero": [R("sel.dp_reach"), R("sel.dp_zero_c")],
|
|
|
|
| 3615 |
return [R("pl.le.not_gt")]
|
| 3616 |
|
| 3617 |
# Swap muxes: sel = pl.lt; L side takes b when sel, S side takes a.
|
| 3618 |
+
m = re.match(r"^swap\.(sign_l|exp_l|exp_s|mant_l|mant_s|impl_l|impl_s)(?:\.bit(\d+))?\.(and_a|and_b|or)$", suffix)
|
| 3619 |
if m:
|
| 3620 |
name = m.group(1)
|
| 3621 |
k = int(m.group(2)) if m.group(2) is not None else 0
|
|
|
|
| 3624 |
if name == "sign_l":
|
| 3625 |
a_side, b_side = a(0), b(0)
|
| 3626 |
elif name in ("exp_l", "exp_s"):
|
| 3627 |
+
# LSB carries the subnormal correction (effective exponent >= 1).
|
| 3628 |
+
a_side, b_side = ((R("a.eexp_lsb"), R("b.eexp_lsb")) if k == 0
|
| 3629 |
+
else (a(E - k), b(E - k)))
|
| 3630 |
+
elif name in ("impl_l", "impl_s"):
|
| 3631 |
+
a_side, b_side = R("a.exp_nzero"), R("b.exp_nzero")
|
| 3632 |
else:
|
| 3633 |
a_side, b_side = a(W - 1 - k), b(W - 1 - k)
|
| 3634 |
l_side = name.endswith("_l") or name == "sign_l"
|
|
|
|
| 3736 |
bbit = int(m.group(1))
|
| 3737 |
k = int(m.group(2))
|
| 3738 |
kind = m.group(3)
|
| 3739 |
+
sel = R(f"nsh.bit{bbit}.or") # clamped shift = min(nlz, exp_L)
|
| 3740 |
|
| 3741 |
def nin(kk: int) -> int:
|
| 3742 |
if kk < 0 or kk >= RES:
|
|
|
|
| 3771 |
k = int(m.group(1))
|
| 3772 |
rest = m.group(2)
|
| 3773 |
cin = R(f"rnd.bit{F - 1}.carry") if k == 0 else R(f"exp_round.bit{k - 1}.carry")
|
| 3774 |
+
e_bit = R(f"exprc.bit{k}") # exp_r clamped to 0 when subnormal
|
| 3775 |
if rest == "xor.layer1.or" or rest == "xor.layer1.nand":
|
| 3776 |
return [e_bit, cin]
|
| 3777 |
if rest == "xor.layer2":
|
|
|
|
| 3783 |
if m:
|
| 3784 |
return [R(f"lzc.nlz.bit{int(m.group(1))}")]
|
| 3785 |
|
| 3786 |
+
m = re.match(r"^exprc\.bit(\d+)$", suffix) # exp_r clamped to 0 on underflow
|
| 3787 |
+
if m:
|
| 3788 |
+
k = int(m.group(1))
|
| 3789 |
+
return [R(f"exp_r.fa{k}.ha2.sum.layer2"), R("er.not_underflow")]
|
| 3790 |
+
|
| 3791 |
+
m = re.match(r"^nsh\.bit(\d+)\.(not_sel|and_a|and_b|or)$", suffix) # min(nlz, exp_L)
|
| 3792 |
+
if m:
|
| 3793 |
+
bbit = int(m.group(1))
|
| 3794 |
+
kind = m.group(2)
|
| 3795 |
+
if kind == "not_sel":
|
| 3796 |
+
return [R("er.underflow")]
|
| 3797 |
+
if kind == "and_a":
|
| 3798 |
+
return [R(f"lzc.nlz.bit{bbit}"), R(f"nsh.bit{bbit}.not_sel")]
|
| 3799 |
+
if kind == "and_b":
|
| 3800 |
+
return [exp_l(bbit), R("er.underflow")]
|
| 3801 |
+
return [R(f"nsh.bit{bbit}.and_a"), R(f"nsh.bit{bbit}.and_b")]
|
| 3802 |
+
|
| 3803 |
m = re.match(r"^exp_s1\.fa(\d+)\.(.+)$", suffix)
|
| 3804 |
if m:
|
| 3805 |
k = int(m.group(1))
|
src/eval.py
CHANGED
|
@@ -780,8 +780,9 @@ def float_div_oracle(aw: int, bw: int, exp_bits: int, frac_bits: int) -> int:
|
|
| 780 |
def float_add_oracle(aw: int, bw: int, exp_bits: int, frac_bits: int) -> int:
|
| 781 |
"""Expected sum word under the documented contract: exact IEEE specials
|
| 782 |
(NaN, infinities, opposite-sign infinities -> NaN, signed zeros, exact
|
| 783 |
-
cancellation -> +0),
|
| 784 |
-
|
|
|
|
| 785 |
E, F = exp_bits, frac_bits
|
| 786 |
emax = (1 << E) - 1
|
| 787 |
fmask = (1 << F) - 1
|
|
@@ -792,59 +793,72 @@ def float_add_oracle(aw: int, bw: int, exp_bits: int, frac_bits: int) -> int:
|
|
| 792 |
b_nan = eb == emax and fb != 0
|
| 793 |
a_inf = ea == emax and fa == 0
|
| 794 |
b_inf = eb == emax and fb == 0
|
| 795 |
-
a_zero = ea == 0 # flush-to-zero
|
| 796 |
-
b_zero = eb == 0
|
| 797 |
if a_nan or b_nan or (a_inf and b_inf and sa != sb):
|
| 798 |
return qnan
|
| 799 |
if a_inf or b_inf:
|
| 800 |
s = sa if a_inf else sb
|
| 801 |
return (s << (E + F)) | (emax << F)
|
|
|
|
|
|
|
| 802 |
if a_zero and b_zero:
|
| 803 |
return (sa & sb) << (E + F)
|
| 804 |
if a_zero:
|
| 805 |
return bw
|
| 806 |
if b_zero:
|
| 807 |
return aw
|
| 808 |
-
Ma
|
| 809 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 810 |
if pla >= plb:
|
| 811 |
-
sL, ML, eL, MS, eS = sa, Ma,
|
| 812 |
else:
|
| 813 |
-
sL, ML, eL, MS, eS = sb, Mb,
|
| 814 |
d = eL - eS
|
| 815 |
-
# Exact fixed-point value with G extra low bits below the mantissa LSB
|
| 816 |
-
# so guard/round/sticky are all recoverable for round-to-nearest-even.
|
| 817 |
G = F + 4
|
| 818 |
total = (ML << (d + G)) + (MS << G) if sa == sb else (ML << (d + G)) - (MS << G)
|
| 819 |
if total == 0:
|
| 820 |
return 0 # exact cancellation -> +0
|
| 821 |
t = total.bit_length() - 1
|
| 822 |
-
# Normalize so the leading 1 sits at bit position (F + G); the mantissa
|
| 823 |
-
# is the top F+1 bits, then guard/round/sticky below.
|
| 824 |
exp_r = eS + (t - G) - F
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 830 |
else:
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
if
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
inf_w = (sL << (E + F)) | (emax << F)
|
| 842 |
-
zero_w = sL << (E + F)
|
| 843 |
-
if exp_r >= emax:
|
| 844 |
-
return inf_w
|
| 845 |
-
if exp_r <= 0:
|
| 846 |
-
return zero_w
|
| 847 |
-
return (sL << (E + F)) | (exp_r << F) | frac
|
| 848 |
|
| 849 |
|
| 850 |
# =============================================================================
|
|
|
|
| 780 |
def float_add_oracle(aw: int, bw: int, exp_bits: int, frac_bits: int) -> int:
|
| 781 |
"""Expected sum word under the documented contract: exact IEEE specials
|
| 782 |
(NaN, infinities, opposite-sign infinities -> NaN, signed zeros, exact
|
| 783 |
+
cancellation -> +0), subnormal operands and gradual-underflow subnormal
|
| 784 |
+
results, round-to-nearest-even mantissa. A zero operand passes the other
|
| 785 |
+
through verbatim. Pure integer arithmetic, so exact."""
|
| 786 |
E, F = exp_bits, frac_bits
|
| 787 |
emax = (1 << E) - 1
|
| 788 |
fmask = (1 << F) - 1
|
|
|
|
| 793 |
b_nan = eb == emax and fb != 0
|
| 794 |
a_inf = ea == emax and fa == 0
|
| 795 |
b_inf = eb == emax and fb == 0
|
|
|
|
|
|
|
| 796 |
if a_nan or b_nan or (a_inf and b_inf and sa != sb):
|
| 797 |
return qnan
|
| 798 |
if a_inf or b_inf:
|
| 799 |
s = sa if a_inf else sb
|
| 800 |
return (s << (E + F)) | (emax << F)
|
| 801 |
+
a_zero = ea == 0 and fa == 0 # true zero, not subnormal
|
| 802 |
+
b_zero = eb == 0 and fb == 0
|
| 803 |
if a_zero and b_zero:
|
| 804 |
return (sa & sb) << (E + F)
|
| 805 |
if a_zero:
|
| 806 |
return bw
|
| 807 |
if b_zero:
|
| 808 |
return aw
|
| 809 |
+
Ma = ((1 << F) if ea else 0) | fa # implicit bit = (exp != 0)
|
| 810 |
+
Mb = ((1 << F) if eb else 0) | fb
|
| 811 |
+
eea = ea if ea else 1 # effective (denormal) exponent
|
| 812 |
+
eeb = eb if eb else 1
|
| 813 |
+
pla = (eea << (F + 1)) | Ma
|
| 814 |
+
plb = (eeb << (F + 1)) | Mb
|
| 815 |
if pla >= plb:
|
| 816 |
+
sL, ML, eL, MS, eS = sa, Ma, eea, Mb, eeb
|
| 817 |
else:
|
| 818 |
+
sL, ML, eL, MS, eS = sb, Mb, eeb, Ma, eea
|
| 819 |
d = eL - eS
|
| 820 |
+
# Exact fixed-point value with G extra low bits below the mantissa LSB.
|
|
|
|
| 821 |
G = F + 4
|
| 822 |
total = (ML << (d + G)) + (MS << G) if sa == sb else (ML << (d + G)) - (MS << G)
|
| 823 |
if total == 0:
|
| 824 |
return 0 # exact cancellation -> +0
|
| 825 |
t = total.bit_length() - 1
|
|
|
|
|
|
|
| 826 |
exp_r = eS + (t - G) - F
|
| 827 |
+
if exp_r >= 1:
|
| 828 |
+
lead = F + G
|
| 829 |
+
if t >= lead:
|
| 830 |
+
sh = t - lead
|
| 831 |
+
mant_ext = total >> sh
|
| 832 |
+
sticky_low = 1 if (total & ((1 << sh) - 1)) else 0
|
| 833 |
+
else:
|
| 834 |
+
mant_ext = total << (lead - t)
|
| 835 |
+
sticky_low = 0
|
| 836 |
+
frac = (mant_ext >> G) & fmask
|
| 837 |
+
guard = (mant_ext >> (G - 1)) & 1
|
| 838 |
+
below = (mant_ext & ((1 << (G - 1)) - 1)) or sticky_low
|
| 839 |
+
if guard and ((frac & 1) or below): # round-to-nearest-even
|
| 840 |
+
frac += 1
|
| 841 |
+
if frac > fmask:
|
| 842 |
+
frac = 0
|
| 843 |
+
exp_r += 1
|
| 844 |
+
if exp_r >= emax:
|
| 845 |
+
return (sL << (E + F)) | (emax << F)
|
| 846 |
+
return (sL << (E + F)) | (exp_r << F) | frac
|
| 847 |
+
# subnormal result: gradual underflow to exponent field 0
|
| 848 |
+
rshift = G + 1 - eS
|
| 849 |
+
if rshift <= 0:
|
| 850 |
+
sig = total << (-rshift)
|
| 851 |
else:
|
| 852 |
+
sig = total >> rshift
|
| 853 |
+
guard = (total >> (rshift - 1)) & 1
|
| 854 |
+
sticky = 1 if (total & ((1 << (rshift - 1)) - 1)) else 0
|
| 855 |
+
if guard and ((sig & 1) or sticky):
|
| 856 |
+
sig += 1
|
| 857 |
+
if sig == 0:
|
| 858 |
+
return sL << (E + F)
|
| 859 |
+
if sig >= (1 << F): # rounded up to smallest normal
|
| 860 |
+
return (sL << (E + F)) | (1 << F)
|
| 861 |
+
return (sL << (E + F)) | sig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
|
| 863 |
|
| 864 |
# =============================================================================
|