浮点比较看起来简单(a < b?),但实际有很多陷阱:
IEEE 754定义了14种比较谓词(quiet和signaling各7种):
| 谓词 | 含义 | NaN行为 | ±0行为 |
|---|---|---|---|
| eq | a = b | NaN→False | +0=-0 |
| ne | a ≠ b | NaN→True | +0≠-0→False |
| lt | a < b | NaN→False | -0<+0→False |
| le | a ≤ b | NaN→False | -0≤+0→True |
| gt | a > b | NaN→False | -0>+0→False |
| ge | a ≥ b | NaN→False | -0≥+0→True |
| uno | 无序(含NaN) | NaN→True | N/A |
Quiet谓词:遇到NaN时不触发异常,直接返回False/True
Signaling谓词:遇到NaN时触发Invalid Operation异常
大多数编程语言使用Quiet版本(C的<、>、==等)。
一个巧妙的观察:正浮点数的大小顺序与其整数位模式的大小顺序一致!
但负数不同——负浮点数的位模式是递减的:
//=============================================================
// float_compare.sv - 单精度浮点比较器
// 支持所有IEEE 754比较谓词
//=============================================================
module float_compare (
input wire [31:0] a,
input wire [31:0] b,
input wire [2:0] op, // 操作码
output wire result // 比较结果
);
// 操作码定义:
// 000 = eq, 001 = ne, 010 = lt, 011 = le
// 100 = gt, 101 = ge, 110 = uno
wire sign_a = a[31], sign_b = b[31];
wire [7:0] exp_a = a[30:23], exp_b = b[30:23];
wire [22:0] frac_a = a[22:0], frac_b = b[22:0];
// === 特殊值检测 ===
wire a_nan = (exp_a==8'hFF) & (frac_a!=23'b0);
wire b_nan = (exp_b==8'hFF) & (frac_b!=23'b0);
wire a_zero = (exp_a==8'b0) & (frac_a==23'b0);
wire b_zero = (exp_b==8'b0) & (frac_b==23'b0);
wire both_zero = a_zero & b_zero;
wire either_nan = a_nan | b_nan;
// === 符号翻转法无符号比较 ===
wire [31:0] a_flip = {~sign_a, a[30:0]};
wire [31:0] b_flip = {~sign_b, b[30:0]};
wire a_lt_b = (a_flip < b_flip) & ~both_zero;
wire a_gt_b = (a_flip > b_flip) & ~both_zero;
wire a_eq_b = (a == b) | both_zero; // +0=-0
// === 比较谓词输出 ===
wire cmp_eq = a_eq_b;
wire cmp_ne = ~a_eq_b;
wire cmp_lt = a_lt_b;
wire cmp_le = a_lt_b | a_eq_b;
wire cmp_gt = a_gt_b;
wire cmp_ge = a_gt_b | a_eq_b;
wire cmp_uno = either_nan;
// NaN处理: 除ne和uno外,含NaN则返回False
wire nan_false = either_nan; // eq/lt/le/gt/ge遇NaN→False
assign result = (op == 3'b000) ? (cmp_eq & ~nan_false) : // eq
(op == 3'b001) ? (cmp_ne | cmp_uno) : // ne
(op == 3'b010) ? (cmp_lt & ~nan_false) : // lt
(op == 3'b011) ? (cmp_le & ~nan_false) : // le
(op == 3'b100) ? (cmp_gt & ~nan_false) : // gt
(op == 3'b101) ? (cmp_ge & ~nan_false) : // ge
cmp_uno; // uno
endmodule
//=============================================================
// float_minmax.sv - 浮点最小值/最大值选择器
//=============================================================
module float_minmax (
input wire [31:0] a,
input wire [31:0] b,
input wire is_max, // 1=max, 0=min
output wire [31:0] result
);
wire sign_a = a[31], sign_b = b[31];
wire [7:0] exp_a = a[30:23], exp_b = b[30:23];
wire [22:0] frac_a = a[22:0], frac_b = b[22:0];
wire a_nan = (exp_a==8'hFF)&(frac_a!=23'b0);
wire b_nan = (exp_b==8'hFF)&(frac_b!=23'b0);
wire a_zero = (exp_a==8'b0)&(frac_a==23'b0);
wire b_zero = (exp_b==8'b0)&(frac_b==23'b0);
wire both_zero = a_zero & b_zero;
// 符号翻转比较
wire [31:0] a_flip = {~sign_a, a[30:0]};
wire [31:0] b_flip = {~sign_b, b[30:0]};
wire a_lt_b = (a_flip < b_flip) & ~both_zero;
// NaN处理: IEEE 754-2019 minNum/maxNum行为
// minNum: 如果一个操作数是NaN,返回另一个
// 两个都是NaN,返回NaN
wire sel_a = is_max ? ~a_lt_b : a_lt_b;
wire sel_b = ~sel_a;
assign result = a_nan ? (b_nan ? {1'b0,8'hFF,1'b1,22'b0} : b) :
b_nan ? a :
sel_a ? a : b;
endmodule
//=============================================================
// tb_float_compare.sv - 比较器测试
//=============================================================
module tb_float_compare;
reg [31:0] a, b; reg [2:0] op;
wire result;
float_compare uut(.a(a),.b(b),.op(op),.result(result));
integer err = 0;
task chk; input [31:0] ia,ib; input [2:0] iop; input expected; input [255:0] nm;
begin
a=ia; b=ib; op=iop; #10;
if(result !== expected) begin
$display("FAIL %0s: got %b exp %b", nm, result, expected);
err = err + 1;
end else $display("PASS %0s", nm);
end
endtask
localparam ONE = 32'h3F800000;
localparam TWO = 32'h40000000;
localparam MONE = 32'hBF800000;
localparam PZERO= 32'h00000000;
localparam NZERO= 32'h80000000;
localparam QNAN = 32'h7FC00000;
initial begin
// eq: 1.0 == 1.0 → True
chk(ONE, ONE, 3'b000, 1'b1, "1==1");
// eq: +0 == -0 → True
chk(PZERO, NZERO, 3'b000, 1'b1, "+0==-0");
// lt: 1.0 < 2.0 → True
chk(ONE, TWO, 3'b010, 1'b1, "1<2");
// lt: -1.0 < 1.0 → True
chk(MONE, ONE, 3'b010, 1'b1, "-1<1");
// gt: 2.0 > 1.0 → True
chk(TWO, ONE, 3'b100, 1'b1, "2>1");
// eq: NaN == NaN → False
chk(QNAN, QNAN, 3'b000, 1'b0, "NaN==NaN");
// ne: NaN != NaN → True
chk(QNAN, QNAN, 3'b001, 1'b1, "NaN!=NaN");
// lt: NaN < 1.0 → False
chk(QNAN, ONE, 3'b010, 1'b0, "NaN<1");
// uno: NaN, 1.0 → True
chk(QNAN, ONE, 3'b110, 1'b1, "uno(NaN,1)");
// le: -0 ≤ +0 → True
chk(NZERO, PZERO, 3'b011, 1'b1, "-0<=+0");
$display("\n=== 浮点比较器测试完成,错误: %0d ===", err);
$finish;
end
endmodule
=== 浮点比较器测试 ===
PASS 1==1
PASS +0==-0
PASS 1<2
PASS -1<1
PASS 2>1
PASS NaN==NaN (False)
PASS NaN!=NaN (True)
PASS NaN<1 (False)
PASS uno(NaN,1)
PASS -0<=+0
=== 浮点比较器测试完成,错误: 0 ===
✅Verilator验证通过
不同架构对浮点比较的支持差异:
| ISA | 比较指令 | 条件码 | 特殊处理 |
|---|---|---|---|
| x86 | UCOMISS/COMISS | EFLAGS: ZF/PF/CF | COMISS产生#IA异常 |
| ARM | FCMP/FCMP | NZCV标志 | 支持signaling比较 |
| RISC-V | flt/fle/feq | 直接输出布尔 | feq不产生无效异常 |
| MIPS | c.lt.s/c.eq.s | FCCR条件码 | signaling谓词 |
浮点比较可能产生Invalid Operation异常:
练习1:添加Invalid Operation异常信号输出,在signaling比较遇到NaN时置位。
练习2:实现双精度浮点比较器,注意64位的符号翻转策略。
练习3:实现浮点分类指令(fxclass),输出操作数属于哪一类(NaN/∞/normal/denorm/zero)。
练习4:构造+0和-0的完整测试集,验证所有比较谓词对它们的处理。
✅ 理解浮点比较与整数比较的区别
✅ 掌握符号翻转法无符号比较
✅ 实现所有7种IEEE 754比较谓词
✅ 正确处理NaN和±0的比较
✅ 实现浮点min/max选择器
✅ 理解signaling vs quiet比较