IEEE 754要求硬件支持√x运算。开方的核心在于指数调整和尾数开方:
常用三种算法各有优劣:
开方有严格的特殊值语义:
| 输入 | 结果 | 说明 |
|---|---|---|
| +0 | +0 | 保持符号 |
| -0 | -0 | 保持符号(与+0值相等) |
| +∞ | +∞ | 无穷开方仍无穷 |
| -∞ | NaN | Invalid异常 |
| 负有限数 | NaN | Invalid异常 |
| NaN | NaN | 传播(保持quiet NaN) |
在已有FMA的FPU中,开方可完全复用现有硬件:
"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开方迭代
✅ 逐位开方法实现
✅ 开方特殊值处理