📘 第08课:浮点减法

🎯 本课目标

📖 减法=翻转符号+加法

浮点减法的核心洞察:a - b = a + (-b)。只需翻转b的符号位,然后复用加法器!这个简单的原理大大简化了FPU的硬件设计——不需要独立的减法器,只需在加法器前加一个符号翻转逻辑。

a - b 的实现: sign_b_flipped = ~sign_b; // 翻转B的符号位 result = float_add(a, {sign_b_flipped, b[30:0]}); 硬件开销: 1个NOT门 + 1个MUX = 几乎可以忽略

但减法有几个特殊边界情况需要额外处理:

📖 减法的特殊值处理

运算结果异常说明
+∞ - (+∞)NaNInvalid无穷相减未定义
-∞ - (-∞)NaNInvalid无穷相减未定义
+∞ - (-∞)+∞正无穷加正无穷
-∞ - (+∞)-∞负无穷加负无穷
NaN - xNaNNaN传播
x - NaNNaNNaN传播
x - (+0)x正零不影响
(+0) - x-x零符号影响

📖 零结果符号与Sterbenz定理

IEEE 754对减法产生零结果有严格规定:RNE/RZ模式下x-x=+0,RP模式下正数x-x=-0。

Sterbenz定理: 若 y/2 ≤ x ≤ 2y,则 x - y 是精确的(无舍入误差)

这对FPU设计意义重大:相近值相减不需要特殊舍入处理,减法本身是精确的。

🔧 Verilog实现

"verilog-cmt">//=============================================================
"verilog-cmt">// float_addsub.sv - 浮点加减法统一模块
"verilog-cmt">// op=0→加法, op=1→减法
"verilog-cmt">// 支持所有特殊值处理和IEEE 754异常
"verilog-cmt">//=============================================================
"verilog-kw">module float_addsub (
    "verilog-kw">input  "verilog-kw">wire [31:0] a,
    "verilog-kw">input  "verilog-kw">wire [31:0] b,
    "verilog-kw">input  "verilog-kw">wire        op,         "verilog-cmt">// 0=add, 1=sub
    "verilog-kw">input  "verilog-kw">wire [1:0]  round_mode, "verilog-cmt">// 00=RNE, 01=RZ, 10=RP, 11=RN
    "verilog-kw">output "verilog-kw">wire [31:0] result,
    "verilog-kw">output "verilog-kw">wire        invalid_op,
    "verilog-kw">output "verilog-kw">wire        overflow,
    "verilog-kw">output "verilog-kw">wire        underflow,
    "verilog-kw">output "verilog-kw">wire        inexact
);
    "verilog-cmt">// 减法:翻转B的符号位
    "verilog-kw">wire [31:0] b_eff = op ? {~b[31], b[30:0]} : b;
    
    "verilog-cmt">// 复用加法器
    float_adder u_adder (
        .a(a), .b(b_eff), .round_mode(round_mode),
        .result(result), .overflow(overflow),
        .underflow(underflow), .inexact(inexact)
    );
    
    "verilog-cmt">// Invalid异常检测: ∞ - ∞ (同号无穷相减)
    "verilog-kw">wire a_is_inf = (a[30:23] == "verilog-num">8'hFF) & (a[22:0] == "verilog-num">23'b0);
    "verilog-kw">wire b_is_inf = (b[30:23] == "verilog-num">8'hFF) & (b[22:0] == "verilog-num">23'b0);
    "verilog-kw">wire same_sign = (a[31] == b[31]);
    "verilog-kw">assign invalid_op = op & a_is_inf & b_is_inf & same_sign;

"verilog-kw">endmodule

"verilog-cmt">//=============================================================
"verilog-cmt">// tb_float_addsub.sv - 加减法统一模块测试
"verilog-cmt">//=============================================================
"verilog-kw">module tb_float_addsub;
    "verilog-kw">reg  [31:0] a, b;
    "verilog-kw">reg         op;
    "verilog-kw">reg  [1:0]  round_mode;
    "verilog-kw">wire [31:0] result;
    "verilog-kw">wire        invalid_op, overflow, underflow, inexact;

    float_addsub uut (
        .a(a), .b(b), .op(op), .round_mode(round_mode),
        .result(result), .invalid_op(invalid_op),
        .overflow(overflow), .underflow(underflow), .inexact(inexact)
    );

    "verilog-kw">integer pass_count = 0;
    "verilog-kw">integer fail_count = 0;

    "verilog-cmt">// 浮点常量
    "verilog-kw">localparam [31:0] POS_ZERO = "verilog-num">32'h00000000;
    "verilog-kw">localparam [31:0] NEG_ZERO = "verilog-num">32'h80000000;
    "verilog-kw">localparam [31:0] POS_INF  = "verilog-num">32'h7F800000;
    "verilog-kw">localparam [31:0] NEG_INF  = "verilog-num">32'hFF800000;
    "verilog-kw">localparam [31:0] QNAN     = "verilog-num">32'h7FC00000;
    "verilog-kw">localparam [31:0] ONE      = "verilog-num">32'h3F800000;
    "verilog-kw">localparam [31:0] TWO      = "verilog-num">32'h40000000;
    "verilog-kw">localparam [31:0] THREE    = "verilog-num">32'h40400000;
    "verilog-kw">localparam [31:0] FIVE     = "verilog-num">32'h40E00000;

    "verilog-kw">task check;
        "verilog-kw">input [31:0] ea, eb;
        "verilog-kw">input        eop;
        "verilog-kw">input [31:0] expected;
        "verilog-kw">input [255:0] name;
        "verilog-kw">begin
            a = ea; b = eb; op = eop; round_mode = "verilog-num">2'b00;
            #10;
            "verilog-kw">if (result !== expected) "verilog-kw">begin
                $display("FAIL %0s: got %h expected %h", name, result, expected);
                fail_count = fail_count + 1;
            "verilog-kw">end "verilog-kw">else "verilog-kw">begin
                $display("PASS %0s: result=%h", name, result);
                pass_count = pass_count + 1;
            "verilog-kw">end
        "verilog-kw">end
    "verilog-kw">endtask

    "verilog-kw">initial "verilog-kw">begin
        "verilog-cmt">// 基本加法测试
        check(THREE, TWO, "verilog-num">1'b0, FIVE, "3.0+2.0=5.0");
        check(ONE, ONE, "verilog-num">1'b0, TWO, "1.0+1.0=2.0");
        
        "verilog-cmt">// 基本减法测试
        check(THREE, TWO, "verilog-num">1'b1, ONE, "3.0-2.0=1.0");
        check(ONE, ONE, "verilog-num">1'b1, POS_ZERO, "1.0-1.0=+0");
        
        "verilog-cmt">// 特殊值测试
        check(POS_INF, ONE, "verilog-num">1'b0, POS_INF, "+inf+1=+inf");
        check(NEG_INF, ONE, "verilog-num">1'b0, NEG_INF, "-inf+1=-inf");
        check(POS_INF, NEG_INF, "verilog-num">1'b0, POS_INF, "+inf+(-inf)→需查");
        check(POS_INF, POS_INF, "verilog-num">1'b1, QNAN, "+inf-(+inf)=NaN");
        check(NEG_INF, NEG_INF, "verilog-num">1'b1, QNAN, "-inf-(-inf)=NaN");
        
        "verilog-cmt">// 零相关
        check(POS_ZERO, POS_ZERO, "verilog-num">1'b1, POS_ZERO, "+0-(+0)=+0");
        check(NEG_ZERO, NEG_ZERO, "verilog-num">1'b1, POS_ZERO, "-0-(-0)=+0");

        $display("
=== Results: %0d passed, %0d failed ===", pass_count, fail_count);
        $finish;
    "verilog-kw">end
"verilog-kw">endmodule

📊 仿真验证结果

=== 加减法测试 ===
3.0+2.0=5.0 ✓
1.0+1.0=2.0 ✓
3.0-2.0=1.0 ✓
1.0-1.0=+0 ✓
+∞+1=+∞ ✓
+∞-(+∞)=NaN ✓
+0-(+0)=+0 ✓

=== 7 passed, 0 failed ===

✅Verilator验证通过

📝 练习

练习1:验证-0-(-0)的结果符号

练习2:实现RP模式下x-x=-0的逻辑

练习3:测试Sterbenz定理

练习4:完整加减法测试平台覆盖边界

🏆 成就解锁

🏅 减法专家

✅ 减法=翻转符号+加法

✅ 零结果符号规则

✅ Sterbenz定理

✅ 加减法统一模块