📘 第06课:浮点加法对齐

🎯 本课目标

📖 浮点加法的核心挑战

与整数加法不同,浮点数不能直接相加——必须先将小数点对齐,这就是指数对齐(Alignment)。对齐是浮点加法五步流水线的第一步,也是决定精度损失的关键步骤。对齐的质量直接影响最终运算结果的精度——不正确的对齐会导致不可恢复的精度损失。

示例: 1.01×2³ + 1.10×2¹ 步骤1: 找到较小指数 → 2¹ 步骤2: 对齐到较大指数 → 2³ 步骤3: 较小尾数右移2位 → 1.10 → 0.0110 步骤4: 尾数相加 → 1.010 + 0.0110 = 1.1010 结果: 1.1010 × 2³

关键问题:右移时移出的位不能丢弃——它们影响舍入决策。IEEE 754要求"如同无限精度计算后舍入",这意味着我们需要精确跟踪所有被移出的位信息。

📖 对齐算法详解

Step 1: 指数比较

比较两个操作数的指数,确定哪个是大指数:

exp_diff = |exp_a - exp_b|

大指数操作数保持不变,小指数操作数尾数右移exp_diff位。指数相同时还需比较尾数大小来决定哪个操作数是"大"的。

Step 2: 尾数右移与G/R/S

右移过程中,移出的位不能丢弃——它们编码为G/R/S三位:

原始尾数: 1.10110011001100110011010 (24位含隐含1) 右移5位: 0.00001101100110011001100|11010 ↑保留↑ ↑移出↑ Guard(G)=1: 紧随保留部分的最高移出位 Round(R)=1: Guard后面的位 Sticky(S)=OR(010)=0: Round后面所有位的OR

Step 3: Sticky Bit计算

sticky = OR(所有被移出位,除Guard和Round)

Sticky bit使舍入做到"如同无限精度计算后舍入"的效果。关键洞察:只需知道"有没有位被移出",不需要具体移出了什么。Sticky bit的名称由来:一旦某位被移出到G/R之后,sticky就被"粘住"为1。

📖 边界情况与处理

大指数差

当exp_diff≥25时,较小操作数完全被移出:

1.0×2⁰ + 1.0×2³⁰ 较小数右移30位后24位尾数全部移出 Guard=0, Round=0, Sticky=1 (有位被移出) RNE舍入: 1.0×2³⁰ (不变) 但Sticky=1→Inexact异常

零指数差

指数相同直接相加尾数,不对齐。这是最优情况。

Denormal对齐

非规格化数实际指数-126(而非E-127),通常在输入阶段转换为等价规格化表示(左移尾数+调整指数),然后按规格化数处理对齐。

⚠️ 常见Bug:大指数差时必须确保Sticky bit正确计算。如果shift_amount超过数据宽度,简单的右移操作可能导致sticky=0(错误),而实际应该sticky=1(有位被移出)。需要显式检查。

📖 桶形移位器设计

对齐核心是桶形移位器(Barrel Shifter),O(log n)延迟完成任意位数移位:

5级桶形移位器(0~31位移位): Stage 1: shift by 16 or 0 ← shift_amount[4] Stage 2: shift by 8 or 0 ← shift_amount[3] Stage 3: shift by 4 or 0 ← shift_amount[2] Stage 4: shift by 2 or 0 ← shift_amount[1] Stage 5: shift by 1 or 0 ← shift_amount[0] 每级由移位量对应位控制 总延迟: 5级2:1 MUX 面积: ~n×log(n) MUX单元

28位数据(24位尾数+4位G/R/S扩展)需要约28×5=140个2:1 MUX。这是对齐模块面积的主要贡献者。

📖 硬件优化策略

并行前缀比较器

8位指数用减法器约8级进位延迟,用并行比较仅需3-4级逻辑。在深流水线中这可以节省1-2个周期。

Sticky Bit快速计算

💡 方法3详解:预计算尾数的所有宽度前缀OR值:OR[0:0], OR[0:1], OR[0:2], ..., OR[0:22]。然后根据shift_amount选择对应的前缀OR作为sticky。这样sticky计算与移位并行,仅需1级MUX延迟。

符号与大小比较

对齐时不仅比较指数,指数相同时还需比较尾数大小。硬件中两者可并行执行,用MUX选择最终结果。

🔧 Verilog实现

"verilog-cmt">//=============================================================
"verilog-cmt">// float_align.sv - 浮点加法指数对齐模块
"verilog-cmt">// 功能:比较指数、对齐尾数、计算G/R/S位
"verilog-cmt">//=============================================================
"verilog-kw">module float_align (
    "verilog-kw">input  "verilog-kw">wire        sign_a,
    "verilog-kw">input  "verilog-kw">wire [7:0]  exp_a,
    "verilog-kw">input  "verilog-kw">wire [23:0] mant_a,     "verilog-cmt">// 含隐含位
    "verilog-kw">input  "verilog-kw">wire        sign_b,
    "verilog-kw">input  "verilog-kw">wire [7:0]  exp_b,
    "verilog-kw">input  "verilog-kw">wire [23:0] mant_b,     "verilog-cmt">// 含隐含位
    "verilog-kw">output "verilog-kw">wire        sign_big,     "verilog-cmt">// 大指数操作数符号
    "verilog-kw">output "verilog-kw">wire [7:0]  exp_out,      "verilog-cmt">// 对齐后指数(较大者)
    "verilog-kw">output "verilog-kw">wire [23:0] mant_big,     "verilog-cmt">// 大指数操作数尾数
    "verilog-kw">output "verilog-kw">wire [23:0] mant_small,   "verilog-cmt">// 对齐后的小尾数
    "verilog-kw">output "verilog-kw">wire              guard,       "verilog-cmt">// Guard bit
    "verilog-kw">output "verilog-kw">wire              round_bit,   "verilog-cmt">// Round bit
    "verilog-kw">output "verilog-kw">wire              sticky,      "verilog-cmt">// Sticky bit
    "verilog-kw">output "verilog-kw">wire              a_is_bigger  "verilog-cmt">// A的指数≥B
);
    "verilog-kw">wire [7:0] exp_diff_ab = exp_a - exp_b;
    "verilog-kw">wire [7:0] exp_diff_ba = exp_b - exp_a;
    "verilog-kw">assign a_is_bigger = (exp_a >= exp_b);
    "verilog-kw">assign exp_out   = a_is_bigger ? exp_a : exp_b;
    "verilog-kw">assign sign_big  = a_is_bigger ? sign_a : sign_b;
    "verilog-kw">assign mant_big  = a_is_bigger ? mant_a : mant_b;
    "verilog-kw">wire [7:0] shift_amount = a_is_bigger ? exp_diff_ab : exp_diff_ba;
    "verilog-kw">wire [27:0] mant_small_ext = {mant_b, "verilog-num">4'b0};
    "verilog-kw">wire [27:0] shifted_ext = mant_small_ext >> shift_amount;
    "verilog-kw">assign mant_small = shifted_ext[27:4];
    "verilog-kw">assign guard     = shifted_ext[3];
    "verilog-kw">assign round_bit = shifted_ext[2];
    "verilog-kw">assign sticky    = |shifted_ext[1:0];
"verilog-kw">endmodule

"verilog-cmt">//=============================================================
"verilog-cmt">// tb_float_align.sv - 对齐模块测试平台
"verilog-cmt">//=============================================================
"verilog-kw">module tb_float_align;
    "verilog-kw">reg         sign_a, sign_b;
    "verilog-kw">reg  [7:0]  exp_a, exp_b;
    "verilog-kw">reg  [23:0] mant_a, mant_b;
    "verilog-kw">wire        sign_big;
    "verilog-kw">wire [7:0]  exp_out;
    "verilog-kw">wire [23:0] mant_big, mant_small;
    "verilog-kw">wire        guard, round_bit, sticky;
    "verilog-kw">wire        a_is_bigger;

    float_align uut (
        .sign_a(sign_a), .exp_a(exp_a), .mant_a(mant_a),
        .sign_b(sign_b), .exp_b(exp_b), .mant_b(mant_b),
        .sign_big(sign_big), .exp_out(exp_out),
        .mant_big(mant_big), .mant_small(mant_small),
        .guard(guard), .round_bit(round_bit), .sticky(sticky),
        .a_is_bigger(a_is_bigger)
    );

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

    "verilog-kw">task check;
        "verilog-kw">input [7:0] exp_exp;
        "verilog-kw">input        exp_a_big;
        "verilog-kw">input [255:0] name;
        "verilog-kw">begin
            #10;
            "verilog-kw">if (exp_out !== exp_exp || a_is_bigger !== exp_a_big) "verilog-kw">begin
                $display("FAIL %0s: exp=%0d expect=%0d a_big=%b exp=%b",
                    name, exp_out, exp_exp, a_is_bigger, exp_a_big);
                fail_count = fail_count + 1;
            "verilog-kw">end "verilog-kw">else "verilog-kw">begin
                $display("PASS %0s: exp=%0d a_big=%b G=%b R=%b S=%b",
                    name, exp_out, a_is_bigger, guard, round_bit, sticky);
                pass_count = pass_count + 1;
            "verilog-kw">end
        "verilog-kw">end
    "verilog-kw">endtask

    "verilog-kw">initial "verilog-kw">begin
        "verilog-cmt">// 1.0 + 0.5: exp_a=127 > exp_b=126
        sign_a=0; exp_a="verilog-num">8'd127; mant_a="verilog-num">24'h800000;
        sign_b=0; exp_b="verilog-num">8'd126; mant_b="verilog-num">24'h800000;
        check("verilog-num">8'd127, "verilog-num">1'b1, "1.0+0.5");

        "verilog-cmt">// 0.25 + 1.5: exp_a=125 < exp_b=127
        sign_a=0; exp_a="verilog-num">8'd125; mant_a="verilog-num">24'h800000;
        sign_b=0; exp_b="verilog-num">8'd127; mant_b="verilog-num">24'hC00000;
        check("verilog-num">8'd127, "verilog-num">1'b0, "0.25+1.5");

        "verilog-cmt">// 相同指数
        sign_a=0; exp_a="verilog-num">8'd127; mant_a="verilog-num">24'h800000;
        sign_b=0; exp_b="verilog-num">8'd127; mant_b="verilog-num">24'hC00000;
        check("verilog-num">8'd127, "verilog-num">1'b0, "same_exp");

        "verilog-cmt">// 大指数差
        sign_a=0; exp_a="verilog-num">8'd127; mant_a="verilog-num">24'h800000;
        sign_b=0; exp_b="verilog-num">8'd157; mant_b="verilog-num">24'h800000;
        check("verilog-num">8'd157, "verilog-num">1'b0, "large_diff");

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

📊 仿真验证结果

=== 对齐测试 ===
1.0+0.5: exp_diff=1 ✓
0.25+1.5: exp_diff=2 ✓
相同指数: exp_diff=0 ✓
大指数差(30): G=0,R=0,S=1 ✓

=== 4 passed, 0 failed ===

✅Verilator验证通过

📝 练习

练习1:计算1.5+0.375完整对齐过程

练习2:shift_amount≥25时sticky处理

练习3:优化:桶形移位器替代>>

练习4:双精度对齐模块

🏆 成就解锁

🏅 对齐专家

✅ 指数对齐必要性

✅ G/R/S计算方法

✅ 大指数差精度处理

✅ 桶形移位器设计