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

FPU rounding module #728

Merged
merged 18 commits into from
Nov 12, 2024
Merged

Conversation

Durchbruchswagen
Copy link
Contributor

Implementation of the rounding module for FPU.

This module excepts that input is in normalized form (unless the number is subnormal) and, in accordance with RISC-V specification, detects tininess after rounding. The flag that allows generation of a rounding module that assumes that input was already rounded is to be used by modules that are able to perform both their operation and rounding at the same time (adder/subtractor, for example). In that case, this module only checks for errors and special cases (one of the inputs was inf/nan).

coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py Outdated Show resolved Hide resolved
coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py Outdated Show resolved Hide resolved
]


class FPUrounding(Component):
Copy link
Member

Choose a reason for hiding this comment

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

Docstring would be helpful

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, added docstring

Comment on lines 80 to 121
self.rtval = {}
self.max_exp = C(
2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 1,
unsigned(self.fpu_rounding_params.fpu_params.exp_width),
)
self.max_normal_exp = C(
2 ** (self.fpu_rounding_params.fpu_params.exp_width) - 2,
unsigned(self.fpu_rounding_params.fpu_params.exp_width),
)
self.quiet_nan = C(
2 ** (self.fpu_rounding_params.fpu_params.sig_width - 1),
unsigned(self.fpu_rounding_params.fpu_params.sig_width),
)
self.max_sig = C(
2 ** (self.fpu_rounding_params.fpu_params.sig_width) - 1,
unsigned(self.fpu_rounding_params.fpu_params.sig_width),
)
self.add_one = Signal()
self.inc_rtnte = Signal()
self.inc_rtnta = Signal()
self.inc_rtpi = Signal()
self.inc_rtmi = Signal()

self.rounded_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width + 1)
self.normalised_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width)
self.rounded_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width + 1)

self.final_guard_bit = Signal()
self.final_sticky_bit = Signal()

self.overflow = Signal()
self.underflow = Signal()
self.inexact = Signal()
self.tininess = Signal()
self.is_inf = Signal()
self.is_nan = Signal()
self.input_not_special = Signal()
self.rounded_inexact = Signal()

self.final_exp = Signal(self.fpu_rounding_params.fpu_params.exp_width)
self.final_sig = Signal(self.fpu_rounding_params.fpu_params.sig_width)
self.final_sign = Signal()
Copy link
Member

Choose a reason for hiding this comment

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

those are module internal signals/constants, there is no need to make them public. definitions could be placed in elaborate code and without self.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

("sign", 1),
("sig", fpu_params.sig_width),
("exp", fpu_params.exp_width),
("guard_bit", 1),
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean round_bit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, my bad. After reading a few papers, it can get a little bit confusing, but most of the books and papers define this bit as a round bit.

coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py Outdated Show resolved Hide resolved
test/func_blocks/fu/test_fpu_rounding.py Outdated Show resolved Hide resolved
coreblocks/func_blocks/fu/fpu/fpu_rounding_module.py Outdated Show resolved Hide resolved
Comment on lines 237 to 238
with m.If((self.rounded_exp == 0) & (self.normalised_sig[-1] == 1)):
m.d.comb += self.final_exp.eq(1)
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain this condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the corner case for rounding subnormal numbers. Basically, in IEEE 754 standard, the 0 and 1 exponents represent the same exponent exp_min (1-bias if I remember correctly). The difference is that exp = 1 indicates that the number is normal (the implicit bit is equal to 1) and exp = 0 indicates that the number is in denormalized form (the implicit bit is equal to 0). The numbers with exp = 0 are called subnormal (except 0). There are 3 possible results of rounding for a subnormal number: zero, subnormal number or smallest normal number. This condition is for the last case. I basically check if exp == 0 and if the msb bit (the one that is implicit) is equal to one. In that case, I have to change the exponent to 1 to indicate that the number is now normal.

Comment on lines 130 to 140
input_values_dict["exp"] = help_values.max_exp
input_values_dict["sig"] = help_values.qnan
input_values_dict["invalid_operation"] = 1
input_values_dict["division_by_zero"] = 0

resp = yield from request_adapter.call(input_values_dict)

assert resp["sign"] == input_values_dict["sign"]
assert resp["exp"] == input_values_dict["exp"]
assert resp["sig"] == input_values_dict["sig"]
assert resp["errors"] == 1
Copy link
Member

Choose a reason for hiding this comment

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

There is a lot of needlessly duplicated code here, which would make maintenance harder. You should use a loop over a list of test cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, there are now two arrays, one with input data and one with expected results, that are looped.

@Durchbruchswagen Durchbruchswagen requested a review from tilk October 7, 2024 14:10
Copy link
Member

@piotro888 piotro888 left a comment

Choose a reason for hiding this comment

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

Sorry for late review

("sign", 1),
("sig", fpu_params.sig_width),
("exp", fpu_params.exp_width),
("errors", 5),
Copy link
Member

Choose a reason for hiding this comment

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

Please use IntFlag enum as errors shape and when referencing them

]


class FPUrounding(Elaboratable):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
class FPUrounding(Elaboratable):
class FPURounding(Elaboratable):

typo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, my bad. Typo is now fixed.

"""

def __init__(self, *, fpu_params: FPUParams):
self.error_in_layout = [
Copy link
Member

Choose a reason for hiding this comment

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

Usage of input_inf as in input to previous stages is not obvious by name, comment about that here could be helpful for future.

Copy link
Member

Choose a reason for hiding this comment

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

Please resolve comment from previous review

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for the late response. I pushed changes at around 3 a.m. and went to sleep with a plan to resolve comments next day, but by then you had already reviewed them. I was also thinking if maybe a better solution would be to write about this flag in some class for input layout for modules that perform arithmetic operations or something like that, but probably a small comment about why an error module needs it is warranted, so I added it.

Copy link
Member

Choose a reason for hiding this comment

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

I thought it was finished, sorry!



class FPUParams:
"""FPU parameters
Copy link
Member

Choose a reason for hiding this comment

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

Same thing here with documentation: it would be helpful to add information that sig_width contains the implicit bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@tilk
Copy link
Member

tilk commented Nov 5, 2024

Waiting with merge until reviews are addressed.

@tilk tilk added the enhancement New feature or request label Nov 5, 2024
Comment on lines 12 to 17
class Errors(enum.IntFlag, shape=5):
INVALID_OPERATION = 0
DIVISION_BY_ZERO = 1
OVERFLOW = 2
UNDERFLOW = 3
INEXACT = 4
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
class Errors(enum.IntFlag, shape=5):
INVALID_OPERATION = 0
DIVISION_BY_ZERO = 1
OVERFLOW = 2
UNDERFLOW = 3
INEXACT = 4
class Errors(enum.IntFlag):
INVALID_OPERATION = auto()
DIVISION_BY_ZERO = auto()
OVERFLOW = auto()
UNDERFLOW = auto()
INEXACT = auto()

IntFlag is supposed to be used as bit flag value, and not bit index! (this is why shape was not working)

auto() can be used on IntFlags resolve to next power of two flags so INVALID_OPERATION=1 DIVISION_BY_ZERO=2 OVERFLOW=4
Shape should not be needed if used as flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad, I hadn't thought about assigning powers of 2 to enums. Changed Errors according to your suggestion

("sign", 1),
("sig", fpu_params.sig_width),
("exp", fpu_params.exp_width),
("errors", Shape.cast(Errors)),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
("errors", Shape.cast(Errors)),
("errors", Errors),

with change from previous comment it can be written without workaround

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

# division by zero
{"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 2},
# overflow but no round and sticky bits
{"sign": 0, "sig": 0, "exp": help_values.max_exp, "errors": 20},
Copy link
Member

Choose a reason for hiding this comment

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

Magic numbers from errors can be now replaced with simply ORing Error fields

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines 156 to 160
m.d.av_comb += final_errors[Errors.INVALID_OPERATION].eq(arg.invalid_operation)
m.d.av_comb += final_errors[Errors.DIVISION_BY_ZERO].eq(arg.division_by_zero)
m.d.av_comb += final_errors[Errors.OVERFLOW].eq(overflow)
m.d.av_comb += final_errors[Errors.UNDERFLOW].eq(underflow)
m.d.av_comb += final_errors[Errors.INEXACT].eq(inexact)
Copy link
Member

Choose a reason for hiding this comment

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

final_errors must be now constructed from ORing Error fields.
I think it can be done like that for now final_errors = Mux(arg.invalid_operation, Error.INVALID_OPERATION, 0) | .....

Extra note:
It is a common use case, and it cannot be done too cleanly, so we should probably later create some helper method in transactron library for that usage pattern or maybe suggest feature to amaranth enums?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

"""

def __init__(self, *, fpu_params: FPUParams):
self.error_in_layout = [
Copy link
Member

Choose a reason for hiding this comment

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

Please resolve comment from previous review

@tilk tilk merged commit 34565a3 into kuznia-rdzeni:master Nov 12, 2024
13 checks passed
github-actions bot pushed a commit that referenced this pull request Nov 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants