与整数加法不同,浮点数不能直接相加——必须先将小数点对齐,这就是指数对齐(Alignment)。对齐是浮点加法五步流水线的第一步,也是决定精度损失的关键步骤。对齐的质量直接影响最终运算结果的精度——不正确的对齐会导致不可恢复的精度损失。
关键问题:右移时移出的位不能丢弃——它们影响舍入决策。IEEE 754要求"如同无限精度计算后舍入",这意味着我们需要精确跟踪所有被移出的位信息。
比较两个操作数的指数,确定哪个是大指数:
大指数操作数保持不变,小指数操作数尾数右移exp_diff位。指数相同时还需比较尾数大小来决定哪个操作数是"大"的。
右移过程中,移出的位不能丢弃——它们编码为G/R/S三位:
Sticky bit使舍入做到"如同无限精度计算后舍入"的效果。关键洞察:只需知道"有没有位被移出",不需要具体移出了什么。Sticky bit的名称由来:一旦某位被移出到G/R之后,sticky就被"粘住"为1。
当exp_diff≥25时,较小操作数完全被移出:
指数相同直接相加尾数,不对齐。这是最优情况。
非规格化数实际指数-126(而非E-127),通常在输入阶段转换为等价规格化表示(左移尾数+调整指数),然后按规格化数处理对齐。
对齐核心是桶形移位器(Barrel Shifter),O(log n)延迟完成任意位数移位:
28位数据(24位尾数+4位G/R/S扩展)需要约28×5=140个2:1 MUX。这是对齐模块面积的主要贡献者。
8位指数用减法器约8级进位延迟,用并行比较仅需3-4级逻辑。在深流水线中这可以节省1-2个周期。
对齐时不仅比较指数,指数相同时还需比较尾数大小。硬件中两者可并行执行,用MUX选择最终结果。
"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计算方法
✅ 大指数差精度处理
✅ 桶形移位器设计