diff --git a/spec/std/isa/inst/F/fdiv.s.yaml b/spec/std/isa/inst/F/fdiv.s.yaml index fd6804438..a25f758dd 100644 --- a/spec/std/isa/inst/F/fdiv.s.yaml +++ b/spec/std/isa/inst/F/fdiv.s.yaml @@ -30,7 +30,10 @@ access: vu: always data_independent_timing: true operation(): | - + check_f_ok($encoding); + RoundingMode mode = rm_to_mode(rm, $encoding); + f[fd] = f32_div(f[fs1], f[fs2], mode); + mark_f_state_dirty(); # SPDX-SnippetBegin # SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model # SPDX-License-Identifier: BSD-2-Clause diff --git a/spec/std/isa/inst/F/fmadd.s.yaml b/spec/std/isa/inst/F/fmadd.s.yaml index 78aaa3274..d4f21cfcf 100644 --- a/spec/std/isa/inst/F/fmadd.s.yaml +++ b/spec/std/isa/inst/F/fmadd.s.yaml @@ -31,6 +31,11 @@ access: vu: always data_independent_timing: true operation(): | + check_f_ok($encoding); + RoundingMode mode = rm_to_mode(rm, $encoding); + F32MulAddOp op = F32MulAddOp::Softfloat_mulAdd_addC; + f[fd] = f32_muladd(f[fs1], f[fs2], f[fs3], op, mode); + mark_f_state_dirty(); # SPDX-SnippetBegin # SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model diff --git a/spec/std/isa/inst/F/fmsub.s.yaml b/spec/std/isa/inst/F/fmsub.s.yaml index fe088c732..b13201011 100644 --- a/spec/std/isa/inst/F/fmsub.s.yaml +++ b/spec/std/isa/inst/F/fmsub.s.yaml @@ -31,6 +31,11 @@ access: vu: always data_independent_timing: true operation(): | + check_f_ok($encoding); + RoundingMode mode = rm_to_mode(rm, $encoding); + F32MulAddOp op = F32MulAddOp::Softfloat_mulAdd_subC; + f[fd] = f32_muladd(f[fs1], f[fs2], f[fs3], op, mode); + mark_f_state_dirty(); # SPDX-SnippetBegin # SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model diff --git a/spec/std/isa/inst/F/fmul.s.yaml b/spec/std/isa/inst/F/fmul.s.yaml index 092fc2e74..388f44e57 100644 --- a/spec/std/isa/inst/F/fmul.s.yaml +++ b/spec/std/isa/inst/F/fmul.s.yaml @@ -30,6 +30,10 @@ access: vu: always data_independent_timing: true operation(): | + check_f_ok($encoding); + RoundingMode mode = rm_to_mode(rm, $encoding); + f[fd] = f32_mul(f[fs1], f[fs2], mode); + mark_f_state_dirty(); # SPDX-SnippetBegin # SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model diff --git a/spec/std/isa/inst/F/fnmadd.s.yaml b/spec/std/isa/inst/F/fnmadd.s.yaml index 8e9cb7742..efe128bad 100644 --- a/spec/std/isa/inst/F/fnmadd.s.yaml +++ b/spec/std/isa/inst/F/fnmadd.s.yaml @@ -32,6 +32,11 @@ access: vu: always data_independent_timing: true operation(): | + check_f_ok($encoding); + RoundingMode mode = rm_to_mode(rm, $encoding); + F32MulAddOp op = F32MulAddOp::Softfloat_mulAdd_subC; + f[fd] = f32_muladd(-f[fs1], f[fs2], -f[fs3], op, mode); + mark_f_state_dirty(); # SPDX-SnippetBegin # SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model diff --git a/spec/std/isa/inst/F/fnmsub.s.yaml b/spec/std/isa/inst/F/fnmsub.s.yaml index 461f87144..6c6489a21 100644 --- a/spec/std/isa/inst/F/fnmsub.s.yaml +++ b/spec/std/isa/inst/F/fnmsub.s.yaml @@ -32,6 +32,11 @@ access: vu: always data_independent_timing: true operation(): | + check_f_ok($encoding); + RoundingMode mode = rm_to_mode(rm, $encoding); + F32MulAddOp op = F32MulAddOp::Softfloat_mulAdd_addC; + f[fd] = f32_muladd(-f[fs1], f[fs2], f[fs3], op, mode); + mark_f_state_dirty(); # SPDX-SnippetBegin # SPDX-FileCopyrightText: 2017-2025 Contributors to the RISCV Sail Model diff --git a/spec/std/isa/isa/fp.idl b/spec/std/isa/isa/fp.idl index 1dcfe70b9..a2229f433 100644 --- a/spec/std/isa/isa/fp.idl +++ b/spec/std/isa/isa/fp.idl @@ -49,6 +49,12 @@ enum FpFlag { NV 0b10000 # Invalid Operation } +enum F32MulAddOp { + Softfloat_mulAdd_addC # will get value 0 + Softfloat_mulAdd_subC # will get value 1 + Softfloat_mulAdd_subProd # will get value 2 +} + function set_fp_flag { arguments FpFlag flag @@ -313,6 +319,7 @@ function softfloat_shiftRightJam32 { } } + function softfloat_shiftRightJam64 { returns Bits<64> arguments @@ -475,6 +482,19 @@ function softfloat_normSubnormalF16Sig { } } +function softfloat_normSubnormalF32Sig { + returns Bits<8>, Bits<23> + arguments + Bits<32> sp_value + description { + normalize subnormal single-precision value + } + body { + Bits<8> shift_dist = count_leading_zeros<32>(sp_value) - 8; + return 1 - shift_dist, sp_value << shift_dist; + } +} + function softfloat_roundPackToF32 { returns Bits<32> # single precision value arguments @@ -759,7 +779,7 @@ function softfloat_addMagsF32 { U32 sigZ = 0x20000000 + sigA + sigB; if ( sigZ < 0x40000000 ) { - expZ = expZ - 1; + expZ = expZ `- 1'b1; sigZ = sigZ << 1; } } @@ -1236,3 +1256,364 @@ function round_f32_to_integral { return i32_to_f32_no_flag(intermediate, mode); } } + +function f32_mul { + returns U32 + arguments + U32 a, + U32 b, + RoundingMode mode + description { + Returns product of 2 floating point numbers + } + body { + Bits<1> signA = signF32UI(a); + Bits<8> expA = expF32UI(a); + Bits<23> sigA = fracF32UI(a); + Bits<1> signB = signF32UI(b); + Bits<8> expB = expF32UI(b); + Bits<23> sigB = fracF32UI(b); + + # declare a variable to store significand of product + U32 sigZ; + + # declare a variable to store sign of product + Bits<1> signZ; + + # declare a variable to store the exponent part of product + Bits<8> expZ; + + # calculate sign of product + signZ = signA ^ signB; + + U32 magBits; + + if (expA == 0xFF) { + if ((sigA != 0) || ((expB == 0xFF) && (sigB != 0))) { + return softfloat_propagateNaNF32UI(a, b); + magBits = expB | sigB; + if (magBits == 0) { + set_fp_flag(FpFlag::NV); + return UI32_NAN; + } else { + return packToF32UI(signZ, 0xFF, 0); + } + } + } + + if (expB == 0xFF) { + if (sigB != 0) { + return softfloat_propagateNaNF32UI(a, b); + } + magBits = expA | sigA; + if (magBits == 0) { + set_fp_flag(FpFlag::NV); + return UI32_NAN; + } else { + return packToF32UI(signZ, 0xFF, 0); + } + } + + if (expA == 0) { + if (sigA == 0) { + return packToF32UI(signZ, 0, 0); + } + (expA, sigA) = softfloat_normSubnormalF32Sig(sigA); + } + + if (expB == 0) { + if (sigB == 0) { + return packToF32UI(signZ, 0, 0); + } + (expB, sigB) = softfloat_normSubnormalF32Sig(sigB); + } + + expZ = expA + expB - 0x7F; + sigA = (sigA | 0x00800000)<<7; + sigB = (sigB | 0x00800000)<<8; + sigZ = softfloat_shiftRightJam64(sigA `* sigB, 32); + if (sigZ < 0x40000000) { + expZ = expZ `- 1'b1; + sigZ = sigZ << 1; + } + return softfloat_roundPackToF32(signZ, expZ, sigZ, mode); + } +} + +function f32_div { + returns U32 + arguments + U32 a, + U32 b, + RoundingMode mode + description { + Returns quotient of 2 floating point numbers + } + body { + Bits<1> signA = signF32UI(a); + Bits<8> expA = expF32UI(a); + Bits<23> sigA = fracF32UI(a); + Bits<1> signB = signF32UI(b); + Bits<8> expB = expF32UI(b); + Bits<23> sigB = fracF32UI(b); + Bits<1> signZ = signA ^ signB; + Bits<8> expZ; + Bits<23> sigZ; + + U64 sig64A; + + if (expA == 0xFF) { + if (sigA != 0) { + return softfloat_propagateNaNF32UI(a, b); + } + if ((expB == 0xFF) && (sigB != 0)) { + return softfloat_propagateNaNF32UI(a, b); + } + if ((expB == 0xFF) && (sigB == 0)) { + set_fp_flag(FpFlag::NV); + return SP_CANONICAL_NAN; + } + return packToF32UI(signZ, 0xFF, 0); + } + + if (expB == 0xFF) { + if (sigB != 0) { + return softfloat_propagateNaNF32UI(a, b); + } + return packToF32UI(signZ, 0, 0); + } + + if (expB == 0) { + if (sigB == 0) { + if ((expA == 0) && (sigA == 0)) { + set_fp_flag(FpFlag::NV); + return SP_CANONICAL_NAN; + } + set_fp_flag(FpFlag::OF); + set_fp_flag(FpFlag::NX); + return packToF32UI(signZ, 0xFF, 0); + } + (expB, sigB) = softfloat_normSubnormalF32Sig(sigB); + } + if (expA == 0) { + if (sigA == 0) { + return packToF32UI(signZ, 0, 0); + } + (expA, sigA) = softfloat_normSubnormalF32Sig(sigA); + } + + expZ = expA - expB + 0x7E; + sigA = (sigA | 0x800000); + sigB = (sigB | 0x800000); + + if (sigA < sigB) { + expZ = expZ - 1; + sig64A = sigA `<< 31; + } else { + sig64A = sigA `<< 30; + } + + sigZ = sig64A / sigB; + # Check if the lower 6 bits are zero, meaning the division was exact. + # This is used to determine if rounding/jamming is needed. + if (!(sigZ & 0x3F)) { + sigZ = sigZ | ((sigB `* sigZ) != sig64A); + } + + return softfloat_roundPackToF32(signZ, expZ, sigZ, mode); + } +} + +function propagateNaN_ABC { + returns U32 + arguments + U32 a, + U32 b, + U32 c + description { + Applies softfloat_propagateNaNF32UI to 3 numbers + } + body { + U32 z = softfloat_propagateNaNF32UI(a, b); + return softfloat_propagateNaNF32UI(z, c); + } +} + +function f32_muladd { + returns U32 + arguments + U32 a, + U32 b, + U32 c, + F32MulAddOp op, + RoundingMode mode + description { + Sum of the product of `a` and `b` with `c`. Sign of `c` depends on `op` + } + body { + Bits<1> signA = signF32UI(a); + Bits<8> expA = expF32UI(a); + Bits<23> sigA = fracF32UI(a); + Bits<1> signB = signF32UI(b); + Bits<8> expB = expF32UI(b); + Bits<23> sigB = fracF32UI(b); + Bits<1> signC = signF32UI(c) ^ (op == F32MulAddOp::Softfloat_mulAdd_subC); + Bits<8> expC = expF32UI(c); + Bits<23> sigC = fracF32UI(c); + Bits<1> signProd = (signA ^ signB) ^ (op == F32MulAddOp::Softfloat_mulAdd_subProd); + Bits<8> expProd; + U64 sigProd; + Bits<1> signZ; + Bits<8> expZ; + Bits<23> sigZ; + + Bits<32> Z; + U64 sig64Z, sig64C; + + Bits<32> expDiff; + + if (expA == 0xFF) { + if ((sigA != 0) || ((expB == 0xFF) && (sigB != 0))) { + propagateNaN_ABC(a,b,c); + } + magBits = expB | sigB; + if (magBits != 0) { + Z = packToF32UI(signProd, 0xFF, 0); + if (expC != 0xFF) { + return Z; + } + if (sigC != 0) { + softfloat_propagateNaNF32UI(Z, c); + } + if (signProd == signC) { + return Z; + } + } + set_fp_flag(FpFlag::NV); + Z = UI32_NaN; + softfloat_propagateNaNF32UI(Z, c); + return Z; + } + + if (expB == 0xFF) { + if (sigB != 0) { + propagateNaN_ABC(a,b,c); + } + magBits = expA | sigA; + if (magBits != 0) { + Z = packToF32UI(signProd, 0xFF, 0); + if (expC != 0xFF) { + return Z; + } + if (sigC != 0) { + softfloat_propagateNaNF32UI(Z, c); + } + if (signProd == signC) { + return Z; + } + } + set_fp_flag(FpFlag::NV); + Z = UI32_NaN; + softfloat_propagateNaNF32UI(Z, c); + return Z; + } + + if (expC == 0xFF) { + if (sigC != 0) { + Z = 0; + softfloat_propagateNaNF32UI(Z, c); + } + Z = c; + return Z; + } + + if (expA == 0) { + if (sigA == 0) { + Z = c; + if (((expC | sigC) == 0) && (signProd != signC)) { + Z = packToF32UI((mode == RoundingMode::RDN), 0, 0); + } + return Z; + } + (expA, sigA) = softfloat_normSubnormalF32Sig(sigA); + } + + if (expB == 0) { + if (sigB == 0) { + Z = c; + if (((expC | sigC) == 0) && (signProd != signC)) { + Z = packToF32UI((mode == RoundingMode::RDN), 0, 0); + } + return Z; + } + (expB, sigB) = softfloat_normSubnormalF32Sig(sigB); + } + + expProd = expA + expB - 0x7E; + sigA = (sigA | 0x00800000)<<7; + sigB = (sigB | 0x00800000)<<7; + sigProd = sigA `* sigB; + if ( sigProd < 0x2000000000000000 ) { + expProd = expProd `- 1; + sigProd = sigProd << 1; + } + signZ = signProd; + if (expC == 0) { + if (sigC == 0) { + expZ = expProd - 1; + sigZ = softfloat_shiftRightJam64(sigProd, 31); + return softfloat_roundPackToF32(signZ, expZ, sigZ); + } + (expC, sigC) = softfloat_normSubnormalF32Sig( sigC ); + } + sigC = (sigC | 0x00800000)<<6; + expDiff = expProd - expC; + if ( signProd == signC ) { + if ( expDiff <= 0 ) { + expZ = expC; + sigZ = sigC + softfloat_shiftRightJam64( sigProd, 32 - expDiff ); + } else { + expZ = expProd; + sig64Z = + sigProd + + softfloat_shiftRightJam64( + sigC `<< 32, expDiff ); + sigZ = softfloat_shiftRightJam64( sig64Z, 32 ); + } + if ( sigZ < 0x40000000 ) { + expZ = expZ - 1; + sigZ = sigZ - 1; + } + } else { + sig64C = sigC `<< 32; + if ( expDiff < 0 ) { + signZ = signC; + expZ = expC; + sig64Z = sig64C - softfloat_shiftRightJam64( sigProd, -expDiff ); + } else if ( expDiff == 0 ) { + expZ = expProd; + sig64Z = sigProd - sig64C; + if ( sig64Z == 0 ) { + return packToF32UI( + ((mode == RoundingMode::RDN)), 0, 0 ); + } + if ( (sig64Z & 0x8000000000000000) == 1 ) { + signZ = ! signZ; + sig64Z = -sig64Z; + } + } else { + expZ = expProd; + sig64Z = sigProd - softfloat_shiftRightJam64( sig64C, expDiff ); + } + shiftDist = count_leading_zeros<64>( sig64Z ) - 1; + expZ = expZ - shiftDist; + shiftDist = shiftDist - 32; + if ( shiftDist < 0 ) { + sigZ = softfloat_shiftRightJam64( sig64Z, -shiftDist ); + } else { + sigZ = sig64Z<