From bb3dec27efc70c058dbbe7754425d5d489b48c52 Mon Sep 17 00:00:00 2001 From: Nathanael Huffman Date: Thu, 23 Jan 2025 15:57:11 -0600 Subject: [PATCH] We had various deficiencies around sharing and overriding reset values on registers that share types. This provides the groundwork for better functioning here and provides error messages when the user asks for something we don't support. We also added new reset_0s and reset_1s functions to all register types so long as they don't have enumerated types. This provides some additional easy knobs to get alternate reset values for your register types. --- tools/site_cobble/rdl_pkg/listeners.py | 26 ++++++ tools/site_cobble/rdl_pkg/models.py | 92 ++++++++++++++++--- .../rdl_pkg/templates/regpkg_vhdl.jinja2 | 34 +++++++ 3 files changed, 138 insertions(+), 14 deletions(-) diff --git a/tools/site_cobble/rdl_pkg/listeners.py b/tools/site_cobble/rdl_pkg/listeners.py index e0dfb8bb..36f68d4c 100644 --- a/tools/site_cobble/rdl_pkg/listeners.py +++ b/tools/site_cobble/rdl_pkg/listeners.py @@ -78,6 +78,7 @@ class BaseListener(RDLListener): def __init__(self): self.prefix_stack = [] self.known_types = [] + self.reset_byte_known_type = {} self.cur_reg = None self.registers = [] @@ -136,6 +137,31 @@ def exit_Reg(self, node): # print(f"Exit reg: {node.inst_name}") self.cur_reg.elaborate() self.registers.append(self.cur_reg) + # If not a repeated type, put the reset value which we now know into a dictionary lookup + if not self.cur_reg.repeated_type: + self.reset_byte_known_type[self.cur_reg.type_name] = self.cur_reg.elaborated_reset, self.cur_reg.prefixed_name + else: + # If it was a repeated type, check that the reset value here matches the default reset value + base_reset, base_name = self.reset_byte_known_type.get(self.cur_reg.type_name) + reg_reset = self.cur_reg.elaborated_reset + + # base reset is None, we'll allow 1s or 0s, nothing else. Anything else is an error + if base_reset is None and not (self.cur_reg.reset_is_all_1s or self.cur_reg.reset_is_all_0s): + raise ValueError( + f"Reset value '{reg_reset}'for register {self.cur_reg.prefixed_name} is not all 1s or all 0s, " + "and there's no default reset value for the type. The RDL subsystem doesn't support this pattern. " + "Please check that you can't use a default value for the type, or elide the special reset value" + " from the RDL and implement it in your own logic. You may also file an enhancement request to " + "figure out how to better support this pattern." + ) + elif reg_reset != base_reset and base_reset is not None: + raise ValueError( + f"Reset value '{reg_reset}' for register {self.cur_reg.prefixed_name} does not match reset value '{base_reset}'" + f" already defined for that type: {base_name}.\n" + "This is likely due to a reset value override on an instance of the type that is conflicting " + f"with a default value on the shared type '{self.cur_reg.type_name}'" + + ) self.cur_reg = None def enter_Field(self, node) -> None: diff --git a/tools/site_cobble/rdl_pkg/models.py b/tools/site_cobble/rdl_pkg/models.py index b76b6853..85932bb1 100644 --- a/tools/site_cobble/rdl_pkg/models.py +++ b/tools/site_cobble/rdl_pkg/models.py @@ -20,8 +20,8 @@ class DuplicateEnumNameError(Exception): class BaseModel: @classmethod - def from_node(cls, node: RegNode, prefix_stack, repeated_type=False): - return cls(node=node, prefix_stack=prefix_stack, repeated_type=repeated_type) + def from_node(cls, node: RegNode, prefix_stack, repeated_type=False, base_reset=None): + return cls(node=node, prefix_stack=prefix_stack, repeated_type=repeated_type, base_reset=base_reset) def __init__(self, **kwargs): self.prefix = copy.deepcopy(kwargs.pop("prefix_stack")) @@ -40,11 +40,11 @@ def __init__(self, **kwargs): self._max_field_name_chars = 0 @property - def prefixed_name(self): + def prefixed_name(self) -> str: return "_".join(self.prefix) + "_" + self.node.get_path_segment() @property - def name(self): + def name(self) -> str: # We're generating address maps but we can skip the first address map name, but we want the rest of the elaboration return ( "_".join(self.prefix[1:]) + "_" + self.node.get_path_segment() @@ -111,27 +111,67 @@ def __init__(self, **kwargs): super().__init__(**kwargs) @property - def used_bits(self): + def used_bits(self) -> int: return sum([x.width for x in self.packed_fields]) @property - def packed_fields(self): + def packed_fields(self) -> List["Field"]: """ Returns all the defined register fields, skipping any ReservedFields (undefined spaces) """ return [x for x in self.fields if not isinstance(x, ReservedField)] @property - def encoded_fields(self): + def encoded_fields(self) -> List["Field"]: """ Returns any register fields that have encodings """ return [x for x in self.packed_fields if x.has_encode()] + + @property + def has_encoded_fields(self) -> bool: + """ + True if any fields have an encoding, meaning we'll generate enumerated types for them + """ + return len(self.encoded_fields) > 0 + + @property + def reset_is_all_1s(self) -> bool: + """ + True if all defined, non-reserved fields have a reset value of all 1s + """ + for field in self.fields: + rst_prop = field.get_property("reset") + if (not isinstance(field, ReservedField) and + (rst_prop is not None) and + rst_prop != field.reset_1s): + return False + return True + + @property + def reset_is_all_0s(self) -> bool: + """ + True if all defined, non-reserved fields have a reset value of all 0s + """ + return self.elaborated_reset == 0 + + @property + def elaborated_reset(self) -> int: + """ + Returns the combined reset value of the register post elaboration, if it has one. + """ + if not self.has_reset_definition: + return None + reset_val = 0 + for field in self.fields: + if (field.get_property("reset") is not None) and (not isinstance(field, ReservedField)): + reset_val |= field.get_property("reset") << field.low + return reset_val @property - def has_reset_definition(self): + def has_reset_definition(self) -> bool: """ - RDS doesn't force a reset definition on registers but we may want to conditionally generate + RDL doesn't force a reset definition on registers but we may want to conditionally generate reset logic if a reset value was specified. """ # Get the reset value for all the fields. If we don't see None in any of them we have defined reset behavior @@ -142,7 +182,7 @@ def has_reset_definition(self): ] return False if None in a else True - def elaborate(self): + def elaborate(self) -> None: """ Register elaboration consists of sorting the defined fields by the low index of the field. We then loop through the fields and @@ -177,7 +217,7 @@ def elaborate(self): # Combine fields and re-sort, leaving us with a completely specified register self.fields = sorted(self.fields + gaps, key=lambda x: x.low, reverse=True) - def format_field_name(self, name): + def format_field_name(self, name) -> str: """ To nicely generate aligned outputs, it's handy to know the max length of the names of fields on a per-register basis, this function @@ -225,20 +265,44 @@ def get_property(self, *args, **kwargs): return prop @property - def reset_str(self): + def reset_str(self) -> str: my_rst = self.node.get_property("reset") return "{:#0x}".format(my_rst) if my_rst is not None else "None" @property - def vhdl_reset_or_default(self): + def vhdl_reset_or_default(self) -> str: my_rst = self.node.get_property("reset") reset_val = 0 if my_rst is None else my_rst if self.width > 1: return f'{self.width}x"{reset_val:X}"' else: return f"'{reset_val:1X}'" + + @property + def reset_1s(self) -> int: + if self.width > 1: + return (1 << self.width) - 1 + else: + return 1 + + @property + def vhdl_reset_1s(self) -> str: + if self.width > 1: + reset_val = (1 << self.width) - 1 + return f'{self.width}x"{reset_val:X}"' + else: + reset_val = 1 + return f"'{reset_val:1X}'" + + @property + def vhdl_reset_0s(self) -> str: + reset_val = 0 + if self.width > 1: + return f'{self.width}x"{reset_val:X}"' + else: + return f"'{reset_val:1X}'" - def has_encode(self): + def has_encode(self) -> bool: return False def __str__(self): diff --git a/tools/site_cobble/rdl_pkg/templates/regpkg_vhdl.jinja2 b/tools/site_cobble/rdl_pkg/templates/regpkg_vhdl.jinja2 index d76e713f..72fc8adc 100644 --- a/tools/site_cobble/rdl_pkg/templates/regpkg_vhdl.jinja2 +++ b/tools/site_cobble/rdl_pkg/templates/regpkg_vhdl.jinja2 @@ -22,6 +22,10 @@ -- sizeof() returns an integer of the number of used bits in the register -- rec_reset abusing overload signatures to return the reset value for the -- register type as defined in the RDL +-- reset_1s abusing overload signatures to return the reset value for the +-- register type with all defined bits set to 1 +-- reset_0s abusing overload signatures to return the reset value for the +-- register type as defined bits set to 0 -- "or","and", "xor" we provide overloads for logical bitwise functions for -- the record type with itself on both sides, it on the left side with an -- slv on the right, and it on the left side with an unsigned on the right. @@ -102,7 +106,15 @@ package {{module_name}} is function compress (rec : {{reg_type_name}}) return std_logic_vector; function uncompress (vec : std_logic_vector) return {{reg_type_name}}; function sizeof (rec : {{reg_type_name}}) return integer; + {# Only generate rec_reset if resets were specified #} + {% if register.has_reset_definition %} function rec_reset return {{reg_type_name}}; + {% endif %} + {# We can only generate reset1's and reset0's for non-enumerated types #} + {% if not register.has_encoded_fields %} + function reset_1s return {{reg_type_name}}; + function reset_0s return {{reg_type_name}}; + {% endif %} function "or" (left, right : {{reg_type_name}}) return {{reg_type_name}}; function "or" (left : {{reg_type_name}}; right : std_logic_vector) return {{reg_type_name}}; function "or" (left : {{reg_type_name}}; right : unsigned) return {{reg_type_name}}; @@ -227,6 +239,8 @@ package body {{module_name}} is begin return {{register.used_bits}}; end sizeof; + {# Only generate rec_reset if resets were specified #} + {% if register.has_reset_definition %} function rec_reset return {{reg_type_name}} is variable ret_rec : {{reg_type_name}}; begin @@ -239,6 +253,26 @@ package body {{module_name}} is {% endfor %} return ret_rec; end rec_reset; + {% endif %} + {# We can only generate reset1's and reset0's for non-enumerated types #} + {% if not register.has_encoded_fields %} + function reset_1s return {{reg_type_name}} is + variable ret_rec : {{reg_type_name}}; + begin + {% for field in register.packed_fields %} + ret_rec.{{field.name}} := {{field.vhdl_reset_1s}}; + {% endfor %} + return ret_rec; + end reset_1s; + function reset_0s return {{reg_type_name}} is + variable ret_rec : {{reg_type_name}}; + begin + {% for field in register.packed_fields %} + ret_rec.{{field.name}} := {{field.vhdl_reset_0s}}; + {% endfor %} + return ret_rec; + end reset_0s; + {% endif %} function "or" (left, right : {{reg_type_name}}) return {{reg_type_name}} is variable ret_rec : {{reg_type_name}}; begin