From 0c4c64f0d4b2f2f1fb362ba4a0dce4f496bdb511 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 | 32 +++++-- tools/site_cobble/rdl_pkg/models.py | 96 +++++++++++++++---- .../rdl_pkg/templates/regpkg_vhdl.jinja2 | 34 +++++++ 3 files changed, 140 insertions(+), 22 deletions(-) diff --git a/tools/site_cobble/rdl_pkg/listeners.py b/tools/site_cobble/rdl_pkg/listeners.py index e0dfb8bb..317906f0 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_by_known_type = {} # A dictionary for looking up reset values by their known-type names self.cur_reg = None self.registers = [] @@ -104,11 +105,6 @@ def exit_Regfile(self, node: RegfileNode) -> None: self.prefix_stack.pop() def enter_Reg(self, node: RegNode) -> None: - # print(f"orig type name: {node.orig_type_name}") - # print(f"new type name: {node.type_name}") - # print(f"segment: {node.get_path_segment()}") - # print(f"Enter reg: {node.inst_name}") - # print(f"stack: {self.prefix_stack}") # 2 cases here: # node.orig_type_name is None, use node.type_name # @@ -133,9 +129,33 @@ def exit_Reg(self, node): is then appended list of registers to be used in generation of design collateral. """ - # 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_by_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_by_known_type.get(self.cur_reg.type_name) + reg_reset = self.cur_reg.elaborated_reset + + # base reset is None, we'll allow None, 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 or reg_reset is None): + 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..9df555f6 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 @@ -210,11 +250,11 @@ def text_bitslice_str(self) -> str: return f"{self.high}..{self.low}" @property - def width(self): + def width(self) -> int: return (self.high - self.low) + 1 @property - def mask(self): + def mask(self) -> int: return ((1 << self.width) - 1) << self.low def get_property(self, *args, **kwargs): @@ -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..0b7ffca5 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