Skip to content

Commit

Permalink
We had various deficiencies around sharing and overriding reset value…
Browse files Browse the repository at this point in the history
…s 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.
  • Loading branch information
nathanaelhuffman committed Jan 24, 2025
1 parent 3067102 commit bb3dec2
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 14 deletions.
26 changes: 26 additions & 0 deletions tools/site_cobble/rdl_pkg/listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand Down Expand Up @@ -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:
Expand Down
92 changes: 78 additions & 14 deletions tools/site_cobble/rdl_pkg/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
34 changes: 34 additions & 0 deletions tools/site_cobble/rdl_pkg/templates/regpkg_vhdl.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}};
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit bb3dec2

Please sign in to comment.