Skip to content
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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/components/fixed_point.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

View workflow job for this annotation

GitHub Actions / Run Checks

doc/components/fixed_point.md:31:373 MD047/single-trailing-newline Files should end with a single newline character https://github.com/DavidAnson/markdownlint/blob/v0.29.0/doc/md047.md
11 changes: 11 additions & 0 deletions doc/components/floating_point.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ Currently, the [FloatingPointAdderSimple](https://intel.github.io/rohd-hcl/rohd_

A second [FloatingPointAdderRound](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointAdderRound-class.html) component is available which does perform rounding. It is based on "Delay-Optimized Implementation of IEEE Floating-Point Addition", by Peter-Michael Seidel and Guy Even, using an R-path and an N-path to process far-apart exponents and use rounding and an N-path for exponents within 2 and subtraction, which is exact. If you pass in an optional clock, a pipe stage will be added to help optimize frequency; an optional reset and enable are can control the pipe stage.

## FloatingPointSqrt

A very basic [FloatingPointSqrtSimple] component is available which does not perform any
rounding. It also only operates on variable mantissas of an odd value (1,3,5,etc) but these odd mantissas can be of variable length up to 51. It takes one
[FloatingPoint](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint-class.html) [LogicStructure](https://intel.github.io/rohd/rohd/LogicStructure-class.html) and
performs a square root on it, returning the [FloatingPoint](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint-class.html) value on the output.

Currently, the [FloatingPointSqrtSimple](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointSqrtSimple-class.html) is close in accuracy (as it has no rounding) and is not
optimized for circuit performance, but provides the key functionalities of floating-point square root. Still, this component is a starting point for more realistic
floating-point components that leverage the the logical [FloatingPoint](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint-class.html) and literal [FloatingPointValue](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointValue-class.html) type abstractions.

## FloatingPointMultiplier

A very basic [FloatingPointMultiplierSimple] component is available which does not perform any rounding. It takes two [FloatingPoint](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint-class.html) [LogicStructure](https://intel.github.io/rohd/rohd/LogicStructure-class.html)s and multiplies them, returning a normalized [FloatingPoint](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPoint-class.html) on the output 'product'.
Expand Down
1 change: 1 addition & 0 deletions lib/src/arithmetic/arithmetic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export 'arithmetic_utils.dart';
export 'carry_save_mutiplier.dart';
export 'compound_adder.dart';
export 'divider.dart';
export 'fixed_sqrt.dart';
export 'fixed_to_float.dart';
export 'float_to_fixed.dart';
export 'floating_point/floating_point.dart';
Expand Down
100 changes: 100 additions & 0 deletions lib/src/arithmetic/fixed_sqrt.dart
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;
Copy link
Contributor

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?


import 'package:meta/meta.dart';
import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/rohd_hcl.dart';

/// Abstract base class
Copy link
Contributor

Choose a reason for hiding this comment

The 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

some tips: https://dart.dev/effective-dart/documentation

abstract class FixedPointSqrtBase extends Module {
/// Width of the input and output fields.
final int numWidth;
Copy link
Contributor

Choose a reason for hiding this comment

The 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'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why the name "sqrtF" rather than just "sqrt"?

Choose a reason for hiding this comment

The 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);
}
}
2 changes: 2 additions & 0 deletions lib/src/arithmetic/floating_point/floating_point.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export 'floating_point_converter.dart';
export 'floating_point_multiplier.dart';
export 'floating_point_multiplier_simple.dart';
export 'floating_point_rounding.dart';
export 'floating_point_sqrt.dart';
export 'floating_point_sqrt_simple.dart';
export 'floating_point_utilities.dart';
83 changes: 83 additions & 0 deletions lib/src/arithmetic/floating_point/floating_point_sqrt.dart
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the clk and reset used anywhere? No sequentials in here (yet?)? Did you mean to use it or is this plumbed for future upgrades?

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused by this setup here with error and errorSig. Since the error is already a single-bit Logic, there's no need for this style with an internal and external version of the signal, like there is with the "casting" of LogicStructures. Unless I'm misunderstanding, I think you can just do a single late final Logic error = output('error'); and use it for both internal and external purposes

Choose a reason for hiding this comment

The 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);
}
102 changes: 102 additions & 0 deletions lib/src/arithmetic/floating_point/floating_point_sqrt_simple.dart
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(
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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();
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

File('name.sv').writeAsStringSync(mod.generateSynth());

Choose a reason for hiding this comment

The 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,
])
])
]);
}
}
11 changes: 11 additions & 0 deletions lib/src/arithmetic/signals/fixed_point_logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ class FixedPoint extends Logic {
}
}

/// Multiply
Logic fpMultiply(dynamic other) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can either move this directly into the implementation of operator *, or at least make this private. If you keep it as a separate function, why not just _multiply since it's already applicable to FixedPoint?

Choose a reason for hiding this comment

The 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 .eq() and .lte() methods or the override. That could just be my preference so not married to it. Would like to set it private though.

_verifyCompatible(other);
final product = Multiply(this, other).out;
return FixedPoint.of(product, signed: false, m: product.width - n, n: n);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since Multiply has the same output width as inputs, product.width - n should be the same as m right? Or is it 1 bigger due to sign bit (if signed)?

Something to consider: do we want FixedPoint to widen or keep the same width when multiplied by another FixedPoint? To keep with API consistency, it seems reasonable to keep the same width, since FixedPoint is a type of Logic. But do we need a way to appropriately "expand" a FixedPoint, sort of like a signExtend but that makes it the "right" shape (m and n) for an output of a multiplication?

This is probably beyond the scope of this PR, but since this change is here and you're the first user of this new *, thought I'd ask for thoughts :)

Choose a reason for hiding this comment

The 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 N values we end up with 2*N - 1 result. Because our n doesn't move, that means our m grows by the difference of the output width and n: m = total_width - n. So you could sign extend, but you will lose data. If you look at most multipliers in general your product usually grows with it. You can than leave it to the user to truncate how they will.

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?)

Choose a reason for hiding this comment

The 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 2*m and 2*n. So yes, we either need to sign extend or not. Going to push a fix for that.

Choose a reason for hiding this comment

The 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 m: 2*m, n: 2*n but I think we should keep it widened. (Never not double your check after coding late at night so I'm glad you called this out).

}

/// Greater-than.
@override
Logic operator >(dynamic other) => gt(other);
Expand All @@ -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);
Expand Down
Loading
Loading