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