Add signed/fixedpoint properties (#140)

* declared intwidth, fracwidth, and is_signed UDPs

* fix boolean type name in UDP definition

* generate hwif fields with fixedpoint indices

* make "counter" and "encode" properties mutualy exclusive with signed/fixedpoint

* add signed/unsigned to hwif

* improved fixedpoint error messages, added validation tests

* added fixedpoint tests

* fixedpoint/signed not allowed for signal components

* added signed/fixedpoint UDP docs

* handle single-bit fixedpoint numbers

* fix too many positional arguments lint

* changed spelling of fixedpoint to fixed-point

* use "logic" in place of "unsigned logic"

* split signed and fixedpoint docs, added examples

* allow enums with is_signed=false

* split signed and fixedpoint implementations

* assorted nits picked

* updated is_signed validation unit test
This commit is contained in:
Dana Sorensen
2025-05-15 09:48:44 -06:00
committed by GitHub
parent 62f66fb7ff
commit d2b4911d5f
20 changed files with 585 additions and 10 deletions

View File

@@ -35,11 +35,11 @@ class HWIFStructGenerator(RDLFlatStructGenerator):
super().pop_struct()
self.hwif_report_stack.pop()
def add_member(self, name: str, width: int = 1) -> None: # type: ignore # pylint: disable=arguments-differ
super().add_member(name, width)
def add_member(self, name: str, width: int = 1, *, lsb: int = 0, signed: bool = False) -> None: # type: ignore # pylint: disable=arguments-differ
super().add_member(name, width, lsb=lsb, signed=signed)
if width > 1:
suffix = f"[{width-1}:0]"
if width > 1 or lsb != 0:
suffix = f"[{lsb+width-1}:{lsb}]"
else:
suffix = ""
@@ -145,7 +145,14 @@ class InputStructGenerator_Hier(HWIFStructGenerator):
# Provide input to field's next value if it is writable by hw, and it
# was not overridden by the 'next' property
if node.is_hw_writable and node.get_property('next') is None:
self.add_member("next", node.width)
# Get the field's LSB index (can be nonzero for fixed-point values)
fracwidth = node.get_property("fracwidth")
lsb = 0 if fracwidth is None else -fracwidth
# get the signedness of the field
signed = node.get_property("is_signed")
self.add_member("next", node.width, lsb=lsb, signed=signed)
# Generate implied inputs
for prop_name in ["we", "wel", "swwe", "swwel", "hwclr", "hwset"]:
@@ -271,7 +278,14 @@ class OutputStructGenerator_Hier(HWIFStructGenerator):
# Expose field's value if it is readable by hw
if node.is_hw_readable:
self.add_member("value", node.width)
# Get the node's LSB index (can be nonzero for fixed-point values)
fracwidth = node.get_property("fracwidth")
lsb = 0 if fracwidth is None else -fracwidth
# get the signedness of the field
signed = node.get_property("is_signed")
self.add_member("value", node.width, lsb=lsb, signed=signed)
# Generate output bit signals enabled via property
for prop_name in ["anded", "ored", "xored", "swmod", "swacc", "overflow", "underflow", "rd_swacc", "wr_swacc"]:

View File

@@ -88,16 +88,30 @@ class StructGenerator:
self._struct_stack.append(s)
def add_member(self, name: str, width: int = 1, array_dimensions: Optional[List[int]] = None) -> None:
def add_member(
self,
name: str,
width: int = 1,
array_dimensions: Optional[List[int]] = None,
*,
lsb: int = 0,
signed: bool = False,
) -> None:
if array_dimensions:
suffix = "[" + "][".join((str(n) for n in array_dimensions)) + "]"
else:
suffix = ""
if width == 1:
m = f"logic {name}{suffix};"
if signed:
sign = "signed "
else:
m = f"logic [{width-1}:0] {name}{suffix};"
# the default 'logic' type is unsigned per SV LRM 6.11.3
sign = ""
if width == 1 and lsb == 0:
m = f"logic {sign}{name}{suffix};"
else:
m = f"logic {sign}[{lsb+width-1}:{lsb}] {name}{suffix};"
self.current_struct.children.append(m)

View File

@@ -1,6 +1,8 @@
from .rw_buffering import BufferWrites, WBufferTrigger
from .rw_buffering import BufferReads, RBufferTrigger
from .extended_swacc import ReadSwacc, WriteSwacc
from .fixedpoint import IntWidth, FracWidth
from .signed import IsSigned
ALL_UDPS = [
BufferWrites,
@@ -9,4 +11,7 @@ ALL_UDPS = [
RBufferTrigger,
ReadSwacc,
WriteSwacc,
IntWidth,
FracWidth,
IsSigned,
]

View File

@@ -0,0 +1,73 @@
from typing import Any
from systemrdl.component import Field
from systemrdl.node import Node, FieldNode
from systemrdl.udp import UDPDefinition
class _FixedpointWidth(UDPDefinition):
valid_components = {Field}
valid_type = int
def validate(self, node: "Node", value: Any) -> None:
assert isinstance(node, FieldNode)
intwidth = node.get_property("intwidth")
fracwidth = node.get_property("fracwidth")
assert intwidth is not None
assert fracwidth is not None
prop_ref = node.inst.property_src_ref.get(self.name)
# incompatible with "counter" fields
if node.get_property("counter"):
self.msg.error(
"Fixed-point representations are not supported for counter fields.",
prop_ref
)
# incompatible with "encode" fields
if node.get_property("encode") is not None:
self.msg.error(
"Fixed-point representations are not supported for fields encoded as an enum.",
prop_ref
)
# ensure node width = fracwidth + intwidth
if intwidth + fracwidth != node.width:
self.msg.error(
f"Number of integer bits ({intwidth}) plus number of fractional bits ({fracwidth})"
f" must be equal to the width of the component ({node.width}).",
prop_ref
)
class IntWidth(_FixedpointWidth):
name = "intwidth"
def get_unassigned_default(self, node: "Node") -> Any:
"""
If 'fracwidth' is defined, 'intwidth' is inferred from the node width.
"""
assert isinstance(node, FieldNode)
fracwidth = node.get_property("fracwidth", default=None)
if fracwidth is not None:
return node.width - fracwidth
else:
# not a fixed-point number
return None
class FracWidth(_FixedpointWidth):
name = "fracwidth"
def get_unassigned_default(self, node: "Node") -> Any:
"""
If 'intwidth' is defined, 'fracwidth' is inferred from the node width.
"""
assert isinstance(node, FieldNode)
intwidth = node.get_property("intwidth", default=None)
if intwidth is not None:
return node.width - intwidth
else:
# not a fixed-point number
return None

View File

@@ -0,0 +1,33 @@
from typing import Any
from systemrdl.component import Field
from systemrdl.node import Node
from systemrdl.udp import UDPDefinition
class IsSigned(UDPDefinition):
name = "is_signed"
valid_components = {Field}
valid_type = bool
default_assignment = True
def validate(self, node: "Node", value: Any) -> None:
# "counter" fields can not be signed
if value and node.get_property("counter"):
self.msg.error(
"The property is_signed=true is not supported for counter fields.",
node.inst.property_src_ref["is_signed"]
)
# incompatible with "encode" fields
if value and node.get_property("encode") is not None:
self.msg.error(
"The property is_signed=true is not supported for fields encoded as an enum.",
node.inst.property_src_ref["is_signed"]
)
def get_unassigned_default(self, node: "Node") -> Any:
"""
Unsigned by default if not specified.
"""
return False