-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Floating Point Square Root Module with Variable Odd Mantissa and No Rounding #188
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,3 +25,7 @@ | |
## Float8ToFixed | ||
|
||
This component converts an 8-bit floating-point (FP8) representation ([FloatingPoint8E4M3Value](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint8E4M3Value-class.html) or [FloatingPoint8E5M2Value](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint8E5M2Value-class.html)) to a signed fixed-point representation. This component offers using the same hardware for both FP8 formats. Therefore, both input and output are of type [Logic](https://intel.github.io/rohd/rohd/Logic-class.html) and can be cast from/to floating point/fixed point by the producer/consumer based on the selected `mode`. Infinities and NaN's are not supported. The output width is 33bits to accommodate [FloatingPoint8E5M2Value](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint8E5M2Value-class.html) without loss. | ||
|
||
## FixedPointSqrt | ||
|
||
This component computes the square root of a 3.x fixed-point value, returning a result in the same format. The square root value is rounded to the ordered number of bits. The integral part must be 3 bits, and the fractional part may be any odd value <= 51. Even numbers of bits are currently not supported, integral bits in numbers other than 3 are currently not supported. | ||
Check failure on line 31 in doc/components/fixed_point.md
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright (C) 2025 Intel Corporation | ||
// SPDX-License-Indentifier: BSD-3-Clause | ||
// | ||
// fixed_point_sqrt.dart | ||
// An abstract base class defining the API for floating-point square root. | ||
// | ||
// 2025 March 3 | ||
// Authors: James Farwell <[email protected]>, Stephen | ||
// Weeks <[email protected]> | ||
|
||
/// An abstract API for fixed point square root. | ||
library; | ||
|
||
import 'package:meta/meta.dart'; | ||
import 'package:rohd/rohd.dart'; | ||
import 'package:rohd_hcl/rohd_hcl.dart'; | ||
|
||
/// Abstract base class | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: these doc comments go into API docs, in general it's best to describe more about what the class does rather than why the class is here in the context of the file. i.e. this is not just an "abstract base class", but a base class for fixed-point square root |
||
abstract class FixedPointSqrtBase extends Module { | ||
/// Width of the input and output fields. | ||
final int numWidth; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just 'width'? |
||
|
||
/// The value [a], named this way to allow for a local variable 'a'. | ||
@protected | ||
late final FixedPoint a; | ||
|
||
/// getter for the computed output. | ||
late final FixedPoint sqrtF = a.clone(name: 'sqrtF')..gets(output('sqrtF')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: why the name "sqrtF" rather than just "sqrt"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, this answers your question below! We did originally have both do sqrt. However, dart was having naming conflicts with math.sqrt() that we needed to run our tests. And honestly, sqrt didn't seem great after that. So I opted for "sqrtF" for sqrt fixed and "sqrtR" which is sqrt result. Though that one should probably be "sqrtFP". |
||
|
||
/// Square root a fixed point number [a], returning result in [sqrtF]. | ||
FixedPointSqrtBase(FixedPoint a, | ||
{super.name = 'fixed_point_square_root', String? definitionName}) | ||
: numWidth = a.width, | ||
super( | ||
definitionName: | ||
definitionName ?? 'FixedPointSquareRoot${a.width}') { | ||
this.a = a.clone(name: 'a')..gets(addInput('a', a, width: a.width)); | ||
|
||
addOutput('sqrtF', width: numWidth); | ||
} | ||
} | ||
|
||
/// Implementation | ||
/// Algorithm explained here; | ||
/// https://projectf.io/posts/square-root-in-verilog/ | ||
class FixedPointSqrt extends FixedPointSqrtBase { | ||
/// Constructor | ||
FixedPointSqrt(super.a) { | ||
Logic solution = | ||
FixedPoint(signed: a.signed, name: 'solution', m: a.m + 1, n: a.n + 1); | ||
Logic remainder = | ||
FixedPoint(signed: a.signed, name: 'remainder', m: a.m + 1, n: a.n + 1); | ||
Logic subtractionValue = | ||
FixedPoint(signed: a.signed, name: 'subValue', m: a.m + 1, n: a.n + 1); | ||
Logic aLoc = | ||
FixedPoint(signed: a.signed, name: 'aLoc', m: a.m + 1, n: a.n + 1); | ||
|
||
solution = Const(0, width: aLoc.width); | ||
remainder = Const(0, width: aLoc.width); | ||
subtractionValue = Const(0, width: aLoc.width); | ||
aLoc = [Const(0), a, Const(0)].swizzle(); | ||
|
||
final outputSqrt = a.clone(name: 'sqrtF'); | ||
output('sqrtF') <= outputSqrt; | ||
|
||
// loop once through input value | ||
for (var i = 0; i < ((numWidth + 2) >> 1); i++) { | ||
// append bits from a, two at a time | ||
remainder = [ | ||
remainder.slice(numWidth + 2 - 3, 0), | ||
aLoc.slice(aLoc.width - 1 - (i * 2), aLoc.width - 2 - (i * 2)) | ||
].swizzle(); | ||
subtractionValue = | ||
[solution.slice(numWidth + 2 - 3, 0), Const(1, width: 2)].swizzle(); | ||
solution = [ | ||
solution.slice(numWidth + 2 - 2, 0), | ||
subtractionValue.lte(remainder) | ||
].swizzle(); | ||
remainder = mux(subtractionValue.lte(remainder), | ||
remainder - subtractionValue, remainder); | ||
} | ||
|
||
// loop again to finish remainder | ||
for (var i = 0; i < ((numWidth + 2) >> 1) - 1; i++) { | ||
// don't try to append bits from a, they are done | ||
remainder = | ||
[remainder.slice(numWidth + 2 - 3, 0), Const(0, width: 2)].swizzle(); | ||
subtractionValue = | ||
[solution.slice(numWidth + 2 - 3, 0), Const(1, width: 2)].swizzle(); | ||
solution = [ | ||
solution.slice(numWidth + 2 - 2, 0), | ||
subtractionValue.lte(remainder) | ||
].swizzle(); | ||
remainder = mux(subtractionValue.lte(remainder), | ||
remainder - subtractionValue, remainder); | ||
} | ||
solution = solution + 1; | ||
outputSqrt <= solution.slice(aLoc.width - 1, aLoc.width - a.width); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright (C) 2025 Intel Corporation | ||
// SPDX-License-Indentifier: BSD-3-Clause | ||
// | ||
// floating_point_sqrt.dart | ||
// An abstract base class defining the API for floating-point square root. | ||
// | ||
// 2025 March 3 | ||
// Authors: James Farwell <[email protected]>, | ||
//Stephen Weeks <[email protected]>, | ||
//Curtis Anderson <[email protected]> | ||
|
||
import 'package:meta/meta.dart'; | ||
import 'package:rohd/rohd.dart'; | ||
import 'package:rohd_hcl/rohd_hcl.dart'; | ||
|
||
/// An abstract API for floating point square root. | ||
abstract class FloatingPointSqrt<FpType extends FloatingPoint> extends Module { | ||
/// Width of the output exponent field. | ||
final int exponentWidth; | ||
|
||
/// Width of the output mantissa field. | ||
final int mantissaWidth; | ||
|
||
/// The [clk] : if a non-null clock signal is passed in, a pipestage is added | ||
/// to the square root to help optimize frequency. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is to make future work easier. In most cases, like all ALUs, you get your best bang for buck when it is pipelined. I wasn't sure how to do that yet, but I wanted to have the ground work. It also keeps it consistent with the other FloatingPoint modules that are also capable of pipeline features. So setup for future work to make it a bit easier (and once I figure out how to do it in ROHD). |
||
@protected | ||
late final Logic? clk; | ||
|
||
/// Optional [reset], used only if a [clk] is not null to reset the pipeline | ||
/// flops. | ||
@protected | ||
late final Logic? reset; | ||
|
||
/// Optional [enable], used only if a [clk] is not null to enable the pipeline | ||
/// flops. | ||
@protected | ||
late final Logic? enable; | ||
|
||
/// The value [a], named this way to allow for a local variable 'a'. | ||
@protected | ||
late final FpType a; | ||
|
||
/// getter for the computed [FloatingPoint] output. | ||
late final FloatingPoint sqrtR = (a.clone(name: 'sqrtR') as FpType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: what's the "R" mean? why not "sqrt"? |
||
..gets(output('sqrtR')); | ||
|
||
/// getter for the [error] output. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little confused by this setup here with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I originally was planning to use the internal error signal to also be used for logic testing throughout when we had our first rough draft of the block diagram. I think I just missed this. |
||
late final Logic error = Logic(name: 'error')..gets(output('error')); | ||
|
||
/// The internal error signal to pass through | ||
late final Logic errorSig; | ||
|
||
/// Square root a floating point number [a], returning result in [sqrtR]. | ||
/// - [clk], [reset], [enable] are optional inputs to control a pipestage | ||
/// (only inserted if [clk] is provided) | ||
FloatingPointSqrt(FpType a, | ||
{Logic? clk, | ||
Logic? reset, | ||
Logic? enable, | ||
super.name = 'floating_point_square_root', | ||
String? definitionName}) | ||
: exponentWidth = a.exponent.width, | ||
mantissaWidth = a.mantissa.width, | ||
super( | ||
definitionName: definitionName ?? | ||
'FloatingPointSquareRoot_E${a.exponent.width}' | ||
'M${a.mantissa.width}') { | ||
this.clk = (clk != null) ? addInput('clk', clk) : null; | ||
this.reset = (reset != null) ? addInput('reset', reset) : null; | ||
this.enable = (enable != null) ? addInput('enable', enable) : null; | ||
this.a = (a.clone(name: 'a') as FpType) | ||
..gets(addInput('a', a, width: a.width)); | ||
|
||
addOutput('sqrtR', width: exponentWidth + mantissaWidth + 1); | ||
errorSig = Logic(name: 'error'); | ||
addOutput('error'); | ||
output('error') <= errorSig; | ||
} | ||
|
||
/// Pipelining helper that uses the context for signals clk/enable/reset | ||
Logic localFlop(Logic input) => | ||
condFlop(clk, input, en: enable, reset: reset); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// Copyright (C) 2025 Intel Corporation | ||
// SPDX-License-Indentifier: BSD-3-Clause | ||
// | ||
// floating_point_sqrt.dart | ||
// An abstract base class defining the API for floating-point square root. | ||
// | ||
// 2025 March 4 | ||
// Authors: James Farwell <[email protected]>, | ||
//Stephen Weeks <[email protected]>, | ||
//Curtis Anderson <[email protected]> | ||
|
||
import 'package:rohd/rohd.dart'; | ||
import 'package:rohd_hcl/rohd_hcl.dart'; | ||
|
||
/// An square root module for FloatingPoint values | ||
class FloatingPointSqrtSimple<FpType extends FloatingPoint> | ||
extends FloatingPointSqrt<FpType> { | ||
/// Square root one floating point number [a], returning results | ||
/// [sqrtR] and [error] | ||
FloatingPointSqrtSimple(super.a, | ||
{super.clk, | ||
super.reset, | ||
super.enable, | ||
super.name = 'floatingpoint_square_root_simple'}) | ||
: super( | ||
definitionName: 'FloatingPointSquareRootSimple_' | ||
'E${a.exponent.width}M${a.mantissa.width}') { | ||
final outputSqrt = FloatingPoint( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noticed the 'normal' tests: are subnormals supported? If not, you should check and throw. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! I forgot that! I'll fix that this weekend though I may need pointers on how to do that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you have an error flag, so you would just need to assign it to ~fp.isNormal(); |
||
exponentWidth: exponentWidth, | ||
mantissaWidth: mantissaWidth, | ||
name: 'sqrtR'); | ||
output('sqrtR') <= outputSqrt; | ||
|
||
// check to see if we do sqrt at all or just return a | ||
final isInf = a.isAnInfinity.named('isInf'); | ||
final isNaN = a.isNaN.named('isNan'); | ||
final isZero = a.isAZero.named('isZero'); | ||
final enableSqrt = ~((isInf | isNaN | isZero) | a.sign).named('enableSqrt'); | ||
|
||
// debias the exponent | ||
final deBiasAmt = (1 << a.exponent.width - 1) - 1; | ||
|
||
// deBias math | ||
final deBiasExp = a.exponent - deBiasAmt; | ||
|
||
// shift exponent | ||
final shiftedExp = | ||
[deBiasExp[-1], deBiasExp.slice(a.exponent.width - 1, 1)].swizzle(); | ||
|
||
// check if exponent was odd | ||
final isExpOdd = deBiasExp[0]; | ||
|
||
// use fixed sqrt unit | ||
final aFixed = FixedPoint(signed: false, m: 3, n: a.mantissa.width); | ||
aFixed <= [Const(1, width: 3), a.mantissa.getRange(0)].swizzle(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you have a chance, in a test you can check out your naming of Verilog upon output. You can create good names by attaching .named('aFixed'); especially after swizzles.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't sure if that was "too many names" when writing this, so tried to keep it to named structures. |
||
|
||
// mux if we shift left by 1 if exponent was odd | ||
final aFixedAdj = aFixed.clone() | ||
..gets(mux(isExpOdd, [aFixed.slice(-2, 0), Const(0)].swizzle(), aFixed) | ||
.named('oddMantissaMux')); | ||
|
||
// mux to choose if we do square root or not | ||
final fixedSqrt = aFixedAdj.clone() | ||
..gets(mux(enableSqrt, FixedPointSqrt(aFixedAdj).sqrtF, aFixedAdj) | ||
.named('sqrtMux')); | ||
|
||
// convert back to floating point representation | ||
final fpSqrt = FixedToFloat(fixedSqrt, | ||
exponentWidth: a.exponent.width, mantissaWidth: a.mantissa.width); | ||
|
||
// final calculation results | ||
Combinational([ | ||
errorSig < Const(0), | ||
If.block([ | ||
Iff(isInf & ~a.sign, [ | ||
outputSqrt < outputSqrt.inf(), | ||
]), | ||
ElseIf(isInf & a.sign, [ | ||
outputSqrt < outputSqrt.inf(negative: true), | ||
errorSig < Const(1), | ||
]), | ||
ElseIf(isNaN, [ | ||
outputSqrt < outputSqrt.nan, | ||
]), | ||
ElseIf(isZero, [ | ||
outputSqrt.sign < a.sign, | ||
outputSqrt.exponent < a.exponent, | ||
outputSqrt.mantissa < a.mantissa, | ||
]), | ||
ElseIf(a.sign, [ | ||
outputSqrt < outputSqrt.nan, | ||
errorSig < Const(1), | ||
]), | ||
Else([ | ||
outputSqrt.sign < a.sign, | ||
outputSqrt.exponent < (shiftedExp + deBiasAmt), | ||
outputSqrt.mantissa < fpSqrt.float.mantissa, | ||
]) | ||
]) | ||
]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,6 +123,13 @@ class FixedPoint extends Logic { | |
} | ||
} | ||
|
||
/// Multiply | ||
Logic fpMultiply(dynamic other) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can either move this directly into the implementation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This I will probably need some Dart education on. I like how we can use |
||
_verifyCompatible(other); | ||
final product = Multiply(this, other).out; | ||
return FixedPoint.of(product, signed: false, m: product.width - n, n: n); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since Something to consider: do we want This is probably beyond the scope of this PR, but since this change is here and you're the first user of this new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, I spent a lot of time on this last night. You do want your Fixed point to grow, or you loose A LOT of precision. Specifically, when we multiply to I'm hesitant to force it to the same size as the incoming products because you just loose so much information. Instead of opting to give precise precision to the user to then truncate as they want (or make an override to do this?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did my math here wrong (darn being out of practice for so long). It is still 2*N-1 but the results are There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So for example, if we do 3.5 * 1.25 we expect 4.375. These numbers are (11.10 *01.01) = 0100.0110. If we truncate that from the Q4.4 format back to Q2.2 format we get 00.01 which is 0.5 and inaccurate. So I DO need to fix this to be |
||
} | ||
|
||
/// Greater-than. | ||
@override | ||
Logic operator >(dynamic other) => gt(other); | ||
|
@@ -131,6 +138,10 @@ class FixedPoint extends Logic { | |
@override | ||
Logic operator >=(dynamic other) => gte(other); | ||
|
||
/// multiply | ||
@override | ||
Logic operator *(dynamic other) => fpMultiply(other); | ||
|
||
@override | ||
Logic eq(dynamic other) { | ||
_verifyCompatible(other); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this
library
keyword here has no effect and is unnecessary? Or is there a reason?