在计算机诞生之初,浮点数的表示方式五花八门——每个硬件厂商都有自己的格式。IBM System/360使用16进制浮点,DEC VAX用自己的一套,CDC 6600又是一套。这导致:
1985年,IEEE发布了754-1985标准(由William Kahan主导设计),统一了浮点数格式。2008年修订为IEEE 754-2008,2019年再次更新为IEEE 754-2019。这一标准是现代所有FPU设计的基石。
小数点位置固定,例如Q15格式:1位符号 + 15位小数
优点:运算简单,硬件代价小
缺点:动态范围有限,精度固定
小数点位置由指数决定,可以"浮动"
优点:动态范围极大,能同时表示天文数字和微观量
缺点:硬件复杂,精度不均匀(大数附近稀疏,小数附近密集)
IEEE 754浮点数由三个字段组成:
指数E不使用补码,而是使用偏移码(Excess/Bias):
| 精度 | 指数位宽k | Bias值 | 指数范围 |
|---|---|---|---|
| 半精度 | 5 | 15 | -14 ~ +15 |
| 单精度 | 8 | 127 | -126 ~ +127 |
| 双精度 | 11 | 1023 | -1022 ~ +1023 |
对于规格化数,尾数的最高位总是1,因此不需要显式存储:
这相当于多了一位精度!单精度实际有24位有效数字。
| 类别 | 指数E | 尾数M | 含义 |
|---|---|---|---|
| 零(Zero) | 全0 | 全0 | +0 或 -0 |
| 非规格化数(Denormal) | 全0 | 非0 | 极小数的渐进下溢 |
| 规格化数(Normal) | 1~254 | 任意 | 常规浮点数 |
| 无穷(Infinity) | 全1 | 全0 | +∞ 或 -∞ |
| NaN(Not a Number) | 全1 | 非0 | 无效运算结果 |
IEEE 754定义了4种舍入模式(第05课将详细讲解):
下面实现一个单精度浮点数分类器,判断输入属于哪一类:
//=============================================================
// float_classifier.sv - IEEE 754 单精度浮点数分类器
// 功能:判断浮点数属于零/非规格化/规格化/无穷/NaN
// 验证:Verilator --lint-only
//=============================================================
module float_classifier (
input wire [31:0] fp_in, // IEEE 754 单精度输入
// 分类输出(one-hot)
output wire is_zero, // ±0
output wire is_denormal, // 非规格化数
output wire is_normal, // 规格化数
output wire is_infinity, // ±∞
output wire is_nan, // NaN
output wire is_signaling, // SNaN
output wire is_quiet, // QNaN
output wire sign, // 符号位
output wire [7:0] exponent, // 原始指数字段
output wire [22:0] mantissa // 原始尾数字段
);
// 拆解字段
assign sign = fp_in[31];
assign exponent = fp_in[30:23];
assign mantissa = fp_in[22:0];
// 中间信号
wire exp_all_zero = (exponent == 8'b0);
wire exp_all_one = (exponent == 8'b1111_1111);
wire mant_zero = (mantissa == 23'b0);
wire mant_msb = mantissa[22]; // QNaN的最高尾数位为1
// 分类逻辑
assign is_zero = exp_all_zero & mant_zero;
assign is_denormal = exp_all_zero & ~mant_zero;
assign is_normal = ~exp_all_zero & ~exp_all_one;
assign is_infinity = exp_all_one & mant_zero;
assign is_nan = exp_all_one & ~mant_zero;
assign is_signaling = is_nan & ~mant_msb; // SNaN: 尾数最高位为0但尾数非零
assign is_quiet = is_nan & mant_msb; // QNaN: 尾数最高位为1
endmodule
//=============================================================
// tb_float_classifier.sv - 测试平台
//=============================================================
module tb_float_classifier;
reg [31:0] fp_in;
wire is_zero, is_denormal, is_normal;
wire is_infinity, is_nan;
wire is_signaling, is_quiet;
wire sign;
wire [7:0] exponent;
wire [22:0] mantissa;
float_classifier uut (
.fp_in(fp_in),
.is_zero(is_zero), .is_denormal(is_denormal),
.is_normal(is_normal), .is_infinity(is_infinity),
.is_nan(is_nan), .is_signaling(is_signaling),
.is_quiet(is_quiet), .sign(sign),
.exponent(exponent), .mantissa(mantissa)
);
integer pass_count = 0;
integer fail_count = 0;
task check;
input [31:0] val;
input [4:0] expected; // {zero,denorm,normal,inf,nan}
input [255:0] name;
begin
fp_in = val;
#10;
if ({is_zero,is_denormal,is_normal,is_infinity,is_nan} !== expected) begin
$display("FAIL %0s: got %b%b%b%b%b expected %b",
name, is_zero, is_denormal, is_normal, is_infinity, is_nan, expected);
fail_count = fail_count + 1;
end else begin
$display("PASS %0s", name);
pass_count = pass_count + 1;
end
end
endtask
initial begin
// +0.0: sign=0, exp=0, mant=0
check(32'h0000_0000, 5'b10000, "+0.0");
// -0.0
check(32'h8000_0000, 5'b10000, "-0.0");
// 最小非规格化数: exp=0, mant=1
check(32'h0000_0001, 5'b01000, "MinDenorm");
// 最大非规格化数: exp=0, mant=all1
check(32'h007F_FFFF, 5'b01000, "MaxDenorm");
// 最小规格化数: exp=1, mant=0
check(32'h0080_0000, 5'b00100, "MinNormal");
// 1.0: exp=127(0x7F), mant=0
check(32'h3F80_0000, 5'b00100, "1.0");
// +∞: exp=all1, mant=0
check(32'h7F80_0000, 5'b00010, "+Infinity");
// -∞
check(32'hFF80_0000, 5'b00010, "-Infinity");
// QNaN: exp=all1, mant MSB=1
check(32'h7FC0_0000, 5'b00001, "QNaN");
// SNaN: exp=all1, mant MSB=0, mant!=0
check(32'h7F80_0001, 5'b00001, "SNaN");
$display("\n=== Results: %0d passed, %0d failed ===", pass_count, fail_count);
$finish;
end
endmodule
使用Verilator进行仿真,验证结果如下:
PASS +0.0
PASS -0.0
PASS MinDenorm
PASS MaxDenorm
PASS MinNormal
PASS 1.0
PASS +Infinity
PASS -Infinity
PASS QNaN
PASS SNaN
=== Results: 10 passed, 0 failed ===
✅Verilator验证通过 ✅10/10测试通过
为什么不直接用补码表示指数?偏移码有几个关键优势:
IEEE 754标准对FPU设计施加了严格的约束:
练习1:将十进制数 -6.625 转换为IEEE 754单精度浮点数的十六进制表示。
提示:6.625 = 110.101₂ = 1.10101 × 2²,指数 = 2 + 127 = 129 = 10000001₂
练习2:0x41200000表示的浮点数是多少?
提示:0100 0001 0010 0... → exp=130, 实际指数=3, mantissa=010...→1.01×2³=10.1₂
练习3:修改分类器,增加对半精度浮点数(16bit)的支持。
练习4:实现一个模块,将单精度浮点数的各字段提取并转换为实际值(用定点数近似表示)。
✅ 理解了IEEE 754标准的设计动机
✅ 掌握了浮点数的五大分类
✅ 实现了浮点数分类器
✅ 理解了偏移码的优势
下一成就:单精度大师 — 掌握单精度格式的每一个细节