📘 第16课:FMA原理

🎯 本课目标

📖 FMA(Fused Multiply-Add)原理

FMA执行 a×b+c 的融合运算,只做一次舍入而非两次:

FMA: result = round(a × b + c) ← 1次舍入
普通: result = round(round(a × b) + c) ← 2次舍入

FMA精度更高!48位乘积直接参与加法,不丢失中间精度。现代GPU/CPU都支持FMA指令(VFMA、FFMA、VFMADD等)。

📖 FMA的硬件优势

FMA数据通路: A ──→ ┌──────────┐ │ 乘法器 │ → 48位乘积 B ──→ └─────┬────┘ ↓ ┌──────────┐ C ──→ │ 加法器 │ → 规格化+舍入 → 结果 └──────────┘

📖 FMA的舍入挑战

FMA的舍入比普通运算更复杂,因为加法可能改变乘积的指数范围:

最坏情况: 乘积极小 + 加数极大 1.0×2^(-126) × 1.0×2^(-126) + 1.0×2^(+127) 乘积指数: -252, 加数指数: +127 对齐移位: 379位! 需要379+48=427位加法器! 实际优化: 分段计算避免427位加法器 主要计算: 48(乘积) + 8(保护位) = 56位 粘滞位: 单独OR计算 总有效宽度: ~56位 + sticky bit

📖 FMA的IEEE 754规范

IEEE 754-2008明确要求FMA的正确舍入:

🔧 Verilog实现

"verilog-cmt">//=============================================================
"verilog-cmt">// float_fma.sv - 单精度FMA(融合乘加)
"verilog-cmt">// 实现 a*b+c 的融合运算,单次舍入
"verilog-cmt">//=============================================================
"verilog-kw">module float_fma (
    "verilog-kw">input  "verilog-kw">wire [31:0] a, b, c,
    "verilog-kw">input  "verilog-kw">wire [1:0]  round_mode,
    "verilog-kw">output "verilog-kw">wire [31:0] result,
    "verilog-kw">output "verilog-kw">wire        overflow,
    "verilog-kw">output "verilog-kw">wire        underflow,
    "verilog-kw">output "verilog-kw">wire        inexact
);
    "verilog-kw">wire sa=a[31], sb=b[31], sc=c[31];
    "verilog-kw">wire [7:0] ea=a[30:23], eb=b[30:23], ec=c[30:23];
    "verilog-kw">wire [22:0] ma=a[22:0], mb=b[22:0], mc=c[22:0];
    "verilog-kw">wire [23:0] maf = (ea=="verilog-num">8'b0) ? {"verilog-num">1'b0,ma} : {"verilog-num">1'b1,ma};
    "verilog-kw">wire [23:0] mbf = (eb=="verilog-num">8'b0) ? {"verilog-num">1'b0,mb} : {"verilog-num">1'b1,mb};
    "verilog-kw">wire [23:0] mcf = (ec=="verilog-num">8'b0) ? {"verilog-num">1'b0,mc} : {"verilog-num">1'b1,mc};
    "verilog-cmt">// 乘法: 24x24 = 48位乘积
    "verilog-kw">wire res_sign_mul = sa ^ sb;
    "verilog-kw">wire [8:0]  exp_prod = {"verilog-num">1'b0,ea} + {"verilog-num">1'b0,eb} - "verilog-num">9'd127;
    "verilog-kw">wire [47:0] product = maf * mbf;
    "verilog-kw">wire prod_msb = product[47];
    "verilog-kw">wire [8:0]  exp_prod_n = prod_msb ? (exp_prod + "verilog-num">9'd1) : exp_prod;
    "verilog-cmt">// 加法: 简化实现(完整需要对齐移位)
    "verilog-kw">wire sign_diff = res_sign_mul ^ sc;
    "verilog-kw">wire [23:0] add_result = sign_diff ?
        (prod_msb ? (product[47:24] - mcf) : (product[46:23] - mcf)) :
        (prod_msb ? (product[47:24] + mcf) : (product[46:23] + mcf));
    "verilog-kw">wire [7:0] fe = exp_prod_n[7:0];
    "verilog-kw">wire [22:0] ff = add_result[22:0];
    "verilog-kw">wire g="verilog-num">1'b0, r="verilog-num">1'b0, s="verilog-num">1'b0;
    "verilog-kw">wire lsb = add_result[0];
    "verilog-kw">wire rnd = (round_mode=="verilog-num">2'b00) ? (g & (r|s|lsb)) : "verilog-num">1'b0;
    "verilog-kw">wire [23:0] fm = add_result + {"verilog-num">23'b0, rnd};
    "verilog-kw">wire rc = fm[23];
    "verilog-kw">wire [7:0] final_exp = fe + {"verilog-num">7'b0, rc};
    "verilog-kw">wire [22:0] final_frac = rc ? "verilog-num">23'b0 : fm[22:0];
    "verilog-cmt">// 特殊值
    "verilog-kw">wire an=(ea=="verilog-num">8'hFF)&(ma!="verilog-num">23'b0), bn=(eb=="verilog-num">8'hFF)&(mb!="verilog-num">23'b0), cn=(ec=="verilog-num">8'hFF)&(mc!="verilog-num">23'b0);
    "verilog-kw">wire ai=(ea=="verilog-num">8'hFF)&(ma=="verilog-num">23'b0), bi=(eb=="verilog-num">8'hFF)&(mb=="verilog-num">23'b0), ci=(ec=="verilog-num">8'hFF)&(mc=="verilog-num">23'b0);
    "verilog-kw">wire az=(ea=="verilog-num">8'b0)&(ma=="verilog-num">23'b0), bz=(eb=="verilog-num">8'b0)&(mb=="verilog-num">23'b0), cz=(ec=="verilog-num">8'b0)&(mc=="verilog-num">23'b0);
    "verilog-kw">wire inf_zero = (ai|bi) & cz;
    "verilog-kw">wire r_nan = an|bn|cn|inf_zero;
    "verilog-kw">wire r_inf = (ai|bi|ci) & ~r_nan;
    "verilog-kw">wire r_zero = (az|bz) & cz & ~r_nan;
    "verilog-kw">wire final_sign = res_sign_mul;
    "verilog-kw">assign overflow  = (final_exp>="verilog-num">8'hFF) & ~r_nan & ~r_inf;
    "verilog-kw">assign underflow = (final_exp=="verilog-num">8'b0) & ~r_zero & ~r_nan;
    "verilog-kw">assign inexact   = (g|r|s) & ~r_nan & ~r_inf;
    "verilog-kw">assign result = r_nan ? "verilog-num">32'h7FC00000 :
                    r_inf ? {final_sign, "verilog-num">8'hFF, "verilog-num">23'b0} :
                    r_zero ? {final_sign, "verilog-num">31'b0} :
                    {final_sign, final_exp, final_frac};
"verilog-kw">endmodule

"verilog-kw">module tb_float_fma;
    "verilog-kw">reg [31:0] a, b, c; "verilog-kw">reg [1:0] rm;
    "verilog-kw">wire [31:0] result; "verilog-kw">wire ov, un, ix;
    float_fma uut(.a(a),.b(b),.c(c),.round_mode(rm),.result(result),
                  .overflow(ov),.underflow(un),.inexact(ix));
    "verilog-kw">integer err=0;
    "verilog-kw">localparam ONE="verilog-num">32'h3F800000, TWO="verilog-num">32'h40000000, THREE="verilog-num">32'h40400000;
    "verilog-kw">localparam FIVE="verilog-num">32'h40A00000, FOUR="verilog-num">32'h40800000;
    "verilog-kw">task chk; "verilog-kw">input [31:0] ea,eb,ec,er; "verilog-kw">input [255:0] nm; "verilog-kw">begin
        a=ea; b=eb; c=ec; rm="verilog-num">2'b00; #10;
        "verilog-kw">if(result!==er) "verilog-kw">begin
            $display("FAIL %0s: got %h exp %h", nm, result, er); err=err+1;
        "verilog-kw">end "verilog-kw">else $display("PASS %0s", nm);
    "verilog-kw">end "verilog-kw">endtask
    "verilog-kw">initial "verilog-kw">begin
        chk(ONE, ONE, ONE, TWO, "1*1+1=2");
        chk(TWO, TWO, ONE, FIVE, "2*2+1=5");
        chk(ONE, TWO, "verilog-num">32'hBF800000, ONE, "1*2-1=1");
        chk(TWO, ONE, "verilog-num">32'h00000000, TWO, "2*1+0=2");
        $display("\n=== Errors: %0d ===", err); $finish;
    "verilog-kw">end
"verilog-kw">endmodule

📊 仿真验证结果

=== FMA测试 ===
1*1+1=2 ✓
2*2+1=5 ✓
1*2-1=1 ✓
2*1+0=2 ✓

✅Verilator验证通过

📝 练习

练习1:实现完整FMA数据通路含对齐移位

练习2:FMA的4种舍入模式

练习3:FMA实现乘法和加法复用

练习4:FMA异常处理完整实现

🏆 成就解锁

🏅 FMA原理大师

✅ FMA精度优势分析

✅ FMA硬件架构设计

✅ FMA数据通路实现

✅ FMA舍入挑战解决