Repository: calyxir/calyx (language: rust)

Issue:
Floating-point conversion error
There is a bug in the tests provided by https://github.com/calyxir/calyx/pull/2318.

[tests/correctness/ieee754-float/parse-and-print.futil.data](https://github.com/calyxir/calyx/pull/2318/files#diff-e4146a309a9876672f57eb53e44b7c9042a5a222c53eb6894ff48df0c6a950fd) provides the input value `15.1239`. However, at the end of the test, the value has changed to `15.12` in [tests/correctness/ieee754-float/parse-and-print.expect](https://github.com/calyxir/calyx/pull/2318/files#diff-5d85d972c197f71bd7796802102b6e470a03f2b8cf64f277a0f8208dffcaca99).

This is even though a 32-bit IEEE float should be able to represent `15.1239`. So there appears to be a bug, most likely in how the data is converted back after the simulation.


Discussion / hints:
Argh, thanks for catching this! Also tagging @jiahanxie353 in case he can get to this before me.
Probably an easy fix? Best guess is that the problem comes from this line:
https://github.com/calyxir/calyx/blob/6b4896976f93f7a913fed46de064f17bc5468b10/fud/fud/stages/verilator/json_to_dat.py#L45

Fix patch:
diff --git a/calyx-py/calyx/numeric_types.py b/calyx-py/calyx/numeric_types.py
index c8277aecff..87a569ad36 100644
--- a/calyx-py/calyx/numeric_types.py
+++ b/calyx-py/calyx/numeric_types.py
@@ -339,12 +339,14 @@ def bitnum_to_fixed(bitnum: Bitnum, int_width: int) -> FixedPoint:
 
 
 @dataclass
-class FloatingPoint(NumericType):
+class IEEE754Float(NumericType):
     """Represents a floating point number."""
 
     def __init__(self, value: str, width: int, is_signed: bool):
         super().__init__(value, width, is_signed)
 
+        assert width in [32, 64], "Floating point numbers must be either 32 or 64 bits."
+
         if self.bit_string_repr is None and self.hex_string_repr is None:
             # The decimal representation was passed in.
             packed = struct.pack("!f", float(self.string_repr))
@@ -353,8 +355,15 @@ def __init__(self, value: str, width: int, is_signed: bool):
             self.uint_repr = int(self.bit_string_repr, 2)
             self.hex_string_repr = np.base_repr(self.uint_repr, 16)
 
-    def to_dec(self, round_place: int):
+    def as_str(self):
         float_value = struct.unpack(
             "!f", int(self.bit_string_repr, 2).to_bytes(4, byteorder="big")
         )[0]
-        return round(float_value, round_place)
+        if self.width == 32:
+            return str(np.float32(float_value))
+        elif self.width == 64:
+            return str(np.float64(float_value))
+        else:
+            raise InvalidNumericType(
+                f"Unsupported width: {self.width} for floating point."
+            )
diff --git a/fud/fud/stages/verilator/json_to_dat.py b/fud/fud/stages/verilator/json_to_dat.py
index 97847af3b2..933511e3f0 100644
--- a/fud/fud/stages/verilator/json_to_dat.py
+++ b/fud/fud/stages/verilator/json_to_dat.py
@@ -1,9 +1,11 @@
-import simplejson as sjson
-import numpy as np
-from calyx.numeric_types import FixedPoint, Bitnum, FloatingPoint, InvalidNumericType
+import logging as log
 from pathlib import Path
+
+import numpy as np
+import simplejson as sjson
+from calyx.numeric_types import Bitnum, FixedPoint, IEEE754Float, InvalidNumericType
+
 from fud.errors import Malformed
-import logging as log
 
 
 def float_to_fixed(value: float, N: int) -> float:
@@ -41,8 +43,8 @@ def parse(hex_value: str):
             else:
                 return int(bn.str_value())
         else:
-            fp = FloatingPoint(hex_value, **args)
-            return fp.to_dec(round_place=2)
+            fp = IEEE754Float(hex_value, **args)
+            return float(fp.as_str())
 
     with path.open("r") as f:
         lines = []
@@ -95,13 +97,15 @@ def provided(x, y):
 
 def convert(x, round: bool, is_signed: bool, width: int, is_bn: bool, int_width=None):
     with_prefix = False
+
     # If `int_width` is not defined, then this is a `Bitnum`
     if int_width is None:
         if is_bn:
             return Bitnum(x, width, is_signed).hex_string(with_prefix)
         else:
-            return FloatingPoint(x, width, is_signed).hex_string(with_prefix)
+            return IEEE754Float(x, width, is_signed).hex_string(with_prefix)
 
+    # Otherwise, this is a fixed-point number.
     try:
         return FixedPoint(x, width, int_width, is_signed).hex_string(with_prefix)
     except InvalidNumericType as error:


Test patch:
diff --git a/tests/correctness/ieee754-float/parse-and-print.expect b/tests/correctness/ieee754-float/parse-and-print.expect
index 77a7ee76bf..54d617922a 100644
--- a/tests/correctness/ieee754-float/parse-and-print.expect
+++ b/tests/correctness/ieee754-float/parse-and-print.expect
@@ -1,9 +1,9 @@
 {
   "inp": [
-    15.12
+    15.1239
   ],
   "out": [
-    15.12,
+    15.1239,
     0.56
   ]
 }