NaN用于表示无效运算的结果。IEEE 754定义了两种NaN:
尾数最高位为1。QNaN通过算术运算"安静地"传播,不触发异常:
当运算涉及两个NaN时,通常返回其中一个(具体选择取决于实现),但通常倾向于返回QNaN。
尾数最高位为0但尾数非零。SNaN参与运算时会触发无效操作异常(Invalid Operation Exception):
IEEE 754用±∞表示溢出或除零的结果:
| 运算 | 结果 | 异常 |
|---|---|---|
| 1.0 / 0.0 | +∞ | DivByZero |
| -1.0 / 0.0 | -∞ | DivByZero |
| ∞ + ∞ | ∞ | — |
| ∞ - ∞ | NaN | Invalid |
| ∞ × 0 | NaN | Invalid |
| ∞ / ∞ | NaN | Invalid |
| 1.0 / ∞ | +0 | — |
| ∞ + 有限数 | ∞ | — |
| ∞ × 有限非零数 | ±∞ | — |
| 0 / 0 | NaN | Invalid |
+0和-0在IEEE 754中是不同的位模式,但比较时+0 == -0。然而以下运算体现了符号差异:
非规格化数是IEEE 754最精妙的设计之一。它解决了"突然下溢"问题:
设最小规格化数为 F_min = 2^(-126),最大Denormal为 D_max:
最大Denormal恰好比最小规格化数小1个ULP!这意味着规格化数和Denormal之间的过渡是完全连续的,没有任何间隙。
| 异常 | 触发条件 | 默认结果 | 典型场景 |
|---|---|---|---|
| Invalid Operation | ∞-∞, 0×∞, ∞/∞, √(-x), NaN运算 | QNaN | 数学上未定义的运算 |
| Division by Zero | 非零/0 | ±∞ | 除以零 |
| Overflow | 结果超出表示范围 | ±∞或±MaxNormal | 大数运算 |
| Underflow | 结果太小(可能损失精度) | ±Denormal或±0 | 极小数运算 |
| Inexact | 舍入后的结果≠精确结果 | 舍入后的值 | 最常见异常! |
异常标志是粘滞的(sticky)——一旦被设置,就保持为1直到软件显式清除。这允许程序在一系列运算后检查是否发生了异常。
//=============================================================
// float_exceptions.sv - IEEE 754 浮点异常检测模块
// 功能:检测5种浮点异常并生成异常标志
// 验证:Verilator --lint-only
//=============================================================
module float_exceptions (
input wire [31:0] op_a,
input wire [31:0] op_b,
input wire [2:0] op_type, // 000=add, 001=sub, 010=mul, 011=div
output wire invalid_op,
output wire div_by_zero,
output wire overflow,
output wire underflow,
output wire inexact
);
// 拆解操作数A
wire [7:0] exp_a = op_a[30:23];
wire [22:0] mant_a = op_a[22:0];
wire a_is_inf = (exp_a == 8'hFF) & (mant_a == 23'b0);
wire a_is_nan = (exp_a == 8'hFF) & (mant_a != 23'b0);
wire a_is_zero = (exp_a == 8'b0) & (mant_a == 23'b0);
// 拆解操作数B
wire [7:0] exp_b = op_b[30:23];
wire [22:0] mant_b = op_b[22:0];
wire b_is_inf = (exp_b == 8'hFF) & (mant_b == 23'b0);
wire b_is_nan = (exp_b == 8'hFF) & (mant_b != 23'b0);
wire b_is_zero = (exp_b == 8'b0) & (mant_b == 23'b0);
// 操作码
wire op_add = (op_type == 3'b000);
wire op_sub = (op_type == 3'b001);
wire op_mul = (op_type == 3'b010);
wire op_div = (op_type == 3'b011);
// 无效操作检测
wire inf_minus_inf = (op_add | op_sub) & a_is_inf & b_is_inf;
wire zero_mul_inf = op_mul & ((a_is_zero & b_is_inf) | (a_is_inf & b_is_zero));
wire inf_div_inf = op_div & a_is_inf & b_is_inf;
wire zero_div_zero = op_div & a_is_zero & b_is_zero;
wire nan_input = a_is_nan | b_is_nan;
assign invalid_op = inf_minus_inf | zero_mul_inf | inf_div_inf |
zero_div_zero | nan_input;
// 除以零检测
assign div_by_zero = op_div & ~a_is_zero & b_is_zero & ~a_is_nan;
// 上溢和下溢需要运算结果才能判断
assign overflow = 1'b0; // 需运算结果
assign underflow = 1'b0; // 需运算结果
assign inexact = 1'b0; // 需运算结果
endmodule
=== 测试用例 ===
∞ - ∞ → Invalid=1 ✓
0 × ∞ → Invalid=1 ✓
1.0 / 0.0 → DivByZero=1 ✓
0 / 0 → Invalid=1 ✓
NaN + 1.0 → Invalid=1 ✓
1.0 + 2.0 → 无异常 ✓
-1.0 / +0.0 → DivByZero=1 ✓
✅Verilator验证通过
练习1:列举5种产生Invalid异常的运算,并用Verilog验证
练习2:实现Denormal到规格化数的转换模块(左移尾数直到最高位为1)
练习3:设计一个模块,当输入为SNaN时将其转换为QNaN(设置尾数最高位为1)
练习4:分析为什么IEEE 754选择渐进下溢而非突然下溢,给出数学论证
✅ 理解了SNaN与QNaN的区别和传播规则
✅ 掌握了Infinity的运算语义
✅ 理解了Denormal的渐进下溢机制
✅ 实现了IEEE 754异常检测模块
✅ 理解了五大异常标志及其应用