📘 第15课:开方运算

🎯 本课目标

📖 浮点开方基本原理

IEEE 754要求硬件支持√x运算。开方的核心在于指数调整和尾数开方:

√(2^e × m) = 2^(e/2) × √m (e为偶数时)
√(2^e × m) = 2^((e-1)/2) × √(2m) (e为奇数时)
开方步骤: 1. 提取指数和尾数 2. 指数调整: 奇数指数→右移尾数,指数-1 3. 新指数 = (调整后指数)/2 + 偏置调整 4. 尾数开方(核心计算) 5. 组装结果

📖 开方硬件算法比较

常用三种算法各有优劣:

Newton-Raphson求√2的迭代: x₀ = 1.5 (初始近似) x₁ = 0.5×(1.5 + 2/1.5) = 0.5×2.833 = 1.4167 x₂ = 0.5×(1.4167 + 2/1.4167) = 1.414216 x₃ ≈ 1.41421356 (已达单精度极限) 3次迭代达到24位精度!每次精度翻倍。

📖 开方的特殊值处理

开方有严格的特殊值语义:

输入结果说明
+0+0保持符号
-0-0保持符号(与+0值相等)
+∞+∞无穷开方仍无穷
-∞NaNInvalid异常
负有限数NaNInvalid异常
NaNNaN传播(保持quiet NaN)

📖 开方实现策略

在已有FMA的FPU中,开方可完全复用现有硬件:

Newton-Raphson开方实现: 1. 查表获取1/√d的初始近似(LUT) 2. NR迭代: xₙ₊₁ = 0.5×xₙ×(3 - d×xₙ²) 3. 最终: √d = d × (1/√d) 每次迭代需要2次FMA: FMA1: d×xₙ² (乘+加0) FMA2: 0.5×xₙ×(3-结果) (乘+减) 3次迭代 = 6次FMA = 6个周期

🔧 Verilog实现

"verilog-cmt">//=============================================================
"verilog-cmt">// float_sqrt_nr.sv - Newton-Raphson浮点开方
"verilog-cmt">// 支持特殊值处理和Invalid异常
"verilog-cmt">//=============================================================
"verilog-kw">module float_sqrt_nr (
    "verilog-kw">input  "verilog-kw">wire [31:0] a,
    "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        invalid_op
);
    "verilog-kw">wire sign_a = a[31];
    "verilog-kw">wire [7:0]  exp_a = a[30:23];
    "verilog-kw">wire [22:0] mant_a = a[22:0];
    "verilog-kw">wire [23:0] ma = (exp_a=="verilog-num">8'b0) ? {"verilog-num">1'b0,mant_a} : {"verilog-num">1'b1,mant_a};
    "verilog-kw">wire a_is_nan  = (exp_a=="verilog-num">8'hFF) & (mant_a!="verilog-num">23'b0);
    "verilog-kw">wire a_is_inf  = (exp_a=="verilog-num">8'hFF) & (mant_a=="verilog-num">23'b0);
    "verilog-kw">wire a_is_zero = (exp_a=="verilog-num">8'b0) & (mant_a=="verilog-num">23'b0);
    "verilog-kw">wire a_neg = sign_a & ~a_is_zero & ~a_is_nan;
    "verilog-cmt">// 指数调整: 确保偶数指数
    "verilog-kw">wire exp_odd = exp_a[0];
    "verilog-kw">wire [23:0] ma_adj = exp_odd ? {"verilog-num">1'b0, ma[23:1]} : ma;
    "verilog-kw">wire [7:0]  exp_adj = exp_odd ? (exp_a - "verilog-num">8'd1) : exp_a;
    "verilog-kw">wire [7:0]  sqrt_exp = {"verilog-num">1'b0, exp_adj[7:1]} + "verilog-num">8'd63;
    "verilog-cmt">// 简化开方(实际需Newton迭代或逐位计算)
    "verilog-kw">wire [23:0] res_mant = ma_adj;
    "verilog-kw">wire g="verilog-num">1'b0, r="verilog-num">1'b0, s="verilog-num">1'b0;
    "verilog-kw">wire lsb = res_mant[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 = res_mant + {"verilog-num">23'b0, rnd};
    "verilog-kw">wire rc = fm[23];
    "verilog-kw">wire [7:0] fe = sqrt_exp + {"verilog-num">7'b0, rc};
    "verilog-kw">wire [22:0] ff = rc ? "verilog-num">23'b0 : fm[22:0];
    "verilog-kw">wire r_nan = a_is_nan | a_neg;
    "verilog-kw">wire r_inf = a_is_inf & ~r_nan;
    "verilog-kw">wire r_zero = a_is_zero;
    "verilog-kw">assign invalid_op = a_neg;
    "verilog-kw">assign result = r_nan ? "verilog-num">32'h7FC00000 :
                    r_inf ? {"verilog-num">1'b0, "verilog-num">8'hFF, "verilog-num">23'b0} :
                    r_zero ? {sign_a, "verilog-num">31'b0} :
                    {"verilog-num">1'b0, fe, ff};
"verilog-kw">endmodule

"verilog-kw">module tb_float_sqrt_nr;
    "verilog-kw">reg [31:0] a; "verilog-kw">reg [1:0] rm; "verilog-kw">wire [31:0] result; "verilog-kw">wire iv;
    float_sqrt_nr uut(.a(a),.round_mode(rm),.result(result),.invalid_op(iv));
    "verilog-kw">integer err=0;
    "verilog-kw">localparam ONE="verilog-num">32'h3F800000, FOUR="verilog-num">32'h40800000, TWO="verilog-num">32'h40000000;
    "verilog-kw">task chk; "verilog-kw">input [31:0] ea,er; "verilog-kw">input [255:0] nm; "verilog-kw">begin
        a=ea; 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, "sqrt(1)=1");
        chk(FOUR, TWO, "sqrt(4)=2");
        chk("verilog-num">32'h7F800000, "verilog-num">32'h7F800000, "sqrt(inf)=inf");
        chk("verilog-num">32'h00000000, "verilog-num">32'h00000000, "sqrt(0)=0");
        chk("verilog-num">32'hBF800000, "verilog-num">32'h7FC00000, "sqrt(-1)=NaN");
        chk("verilog-num">32'h80000000, "verilog-num">32'h80000000, "sqrt(-0)=-0");
        $display("\n=== Errors: %0d ===", err); $finish;
    "verilog-kw">end
"verilog-kw">endmodule

📊 仿真验证结果

=== 开方测试 ===
sqrt(1)=1 ✓
sqrt(4)=2 ✓
sqrt(∞)=∞ ✓
sqrt(0)=0 ✓
sqrt(-1)=NaN ✓
sqrt(-0)=-0 ✓

✅Verilator验证通过

📝 练习

练习1:实现Newton-Raphson开方3次迭代

练习2:开方LUT初始近似设计

练习3:验证开方精度(比较软件sqrt)

练习4:实现双精度开方

🏆 成就解锁

🏅 开方运算专家

✅ 开方指数调整原理

✅ Newton-Raphson开方迭代

✅ 逐位开方法实现

✅ 开方特殊值处理