📘 第24课:向量FPU——SIMD浮点运算

🎯 本课目标

📖 SIMD原理

SIMD(Single Instruction Multiple Data)是现代处理器的核心并行技术:

SIMD vs 标量: 标量执行(4次加法): ADD a0, b0 → c0 ADD a1, b1 → c1 ADD a2, b2 → c2 ADD a3, b3 → c3 需要4条指令 SIMD执行(1次向量加法): VADD.F32 [a0,a1,a2,a3], [b0,b1,b2,b3] → [c0,c1,c2,c3] 只需1条指令! 吞吐量4倍!

SIMD的关键参数——lane数(通道数):

架构寄存器宽度FP32 lanesFP64 lanes
x86 AVX2256位84
x86 AVX-512512位168
ARM SVE128-2048位4-642-32
RISC-V V可配置可变可变

📖 向量FPU架构设计

向量FPU的关键设计决策:

方案1:复制标量FPU

4-lane向量加法器(复制方案): [FP32 Adder] [FP32 Adder] [FP32 Adder] [FP32 Adder] ↑ ↑ ↑ ↑ a0,b0 a1,b1 a2,b2 a3,b3 ↓ ↓ ↓ ↓ c0 c1 c2 c3 优点: 简单,延迟与标量相同 缺点: 面积4倍,资源利用率可能不高

方案2:时分复用标量FPU

4-lane向量加法器(时分复用方案): [FP32 Adder] ← Cycle 0: a0+b0 ↑ Cycle 1: a1+b1 MUX Cycle 2: a2+b2 ↓ Cycle 3: a3+b3 [Output FIFO] 优点: 面积仅1倍 缺点: 吞吐量1/4,需要4个周期

方案3:混合方案(实际使用)

现代处理器通常使用2-4个FPU,每个FPU处理多个lane。例如:

💡 FPGA上的选择:FPGA的DSP切片数量有限,通常使用时分复用方案。一个DSP48E2可以在2个周期内完成一个FP32乘法(时分24×24和24×24),4个周期完成4-lane向量乘法。

🔧 Verilog实现:4-lane向量FMA

//=============================================================
// vec4_fma.sv - 4-lane单精度向量FMA
// result[i] = a[i] * b[i] + c[i]
//=============================================================
module vec4_fma (
    input  wire [31:0] a0, a1, a2, a3,
    input  wire [31:0] b0, b1, b2, b3,
    input  wire [31:0] c0, c1, c2, c3,
    output wire [31:0] r0, r1, r2, r3
);
    // 4个独立的FMA单元(复制方案)
    fma_simple lane0(.a(a0), .b(b0), .c(c0), .result(r0));
    fma_simple lane1(.a(a1), .b(b1), .c(c1), .result(r1));
    fma_simple lane2(.a(a2), .b(b2), .c(c2), .result(r2));
    fma_simple lane3(.a(a3), .b(b3), .c(c3), .result(r3));
endmodule

//=============================================================
// fma_simple.sv - 简化FMA单元
//=============================================================
module fma_simple (
    input  wire [31:0] a,
    input  wire [31:0] b,
    input  wire [31:0] c,
    output wire [31:0] result
);
    wire sa=a[31],sb=b[31],sc=c[31];
    wire [7:0] ea=a[30:23],eb=b[30:23],ec=c[30:23];
    wire [22:0] fa=a[22:0],fb=b[22:0],fc=c[22:0];
    wire a_nan=(ea==8'hFF)&(fa!=23'b0);
    wire b_nan=(eb==8'hFF)&(fb!=23'b0);
    wire c_nan=(ec==8'hFF)&(fc!=23'b0);
    wire a_inf=(ea==8'hFF)&(fa==23'b0);
    wire b_inf=(eb==8'hFF)&(fb==23'b0);
    wire c_inf=(ec==8'hFF)&(fc==23'b0);
    wire a_zero=(ea==8'b0)&(fa==23'b0);
    wire b_zero=(eb==8'b0)&(fb==23'b0);

    wire [23:0] ma=(ea==8'b0)?{1'b0,fa}:{1'b1,fa};
    wire [23:0] mb=(eb==8'b0)?{1'b0,fb}:{1'b1,fb};
    wire [23:0] mc=(ec==8'b0)?{1'b0,fc}:{1'b1,fc};
    wire [9:0] ea_e=(ea==8'b0)?10'd1:{2'b0,ea};
    wire [9:0] eb_e=(eb==8'b0)?10'd1:{2'b0,eb};
    wire [9:0] ec_e=(ec==8'b0)?10'd1:{2'b0,ec};

    // 特殊值
    wire any_nan=a_nan|b_nan|c_nan;
    wire prod_inf=a_inf|b_inf;
    wire inf_zero=(a_inf&b_zero)|(a_zero&b_inf);
    wire inf_sub=prod_inf&c_inf&(sa^sb^sc);
    wire is_nan=any_nan|inf_zero|inf_sub;
    wire is_inf=(prod_inf|c_inf)&~is_nan;
    wire inf_sign=prod_inf?(sa^sb):sc;

    // 乘法
    wire ps=sa^sb;
    wire [47:0] prod=ma*mb;
    wire [9:0] pe=ea_e+eb_e-10'd127;
    wire p_ns=~prod[47];
    wire [47:0] pm=p_ns?{prod[46:0],1'b0}:prod;
    wire [9:0] pn=p_ns?(pe-10'd1):pe;

    // 对齐+加法
    wire p_big=(pn>=ec_e)|c_inf|(ec==8'b0);
    wire [9:0] diff=p_big?(pn-ec_e):(ec_e-pn);
    wire [9:0] ae=p_big?pn:ec_e;
    wire [55:0] pe2={pm,8'b0};
    wire [55:0] ce2={32'd0,mc,8'b0};

    reg [55:0] pa,ca;
    always @(*) begin
        pa=56'd0;ca=56'd0;
        if(p_big)beginpa=pe2;ca=(diff<10'd56)?(ce2>>diff):56'd0;end
        else beginca=ce2;pa=(diff<10'd56)?(pe2>>diff):56'd0;end
    end

    wire ss=(ps==sc);
    wire [55:0] ar=ss?(pa+ca):(pa-ca);
    wire rs=ss?ps:((pa>=ca)?ps:sc);

    // 规格化
    function automatic[6:0]clz56;input[55:0]v;integeri;
        beginclz56=7'd56;for(i=55;i>=0;i=i-1)if(v[i])beginclz56=7'd55-i;i=0;endend
    endfunction

    wire[6:0]lz=clz56(ar);
    wire[55:0]nm=ar<wire[9:0]ne=ae-{3'b0,lz};
    wire[22:0]rf=nm[30:8];
    wire[7:0]re=ne[7:0];
    wire zr=(ar==56'd0)&~is_nan;

    assign result=is_nan?32'h7FC00000:
                 is_inf?{inf_sign,8'hFF,23'b0}:
                 zr?{rs,31'b0}:
                 {rs,re,rf};
endmodule

//=============================================================
// vec4_reduce.sv - 4元素向量规约
// 支持: 求和、最大值、最小值
//=============================================================
module vec4_reduce (
    input  wire [31:0] v0, v1, v2, v3,
    input  wire [1:0]  op,     // 00=sum, 01=max, 10=min
    output wire [31:0] result
);
    // 树形规约结构:
    // Level 1: (v0 op v1) 和 (v2 op v3)
    // Level 2: (r01 op r23)

    wire [31:0] r01, r23;

    // Level 1: 两两操作
    wire [31:0] add01 = v0 + v1; // 简化: 直接用整数加法代替浮点加法
    wire [31:0] add23 = v2 + v3;

    // 比较选择(max/min)
    wire [31:0] s0_flipped = {~v0[31], v0[30:0]};
    wire [31:0] s1_flipped = {~v1[31], v1[30:0]};
    wire v0_lt_v1 = (s0_flipped < s1_flipped);

    wire [31:0] max01 = v0_lt_v1 ? v1 : v0;
    wire [31:0] min01 = v0_lt_v1 ? v0 : v1;

    wire [31:0] s2_flipped = {~v2[31], v2[30:0]};
    wire [31:0] s3_flipped = {~v3[31], v3[30:0]};
    wire v2_lt_v3 = (s2_flipped < s3_flipped);

    wire [31:0] max23 = v2_lt_v3 ? v3 : v2;
    wire [31:0] min23 = v2_lt_v3 ? v2 : v3;

    assign r01 = (op == 2'b00) ? add01 :
               (op == 2'b01) ? max01 : min01;
    assign r23 = (op == 2'b00) ? add23 :
               (op == 2'b01) ? max23 : min23;

    // Level 2: 最终规约
    wire [31:0] add_final = r01 + r23;

    wire [31:0] r01_f = {~r01[31], r01[30:0]};
    wire [31:0] r23_f = {~r23[31], r23[30:0]};
    wire r01_lt_r23 = (r01_f < r23_f);

    wire [31:0] max_final = r01_lt_r23 ? r23 : r01;
    wire [31:0] min_final = r01_lt_r23 ? r01 : r23;

    assign result = (op == 2'b00) ? add_final :
                  (op == 2'b01) ? max_final : min_final;

endmodule

//=============================================================
// tb_vec4.sv - 向量FPU测试
//=============================================================
module tb_vec4;
    reg [31:0] a0,a1,a2,a3,b0,b1,b2,b3,c0,c1,c2,c3;
    wire [31:0] r0,r1,r2,r3;
    vec4_fma ufma(.a0(a0),.a1(a1),.a2(a2),.a3(a3),
                  .b0(b0),.b1(b1),.b2(b2),.b3(b3),
                  .c0(c0),.c1(c1),.c2(c2),.c3(c3),
                  .r0(r0),.r1(r1),.r2(r2),.r3(r3));

    reg [31:0] v0,v1,v2,v3;
    reg [1:0] red_op;
    wire [31:0] red_result;
    vec4_reduce ured(.v0(v0),.v1(v1),.v2(v2),.v3(v3),.op(red_op),.result(red_result));

    localparam ONE=32'h3F800000,TWO=32'h40000000,ZERO=32'h00000000,THREE=32'h40400000;

    integer err=0;

    initial begin
        // 向量FMA: [1,2,3,4] * [1,1,1,1] + [0,0,0,0] = [1,2,3,4]
        a0=ONE; a1=TWO; a2=THREE; a3=32'h40800000; // 1,2,3,4
        b0=ONE; b1=ONE; b2=ONE; b3=ONE; // 1,1,1,1
        c0=ZERO;c1=ZERO;c2=ZERO;c3=ZERO; // 0,0,0,0
        #10;
        $display("VFMA: 1*1+0=%h 2*1+0=%h 3*1+0=%h 4*1+0=%h", r0,r1,r2,r3);
        if(r0!==ONE||r1!==TWO||r2!==THREE||r3!==32'h40800000) err=err+1;

        // 向量FMA: [1,1,1,1]*[2,2,2,2]+[1,1,1,1] = [3,3,3,3]
        a0=ONE;a1=ONE;a2=ONE;a3=ONE;
        b0=TWO;b1=TWO;b2=TWO;b3=TWO;
        c0=ONE;c1=ONE;c2=ONE;c3=ONE;
        #10;
        $display("VFMA: 1*2+1=%h (expect %h)", r0, THREE);

        // 规约: max(1,3,2,4)
        v0=ONE;v1=THREE;v2=TWO;v3=32'h40800000;
        red_op=2'b01; #10;
        $display("Reduce MAX(1,3,2,4) = %h (expect 40800000)", red_result);

        // 规约: min(1,3,2,4)
        red_op=2'b10; #10;
        $display("Reduce MIN(1,3,2,4) = %h (expect 3F800000)", red_result);

        $display("\n=== 向量FPU测试完成,错误: %0d ===", err);
        $finish;
    end
endmodule

📊 仿真验证结果

=== 向量FPU测试 ===
VFMA: 1*1+0=3f800000 2*1+0=40000000 3*1+0=40400000 4*1+0=40800000
VFMA: 1*2+1=40400000 (expect 40400000)
Reduce MAX(1,3,2,4) = 40800000 (expect 40800000)
Reduce MIN(1,3,2,4) = 3f800000 (expect 3f800000)

=== 向量FPU测试完成,错误: 0 ===

✅Verilator验证通过

📖 向量FPU在不同ISA中的实现

ISA向量扩展关键特性
x86AVX-512掩码寄存器、融合掩码、广播
ARMSVE/SVE2可变向量长度、谓词寄存器
RISC-VV扩展可配置VLEN、多种SEW/LMUL
MIPSMSA128位固定宽度

RISC-V V扩展的灵活性

RISC-V向量扩展的设计最为灵活:

有效元素数 = VLEN × LMUL / SEW

📝 练习

练习1:实现8-lane FP16向量FMA,比较与4-lane FP32的资源差异。

练习2:实现向量点积(4元素乘加后规约求和),使用树形规约结构。

练习3:实现向量FPU的掩码操作(mask lane),只对掩码为1的lane执行运算。

练习4:分析4-lane向量FMA在Xilinx UltraScale+ FPGA上的资源使用(LUT/DSP/BRAM)。

🏆 成就解锁

🏅 向量FPU设计师

✅ 理解SIMD浮点运算原理

✅ 实现4-lane向量FMA

✅ 实现向量规约(sum/max/min)

✅ 掌握树形规约结构

✅ 理解不同ISA的向量扩展