### FPU: Implement fsqrt[s] and add a test for fsqrt

```This implements the floating square-root calculation using a table
lookup of the inverse square root approximation, followed by three
iterations of Goldschmidt's algorithm, which gives estimates of both
sqrt(FRB) and 1/sqrt(FRB).  Then the residual is calculated as
FRB - R * R and that is multiplied by the 1/sqrt(FRB) estimate to get
an adjustment to R.  The residual and the adjustment can be negative,
and since we have an unsigned multiplier, the upper bits can be wrong.
In practice the adjustment fits into an 8-bit signed value, and the
bottom 8 bits of the adjustment product are correct, so we sign-extend
them, divide by 4 (because R is in 10.54 format) and add them to R.

Finally the residual is calculated again and compared to 2*R+1 to see
if a final increment is needed.  Then the result is rounded and
written back.

This implements fsqrts as fsqrt, but with rounding to single precision
and underflow/overflow calculation using the single-precision exponent
range.  This could be optimized later.

Signed-off-by: Paul Mackerras <paulus@ozlabs.org>```
parent 394f993e75
commit c350bc1f25
5 changed files with 262 additions and 6 deletions

#### 2 decode1.vhdl Unescape Escape View File

 `@ -419,6 +419,7 @@ architecture behaviour of decode1 is` ` 2#10010# => (FPU, OP_FPOP, FRA, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '1', '0', RC, '0', '0'), -- fdivs` ` 2#10100# => (FPU, OP_FPOP, FRA, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '1', '0', RC, '0', '0'), -- fsubs` ` 2#10101# => (FPU, OP_FPOP, FRA, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '1', '0', RC, '0', '0'), -- fadds` ` 2#10110# => (FPU, OP_FPOP, NONE, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '1', '0', RC, '0', '0'), -- fsqrts` ` 2#11000# => (FPU, OP_FPOP, NONE, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '1', '0', RC, '0', '0'), -- fres` ` 2#11001# => (FPU, OP_FPOP, FRA, NONE, FRC, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '1', '0', RC, '0', '0'), -- fmuls` ` 2#11010# => (FPU, OP_FPOP, NONE, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '1', '0', RC, '0', '0'), -- frsqrtes` `@ -477,6 +478,7 @@ architecture behaviour of decode1 is` ` 2#0010# => (FPU, OP_FPOP, FRA, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '0', '0', RC, '0', '0'), -- fdiv` ` 2#0100# => (FPU, OP_FPOP, FRA, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '0', '0', RC, '0', '0'), -- fsub` ` 2#0101# => (FPU, OP_FPOP, FRA, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '0', '0', RC, '0', '0'), -- fadd` ` 2#0110# => (FPU, OP_FPOP, NONE, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '0', '0', RC, '0', '0'), -- fsqrt` ` 2#0111# => (FPU, OP_FPOP, FRA, FRB, FRC, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '0', '0', RC, '0', '0'), -- fsel` ` 2#1000# => (FPU, OP_FPOP, NONE, FRB, NONE, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '0', '0', RC, '0', '0'), -- fre` ` 2#1001# => (FPU, OP_FPOP, FRA, NONE, FRC, FRT, '0', '0', '0', '0', ZERO, '0', NONE, '0', '0', '0', '0', '0', '0', RC, '0', '0'), -- fmul`

#### 217 fpu.vhdl Unescape Escape View File

 `@ -40,7 +40,7 @@ architecture behaviour of fpu is` ` DO_FMR, DO_FMRG, DO_FCMP,` ` DO_FCFID, DO_FCTI,` ` DO_FRSP, DO_FRI,` ` DO_FADD, DO_FMUL, DO_FDIV,` ` DO_FADD, DO_FMUL, DO_FDIV, DO_FSQRT,` ` DO_FRE, DO_FRSQRTE,` ` DO_FSEL,` ` FRI_1,` `@ -51,6 +51,9 @@ architecture behaviour of fpu is` ` DIV_2, DIV_3, DIV_4, DIV_5, DIV_6,` ` FRE_1,` ` RSQRT_1,` ` SQRT_1, SQRT_2, SQRT_3, SQRT_4,` ` SQRT_5, SQRT_6, SQRT_7, SQRT_8,` ` SQRT_9, SQRT_10, SQRT_11, SQRT_12,` ` INT_SHIFT, INT_ROUND, INT_ISHIFT,` ` INT_FINAL, INT_CHECK, INT_OFLOW,` ` FINISH, NORMALIZE,` `@ -140,6 +143,7 @@ architecture behaviour of fpu is` ` constant BIN_ZERO : std_ulogic_vector(1 downto 0) := "00";` ` constant BIN_R : std_ulogic_vector(1 downto 0) := "01";` ` constant BIN_MASK : std_ulogic_vector(1 downto 0) := "10";` ` constant BIN_PS6 : std_ulogic_vector(1 downto 0) := "11";` ` constant RES_SUM : std_ulogic_vector(1 downto 0) := "00";` ` constant RES_SHIFT : std_ulogic_vector(1 downto 0) := "01";` `@ -604,6 +608,7 @@ begin` ` variable pshift : std_ulogic;` ` variable renorm_sqrt : std_ulogic;` ` variable sqrt_exp : signed(EXP_BITS-1 downto 0);` ` variable shiftin : std_ulogic;` ` begin` ` v := r;` ` illegal := '0';` `@ -717,6 +722,7 @@ begin` ` set_y := '0';` ` pshift := '0';` ` renorm_sqrt := '0';` ` shiftin := '0';` ` case r.state is` ` when IDLE =>` ` if e_in.valid = '1' then` `@ -765,6 +771,9 @@ begin` ` v.state := DO_FDIV;` ` when "10100" | "10101" =>` ` v.state := DO_FADD;` ` when "10110" =>` ` v.is_sqrt := '1';` ` v.state := DO_FSQRT;` ` when "10111" =>` ` v.state := DO_FSEL;` ` when "11000" =>` `@ -1248,6 +1257,43 @@ begin` ` v.quieten_nan := '0';` ` arith_done := '1';` ` when DO_FSQRT =>` ` opsel_a <= AIN_B;` ` v.result_class := r.b.class;` ` v.result_sign := r.b.negative;` ` v.fpscr(FPSCR_FR) := '0';` ` v.fpscr(FPSCR_FI) := '0';` ` if r.b.class = NAN and r.b.mantissa(53) = '0' then` ` v.fpscr(FPSCR_VXSNAN) := '1';` ` invalid := '1';` ` end if;` ` case r.b.class is` ` when FINITE =>` ` v.result_exp := r.b.exponent;` ` if r.b.negative = '1' then` ` v.fpscr(FPSCR_VXSQRT) := '1';` ` qnan_result := '1';` ` arith_done := '1';` ` elsif r.b.mantissa(54) = '0' then` ` v.state := RENORM_B;` ` elsif r.b.exponent(0) = '0' then` ` v.state := SQRT_1;` ` else` ` v.shift := to_signed(1, EXP_BITS);` ` v.state := RENORM_B2;` ` end if;` ` when NAN | ZERO =>` ` -- result is B` ` arith_done := '1';` ` when INFINITY =>` ` if r.b.negative = '1' then` ` v.fpscr(FPSCR_VXSQRT) := '1';` ` qnan_result := '1';` ` -- else result is B` ` end if;` ` arith_done := '1';` ` end case;` ` when DO_FRE =>` ` opsel_a <= AIN_B;` ` v.result_class := r.b.class;` `@ -1454,7 +1500,11 @@ begin` ` -- wait one cycle for inverse_table[B] lookup` ` v.first := '1';` ` if r.insn(4) = '0' then` ` v.state := DIV_2;` ` if r.insn(3) = '0' then` ` v.state := DIV_2;` ` else` ` v.state := SQRT_1;` ` end if;` ` elsif r.insn(2) = '0' then` ` v.state := FRE_1;` ` else` `@ -1545,6 +1595,156 @@ begin` ` v.shift := to_signed(1, EXP_BITS);` ` v.state := NORMALIZE;` ` when SQRT_1 =>` ` -- put invsqr[B] in R and compute P = invsqr[B] * B` ` -- also transfer B (in R) to A` ` set_a := '1';` ` opsel_r <= RES_MISC;` ` misc_sel <= "0111";` ` msel_1 <= MUL1_B;` ` msel_2 <= MUL2_LUT;` ` f_to_multiply.valid <= '1';` ` v.shift := to_signed(-1, EXP_BITS);` ` v.count := "00";` ` v.state := SQRT_2;` ` when SQRT_2 =>` ` -- shift R right one place` ` -- not expecting multiplier result yet` ` opsel_r <= RES_SHIFT;` ` v.first := '1';` ` v.state := SQRT_3;` ` when SQRT_3 =>` ` -- put R into Y, wait for product from multiplier` ` msel_2 <= MUL2_R;` ` set_y := r.first;` ` pshift := '1';` ` if multiply_to_f.valid = '1' then` ` -- put result into R` ` opsel_r <= RES_MULT;` ` v.first := '1';` ` v.state := SQRT_4;` ` end if;` ` when SQRT_4 =>` ` -- compute 1.5 - Y * P` ` msel_1 <= MUL1_Y;` ` msel_2 <= MUL2_P;` ` msel_add <= MULADD_CONST;` ` msel_inv <= '1';` ` f_to_multiply.valid <= r.first;` ` pshift := '1';` ` if multiply_to_f.valid = '1' then` ` v.state := SQRT_5;` ` end if;` ` when SQRT_5 =>` ` -- compute Y = Y * P` ` msel_1 <= MUL1_Y;` ` msel_2 <= MUL2_P;` ` f_to_multiply.valid <= '1';` ` v.first := '1';` ` v.state := SQRT_6;` ` when SQRT_6 =>` ` -- pipeline in R = R * P` ` msel_1 <= MUL1_R;` ` msel_2 <= MUL2_P;` ` f_to_multiply.valid <= r.first;` ` pshift := '1';` ` if multiply_to_f.valid = '1' then` ` v.first := '1';` ` v.state := SQRT_7;` ` end if;` ` when SQRT_7 =>` ` -- first multiply is done, put result in Y` ` msel_2 <= MUL2_P;` ` set_y := r.first;` ` -- wait for second multiply (should be here already)` ` pshift := '1';` ` if multiply_to_f.valid = '1' then` ` -- put result into R` ` opsel_r <= RES_MULT;` ` v.first := '1';` ` v.count := r.count + 1;` ` if r.count < 2 then` ` v.state := SQRT_4;` ` else` ` v.first := '1';` ` v.state := SQRT_8;` ` end if;` ` end if;` ` when SQRT_8 =>` ` -- compute P = A - R * R, which can be +ve or -ve` ` -- we arranged for B to be put into A earlier` ` msel_1 <= MUL1_R;` ` msel_2 <= MUL2_R;` ` msel_add <= MULADD_A;` ` msel_inv <= '1';` ` pshift := '1';` ` f_to_multiply.valid <= r.first;` ` if multiply_to_f.valid = '1' then` ` v.first := '1';` ` v.state := SQRT_9;` ` end if;` ` when SQRT_9 =>` ` -- compute P = P * Y` ` -- since Y is an estimate of 1/sqrt(B), this makes P an` ` -- estimate of the adjustment needed to R. Since the error` ` -- could be negative and we have an unsigned multiplier, the` ` -- upper bits can be wrong, but it turns out the lowest 8 bits` ` -- are correct and are all we need (given 3 iterations through` ` -- SQRT_4 to SQRT_7).` ` msel_1 <= MUL1_Y;` ` msel_2 <= MUL2_P;` ` pshift := '1';` ` f_to_multiply.valid <= r.first;` ` if multiply_to_f.valid = '1' then` ` v.state := SQRT_10;` ` end if;` ` when SQRT_10 =>` ` -- Add the bottom 8 bits of P, sign-extended,` ` -- divided by 4, onto R.` ` -- The division by 4 is because R is 10.54 format` ` -- whereas P is 8.56 format.` ` opsel_b <= BIN_PS6;` ` sqrt_exp := r.b.exponent(EXP_BITS-1) & r.b.exponent(EXP_BITS-1 downto 1);` ` v.result_exp := sqrt_exp;` ` v.shift := to_signed(1, EXP_BITS);` ` v.first := '1';` ` v.state := SQRT_11;` ` when SQRT_11 =>` ` -- compute P = A - R * R (remainder)` ` -- also put 2 * R + 1 into B for comparison with P` ` msel_1 <= MUL1_R;` ` msel_2 <= MUL2_R;` ` msel_add <= MULADD_A;` ` msel_inv <= '1';` ` f_to_multiply.valid <= r.first;` ` shiftin := '1';` ` set_b := r.first;` ` if multiply_to_f.valid = '1' then` ` v.state := SQRT_12;` ` end if;` ` when SQRT_12 =>` ` -- test if remainder is 0 or >= B = 2*R + 1` ` if pcmpb_lt = '1' then` ` -- square root is correct, set X if remainder non-zero` ` v.x := r.p(58) or px_nz;` ` else` ` -- square root needs to be incremented by 1` ` carry_in <= '1';` ` v.x := not pcmpb_eq;` ` end if;` ` v.state := FINISH;` ` when INT_SHIFT =>` ` opsel_r <= RES_SHIFT;` ` set_x := '1';` `@ -1828,8 +2028,12 @@ begin` ` maddend := (others => '0');` ` case msel_add is` ` when MULADD_CONST =>` ` -- addend is 2.0 in 16.112 format` ` maddend(113) := '1'; -- 2.0` ` -- addend is 2.0 or 1.5 in 16.112 format` ` if r.is_sqrt = '0' then` ` maddend(113) := '1'; -- 2.0` ` else` ` maddend(112 downto 111) := "11"; -- 1.5` ` end if;` ` when MULADD_A =>` ` -- addend is A in 16.112 format` ` maddend(121 downto 58) := r.a.mantissa;` `@ -1895,14 +2099,15 @@ begin` ` when BIN_MASK =>` ` in_b0 := mask;` ` when others =>` ` in_b0 := (others => '0');` ` -- BIN_PS6, 6 LSBs of P/4 sign-extended to 64` ` in_b0 := std_ulogic_vector(resize(signed(r.p(7 downto 2)), 64));` ` end case;` ` if opsel_binv = '1' then` ` in_b0 := not in_b0;` ` end if;` ` in_b <= in_b0;` ` if r.shift >= to_signed(-64, EXP_BITS) and r.shift <= to_signed(63, EXP_BITS) then` ` shift_res := shifter_64(r.r & x"00000000000000",` ` shift_res := shifter_64(r.r & shiftin & 55x"00000000000000",` ` std_ulogic_vector(r.shift(6 downto 0)));` ` else` ` shift_res := (others => '0');`

#### 48 tests/fpu/fpu.c Unescape Escape View File

 `@ -1291,6 +1291,53 @@ int fpu_test_21(void)` ` return trapit(0, test21);` `}` `struct sqrtvals {` ` unsigned long val;` ` unsigned long inv;` `} sqrtvals[] = {` ` { 0x0000000000000000, 0x0000000000000000 },` ` { 0x8000000000000000, 0x8000000000000000 },` ` { 0xfff0000000000000, 0x7ff8000000000000 },` ` { 0x7ff0000000000000, 0x7ff0000000000000 },` ` { 0xfff123456789abcd, 0xfff923456789abcd },` ` { 0x3ff0000000000000, 0x3ff0000000000000 },` ` { 0x4000000000000000, 0x3ff6a09e667f3bcd },` ` { 0x4010000000000000, 0x4000000000000000 },` ` { 0xbff0000000000000, 0x7ff8000000000000 },` ` { 0x4008000000000000, 0x3ffbb67ae8584caa },` ` { 0x7fd0000000000000, 0x5fe0000000000000 },` ` { 0x0008000000000000, 0x1ff6a09e667f3bcd },` ` { 0x0004000000000000, 0x1ff0000000000000 },` ` { 0x0002000000000000, 0x1fe6a09e667f3bcd },` ` { 0x0000000000000002, 0x1e66a09e667f3bcd },` ` { 0x0000000000000001, 0x1e60000000000000 },` `};` `int test22(long arg)` `{` ` long i;` ` unsigned long result;` ` struct sqrtvals *vp = sqrtvals;` ` set_fpscr(FPS_RN_NEAR);` ` for (i = 0; i < sizeof(sqrtvals) / sizeof(sqrtvals[0]); ++i, ++vp) {` ` asm("lfd 6,0(%0); fsqrt 7,6; stfd 7,0(%1)"` ` : : "b" (&vp->val), "b" (&result) : "memory");` ` if (result != vp->inv) {` ` print_hex(i, 2, " ");` ` print_hex(result, 16, " ");` ` return i + 1;` ` }` ` }` ` return 0;` `}` `int fpu_test_22(void)` `{` ` enable_fp();` ` return trapit(0, test22);` `}` `int fail = 0;` `void do_test(int num, int (*test)(void))` `@ -1337,6 +1384,7 @@ int main(void)` ` do_test(19, fpu_test_19);` ` do_test(20, fpu_test_20);` ` do_test(21, fpu_test_21);` ` do_test(22, fpu_test_22);` ` return fail;` `}`

#### BIN tests/test_fpu.bin View File

Binary file not shown.

#### 1 tests/test_fpu.console_out Unescape Escape View File

 `@ -19,3 +19,4 @@ test 18:PASS` `test 19:PASS` `test 20:PASS` `test 21:PASS` `test 22:PASS`