-
Notifications
You must be signed in to change notification settings - Fork 16
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
FPU rounding module #728
Conversation
] | ||
|
||
|
||
class FPUrounding(Component): |
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.
Docstring would be helpful
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.
Done, added docstring
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() |
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.
those are module internal signals/constants, there is no need to make them public. definitions could be placed in elaborate
code and without self.
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.
Done
("sign", 1), | ||
("sig", fpu_params.sig_width), | ||
("exp", fpu_params.exp_width), | ||
("guard_bit", 1), |
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.
Did you mean round_bit
?
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.
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
.
with m.If((self.rounded_exp == 0) & (self.normalised_sig[-1] == 1)): | ||
m.d.comb += self.final_exp.eq(1) |
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.
Can you explain this condition?
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.
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.
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 |
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.
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.
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.
Done, there are now two arrays, one with input data and one with expected results, that are looped.
14fa0fd
to
d519a1b
Compare
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.
Sorry for late review
("sign", 1), | ||
("sig", fpu_params.sig_width), | ||
("exp", fpu_params.exp_width), | ||
("errors", 5), |
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.
Please use IntFlag
enum as errors
shape and when referencing them
] | ||
|
||
|
||
class FPUrounding(Elaboratable): |
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.
class FPUrounding(Elaboratable): | |
class FPURounding(Elaboratable): |
typo?
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.
Yes, my bad. Typo is now fixed.
""" | ||
|
||
def __init__(self, *, fpu_params: FPUParams): | ||
self.error_in_layout = [ |
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.
Usage of input_inf as in input to previous stages is not obvious by name, comment about that here could be helpful for future.
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.
Please resolve comment from previous review
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.
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.
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 thought it was finished, sorry!
|
||
|
||
class FPUParams: | ||
"""FPU parameters |
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.
Same thing here with documentation: it would be helpful to add information that sig_width
contains the implicit bit.
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.
Done
Waiting with merge until reviews are addressed. |
class Errors(enum.IntFlag, shape=5): | ||
INVALID_OPERATION = 0 | ||
DIVISION_BY_ZERO = 1 | ||
OVERFLOW = 2 | ||
UNDERFLOW = 3 | ||
INEXACT = 4 |
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.
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.
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.
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)), |
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.
("errors", Shape.cast(Errors)), | |
("errors", Errors), |
with change from previous comment it can be written without workaround
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.
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}, |
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.
Magic numbers from errors can be now replaced with simply ORing Error
fields
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.
Done
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) |
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.
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?
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.
Done
""" | ||
|
||
def __init__(self, *, fpu_params: FPUParams): | ||
self.error_in_layout = [ |
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.
Please resolve comment from previous review
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).