Add support for wide registers (where accesswidth < regwidth)

This commit is contained in:
Alex Mykyta
2022-10-12 20:44:22 -07:00
parent 21a4e5a41c
commit e07e7d26b2
18 changed files with 687 additions and 206 deletions

View File

@@ -30,21 +30,47 @@ class AddressDecode:
assert s is not None
return s
def get_access_strobe(self, node: Union[RegNode, FieldNode]) -> str:
def get_access_strobe(self, node: Union[RegNode, FieldNode], reduce_substrobes: bool=True) -> str:
"""
Returns the Verilog string that represents the register/field's access strobe.
"""
if isinstance(node, FieldNode):
node = node.parent
field = node
path = get_indexed_path(self.top_node, node.parent)
regwidth = node.parent.get_property('regwidth')
accesswidth = node.parent.get_property('accesswidth')
if regwidth > accesswidth:
# Is wide register.
# Determine the substrobe(s) relevant to this field
sidx_hi = field.msb // accesswidth
sidx_lo = field.lsb // accesswidth
if sidx_hi == sidx_lo:
suffix = f"[{sidx_lo}]"
else:
suffix = f"[{sidx_hi}:{sidx_lo}]"
path += suffix
if sidx_hi != sidx_lo and reduce_substrobes:
return "|decoded_reg_strb." + path
else:
path = get_indexed_path(self.top_node, node)
path = get_indexed_path(self.top_node, node)
return "decoded_reg_strb." + path
class DecodeStructGenerator(RDLStructGenerator):
def enter_Reg(self, node: 'RegNode') -> None:
self.add_member(kwf(node.inst_name), array_dimensions=node.array_dimensions)
# if register is "wide", expand the strobe to be able to access the sub-words
n_subwords = node.get_property("regwidth") // node.get_property("accesswidth")
self.add_member(
kwf(node.inst_name),
width=n_subwords,
array_dimensions=node.array_dimensions,
)
# Stub out
def exit_Reg(self, node: 'RegNode') -> None:
@@ -79,16 +105,26 @@ class DecodeLogicGenerator(RDLForLoopGenerator):
self._array_stride_stack.extend(strides)
def _get_address_str(self, node:AddressableNode) -> str:
a = f"'h{(node.raw_absolute_address - self.addr_decode.top_node.raw_absolute_address):x}"
def _get_address_str(self, node:AddressableNode, subword_offset: int=0) -> str:
a = f"'h{(node.raw_absolute_address - self.addr_decode.top_node.raw_absolute_address + subword_offset):x}"
for i, stride in enumerate(self._array_stride_stack):
a += f" + i{i}*'h{stride:x}"
return a
def enter_Reg(self, node: RegNode) -> None:
s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});"
self.add_content(s)
regwidth = node.get_property('regwidth')
accesswidth = node.get_property('accesswidth')
if regwidth == accesswidth:
s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});"
self.add_content(s)
else:
n_subwords = regwidth // accesswidth
subword_stride = accesswidth // 8
for i in range(n_subwords):
s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=(i*subword_stride))});"
self.add_content(s)
def exit_AddressableComponent(self, node: 'AddressableNode') -> None:

View File

@@ -195,11 +195,11 @@ class Dereferencer:
raise NotImplementedError
def get_access_strobe(self, obj: Union[RegNode, FieldNode]) -> str:
def get_access_strobe(self, obj: Union[RegNode, FieldNode], reduce_substrobes: bool=True) -> str:
"""
Returns the Verilog string that represents the register's access strobe
"""
return self.address_decode.get_access_strobe(obj)
return self.address_decode.get_access_strobe(obj, reduce_substrobes)
def get_resetsignal(self, obj: Optional[SignalNode]) -> str:
"""

View File

@@ -15,6 +15,7 @@ from .cpuif.apb4 import APB4_Cpuif
from .hwif import Hwif
from .utils import get_always_ff_event
from .scan_design import DesignScanner
from .validate_design import DesignValidator
class RegblockExporter:
def __init__(self, **kwargs: Any) -> None:
@@ -120,18 +121,17 @@ class RegblockExporter:
if retime_read_response:
self.min_read_latency += 1
# Scan the design for any unsupported features
# Also collect pre-export information
# Scan the design for pre-export information
scanner = DesignScanner(self)
scanner.do_scan()
# Construct exporter components
self.cpuif = cpuif_cls(
self,
cpuif_reset=self.top_node.cpuif_reset,
data_width=scanner.cpuif_data_width,
addr_width=self.top_node.size.bit_length()
)
self.hwif = Hwif(
self,
package_name=package_name,
@@ -139,12 +139,15 @@ class RegblockExporter:
out_of_hier_signals=scanner.out_of_hier_signals,
reuse_typedefs=reuse_hwif_typedefs,
)
self.readback = Readback(
self,
retime_read_fanin
)
# Validate that there are no unsupported constructs
validator = DesignValidator(self)
validator.do_validate()
# Build Jinja template context
context = {
"module_name": module_name,

View File

@@ -24,101 +24,107 @@ class _OnWrite(NextStateConditional):
return f"{strb} && decoded_req_is_wr"
def _wbus_bitslice(self, field: 'FieldNode', subword_idx: int) -> str:
# Get the source bitslice range from the internal cpuif's data bus
# For normal fields this ends up passing-through the field's low/high
# values unchanged.
# For fields within a wide register (accesswidth < regwidth), low/high
# may be shifted down and clamped depending on which sub-word is being accessed
accesswidth = field.parent.get_property('accesswidth')
# Shift based on subword
high = field.high - (subword_idx * accesswidth)
low = field.low - (subword_idx * accesswidth)
# clamp to accesswidth
high = max(min(high, accesswidth), 0)
low = max(min(low, accesswidth), 0)
return f"[{high}:{low}]"
def _wr_data(self, field: 'FieldNode', subword_idx: int=0) -> str:
bslice = self._wbus_bitslice(field, subword_idx)
def _wr_data(self, field: 'FieldNode') -> str:
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{decoded_wr_data[{field.high}:{field.low}]}}}}"
value = f"{{<<{{decoded_wr_data{bslice}}}}}"
else:
value = f"decoded_wr_data[{field.high}:{field.low}]"
value = f"decoded_wr_data{bslice}"
return value
def _wr_biten(self, field: 'FieldNode') -> str:
def _wr_biten(self, field: 'FieldNode', subword_idx: int=0) -> str:
bslice = self._wbus_bitslice(field, subword_idx)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{decoded_wr_biten[{field.high}:{field.low}]}}}}"
value = f"{{<<{{decoded_wr_biten{bslice}}}}}"
else:
value = f"decoded_wr_biten[{field.high}:{field.low}]"
value = f"decoded_wr_biten{bslice}"
return value
def get_assignments(self, field: 'FieldNode') -> List[str]:
accesswidth = field.parent.get_property("accesswidth")
# Due to 10.6.1-f, it is impossible for a field with an onwrite action to
# be split across subwords.
# Therefore it is ok to get the subword idx from only one of the bit offsets
sidx = field.low // accesswidth
# field does not get split between subwords
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field, sidx)
S = self._wr_biten(field, sidx)
lines = [
f"next_c = {self.get_onwrite_rhs(R, D, S)};",
"load_next_c = '1;",
]
return lines
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
raise NotImplementedError
class WriteOneSet(_OnWrite):
comment = "SW write 1 set"
onwritetype = OnWriteType.woset
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} | ({D} & {S});",
"load_next_c = '1;",
]
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} | ({data} & {strb})"
class WriteOneClear(_OnWrite):
comment = "SW write 1 clear"
onwritetype = OnWriteType.woclr
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} & ~({D} & {S});",
"load_next_c = '1;",
]
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} & ~({data} & {strb})"
class WriteOneToggle(_OnWrite):
comment = "SW write 1 toggle"
onwritetype = OnWriteType.wot
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} ^ ({D} & {S});",
"load_next_c = '1;",
]
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} ^ ({data} & {strb})"
class WriteZeroSet(_OnWrite):
comment = "SW write 0 set"
onwritetype = OnWriteType.wzs
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} | (~{D} & {S});",
"load_next_c = '1;",
]
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} | (~{data} & {strb})"
class WriteZeroClear(_OnWrite):
comment = "SW write 0 clear"
onwritetype = OnWriteType.wzc
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} & ({D} | ~{S});",
"load_next_c = '1;",
]
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} & ({data} | ~{strb})"
class WriteZeroToggle(_OnWrite):
comment = "SW write 0 toggle"
onwritetype = OnWriteType.wzt
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} ^ (~{D} & {S});",
"load_next_c = '1;",
]
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} ^ (~{data} & {strb})"
class WriteClear(_OnWrite):
comment = "SW write clear"
@@ -144,11 +150,5 @@ class Write(_OnWrite):
comment = "SW write"
onwritetype = None
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = ({R} & ~{S}) | ({D} & {S});",
"load_next_c = '1;",
]
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"({reg} & ~{strb}) | ({data} & {strb})"

View File

@@ -57,37 +57,6 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
offset_parts.append(str(self.current_offset))
return " + ".join(offset_parts)
def enter_Reg(self, node: 'RegNode') -> None:
# TODO: account for smaller regs that are not aligned to the bus width
# - offset the field bit slice as appropriate
# - do not always increment the current offset
if node.has_sw_readable:
current_bit = 0
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
# Fields are sorted by ascending low bit
for field in node.fields():
if field.is_sw_readable:
# insert reserved assignment before if needed
if field.low != current_bit:
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;")
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
else:
value = self.exp.dereferencer.get_value(field)
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
current_bit = field.high + 1
# Insert final reserved assignment if needed
bus_width = self.exp.cpuif.data_width
if current_bit < bus_width:
self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{current_bit}] = '0;")
self.current_offset += 1
def push_loop(self, dim: int) -> None:
super().push_loop(dim)
self.start_offset_stack.append(self.current_offset)
@@ -105,3 +74,173 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
# Advance current scope's offset to account for loop's contents
self.current_offset = start_offset + n_regs * dim
def enter_Reg(self, node: 'RegNode') -> None:
if not node.has_sw_readable:
return
accesswidth = node.get_property('accesswidth')
regwidth = node.get_property('regwidth')
if accesswidth < regwidth:
self.process_wide_reg(node, accesswidth)
else:
self.process_reg(node)
def process_reg(self, node: 'RegNode') -> None:
current_bit = 0
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
# Fields are sorted by ascending low bit
for field in node.fields():
if not field.is_sw_readable:
continue
# insert reserved assignment before this field if needed
if field.low != current_bit:
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;")
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
else:
value = self.exp.dereferencer.get_value(field)
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
current_bit = field.high + 1
# Insert final reserved assignment if needed
bus_width = self.exp.cpuif.data_width
if current_bit < bus_width:
self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{current_bit}] = '0;")
self.current_offset += 1
def process_wide_reg(self, node: 'RegNode', accesswidth: int) -> None:
bus_width = self.exp.cpuif.data_width
subword_idx = 0
current_bit = 0 # Bit-offset within the wide register
access_strb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
# Fields are sorted by ascending low bit
for field in node.fields():
if not field.is_sw_readable:
continue
# insert zero assignment before this field if needed
if field.low >= accesswidth*(subword_idx+1):
# field does not start in this subword
if current_bit > accesswidth * subword_idx:
# current subword had content. Assign remainder
low = current_bit % accesswidth
high = bus_width - 1
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
self.current_offset += 1
# Advance to subword that contains the start of the field
subword_idx = field.low // accesswidth
current_bit = accesswidth * subword_idx
if current_bit != field.low:
# assign zero up to start of this field
low = current_bit % accesswidth
high = (field.low % accesswidth) - 1
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
current_bit = field.low
# Assign field
# loop until the entire field's assignments have been generated
field_pos = field.low
while current_bit <= field.high:
# Assign the field
rd_strb = f"({access_strb}[{subword_idx}] && !decoded_req_is_wr)"
if (field_pos == field.low) and (field.high < accesswidth*(subword_idx+1)):
# entire field fits into this subword
low = field.low - accesswidth * subword_idx
high = field.high - accesswidth * subword_idx
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
else:
value = self.exp.dereferencer.get_value(field)
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;")
current_bit = field.high + 1
if current_bit == accesswidth*(subword_idx+1):
# Field ends at the subword boundary
subword_idx += 1
self.current_offset += 1
elif field.high >= accesswidth*(subword_idx+1):
# only a subset of the field can fit into this subword
# high end gets truncated
# assignment slice
r_low = field_pos - accesswidth * subword_idx
r_high = accesswidth - 1
# field slice
f_low = field_pos - field.low
f_high = accesswidth * (subword_idx + 1) - 1 - field.low
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
# Mirror the low/high indexes
f_low = field.width - 1 - f_low
f_high = field.width - 1 - f_high
f_low, f_high = f_high, f_low
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}[{f_high}:{f_low}]}}}}"
else:
value = self.exp.dereferencer.get_value(field) + f"[{f_high}:{f_low}]"
self.add_content(f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;")
# advance to the next subword
subword_idx += 1
current_bit = accesswidth * subword_idx
field_pos = current_bit
self.current_offset += 1
else:
# only a subset of the field can fit into this subword
# finish field
# assignment slice
r_low = field_pos - accesswidth * subword_idx
r_high = field.high - accesswidth * subword_idx
# field slice
f_low = field_pos - field.low
f_high = field.high - field.low
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
# Mirror the low/high indexes
f_low = field.width - 1 - f_low
f_high = field.width - 1 - f_high
f_low, f_high = f_high, f_low
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}[{f_high}:{f_low}]}}}}"
else:
value = self.exp.dereferencer.get_value(field) + f"[{f_high}:{f_low}]"
self.add_content(f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;")
current_bit = field.high + 1
if current_bit == accesswidth*(subword_idx+1):
# Field ends at the subword boundary
subword_idx += 1
self.current_offset += 1
# insert zero assignment after the last field if needed
if current_bit > accesswidth * subword_idx:
# current subword had content. Assign remainder
low = current_bit % accesswidth
high = bus_width - 1
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
self.current_offset += 1

View File

@@ -1,8 +1,8 @@
from typing import TYPE_CHECKING, Set, List, Optional
from typing import TYPE_CHECKING, Set, Optional
from collections import OrderedDict
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
from systemrdl.node import SignalNode, AddressableNode
from systemrdl.node import SignalNode
if TYPE_CHECKING:
from systemrdl.node import Node, RegNode, FieldNode
@@ -21,9 +21,6 @@ class DesignScanner(RDLListener):
self.cpuif_data_width = 0
self.msg = exp.top_node.env.msg
# Keep track of max accesswidth encountered in a given block
self.max_accesswidth_stack = [] # type: List[int]
# Collections of signals that were actually referenced by the design
self.in_hier_signal_paths = set() # type: Set[str]
self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode]
@@ -65,73 +62,19 @@ class DesignScanner(RDLListener):
self.msg.fatal(
"Unable to export due to previous errors"
)
raise ValueError
def enter_Reg(self, node: 'RegNode') -> None:
accesswidth = node.get_property('accesswidth')
self.max_accesswidth_stack[-1] = max(self.max_accesswidth_stack[-1], accesswidth)
# The CPUIF's bus width is sized according to the largest accesswidth in the design
self.cpuif_data_width = max(self.cpuif_data_width, accesswidth)
# TODO: remove this limitation eventually
if accesswidth != self.cpuif_data_width:
self.msg.error(
"register blocks with non-uniform accesswidth are not supported yet",
node.inst.property_src_ref.get('accesswidth', node.inst.inst_src_ref)
)
# TODO: remove this limitation eventually
if accesswidth != node.get_property('regwidth'):
self.msg.error(
"Registers that have an accesswidth different from the register width are not supported yet",
node.inst.property_src_ref.get('accesswidth', node.inst.inst_src_ref)
)
def enter_AddressableComponent(self, node: AddressableNode) -> None:
self.max_accesswidth_stack.append(0)
def exit_AddressableComponent(self, node: AddressableNode) -> None:
max_block_accesswidth = self.max_accesswidth_stack.pop()
if self.max_accesswidth_stack:
self.max_accesswidth_stack[-1] = max(self.max_accesswidth_stack[-1], max_block_accesswidth)
alignment = int(max_block_accesswidth / 8)
if (node.raw_address_offset % alignment) != 0:
self.msg.error(
f"Unaligned registers are not supported. Address offset of instance '{node.inst_name}' must be a multiple of {alignment}",
node.inst.inst_src_ref
)
if node.is_array and (node.array_stride % alignment) != 0:
self.msg.error(
f"Unaligned registers are not supported. Address stride of instance array '{node.inst_name}' must be a multiple of {alignment}",
node.inst.inst_src_ref
)
def enter_Component(self, node: 'Node') -> Optional[WalkerAction]:
if node.external and (node != self.exp.top_node):
self.msg.error(
"Exporter does not support external components",
node.inst.inst_src_ref
)
# Do not inspect external components. None of my business
return WalkerAction.SkipDescendants
return None
def enter_Signal(self, node: 'SignalNode') -> None:
# If encountering a CPUIF reset that is nested within the register model,
# warn that it will be ignored.
# Only cpuif resets in the top-level node or above will be honored
if node.get_property('cpuif_reset') and (node.parent != self.exp.top_node):
self.msg.warning(
"Only cpuif_reset signals that are instantiated in the top-level "
+ "addrmap or above will be honored. Any cpuif_reset signals nested "
+ "within children of the addrmap being exported will be ignored.",
node.inst.inst_src_ref
)
def enter_Reg(self, node: 'RegNode') -> None:
# The CPUIF's bus width is sized according to the largest accesswidth in the design
accesswidth = node.get_property('accesswidth')
self.cpuif_data_width = max(self.cpuif_data_width, accesswidth)
def enter_Signal(self, node: 'SignalNode') -> None:
if node.get_property('field_reset'):
path = node.get_path()
self.in_hier_signal_paths.add(path)
@@ -146,25 +89,3 @@ class DesignScanner(RDLListener):
self.out_of_hier_signals[path] = value
else:
self.in_hier_signal_paths.add(path)
# 10.6.1-f: Any field that is software-writable or clear on read shall
# not span multiple software accessible sub-words (e.g., a 64-bit
# register with a 32-bit access width may not have a writable field with
# bits in both the upper and lower half of the register).
#
# Interpreting this further - this rule applies any time a field is
# software-modifiable by any means, including rclr, rset, ruser
# TODO: suppress this check for registers that have the appropriate
# buffer_writes/buffer_reads UDP set
parent_accesswidth = node.parent.get_property('accesswidth')
parent_regwidth = node.parent.get_property('regwidth')
if ((parent_accesswidth < parent_regwidth)
and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth)
and (node.is_sw_writable or node.get_property('onread') is not None)):
# Field spans across sub-words
self.msg.error(
"Software-modifiable field '%s' shall not span multiple software-accessible subwords."
% node.inst_name,
node.inst.inst_src_ref
)

View File

@@ -0,0 +1,99 @@
from typing import TYPE_CHECKING, Optional
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
if TYPE_CHECKING:
from systemrdl.node import Node, RegNode, FieldNode, SignalNode, AddressableNode
from .exporter import RegblockExporter
class DesignValidator(RDLListener):
"""
Performs additional rule-checks on the design that check for limitations
imposed by this exporter.
"""
def __init__(self, exp:'RegblockExporter') -> None:
self.exp = exp
self.msg = exp.top_node.env.msg
def do_validate(self) -> None:
RDLWalker().walk(self.exp.top_node, self)
if self.msg.had_error:
self.msg.fatal(
"Unable to export due to previous errors"
)
def enter_Component(self, node: 'Node') -> Optional[WalkerAction]:
if node.external and (node != self.exp.top_node):
self.msg.error(
"Exporter does not support external components",
node.inst.inst_src_ref
)
# Do not inspect external components. None of my business
return WalkerAction.SkipDescendants
return None
def enter_Signal(self, node: 'SignalNode') -> None:
# If encountering a CPUIF reset that is nested within the register model,
# warn that it will be ignored.
# Only cpuif resets in the top-level node or above will be honored
if node.get_property('cpuif_reset') and (node.parent != self.exp.top_node):
self.msg.warning(
"Only cpuif_reset signals that are instantiated in the top-level "
"addrmap or above will be honored. Any cpuif_reset signals nested "
"within children of the addrmap being exported will be ignored.",
node.inst.inst_src_ref
)
def enter_AddressableComponent(self, node: 'AddressableNode') -> None:
# All registers must be aligned to the internal data bus width
alignment = self.exp.cpuif.data_width_bytes
if (node.raw_address_offset % alignment) != 0:
self.msg.error(
"Unaligned registers are not supported. Address offset of "
f"instance '{node.inst_name}' must be a multiple of {alignment}",
node.inst.inst_src_ref
)
if node.is_array and (node.array_stride % alignment) != 0:
self.msg.error(
"Unaligned registers are not supported. Address stride of "
f"instance array '{node.inst_name}' must be a multiple of {alignment}",
node.inst.inst_src_ref
)
def enter_Reg(self, node: 'RegNode') -> None:
# accesswidth of wide registers must be consistent within the register block
accesswidth = node.get_property('accesswidth')
regwidth = node.get_property('regwidth')
if accesswidth < regwidth:
# register is 'wide'
if accesswidth != self.exp.cpuif.data_width:
self.msg.error(
f"Multi-word registers that have an accesswidth ({accesswidth}) "
"that is inconsistent with this regblock's CPU bus width "
f"({self.exp.cpuif.data_width}) are not supported.",
node.inst.inst_src_ref
)
def enter_Field(self, node: 'FieldNode') -> None:
# 10.6.1-f: Any field that is software-writable or clear on read shall
# not span multiple software accessible sub-words (e.g., a 64-bit
# register with a 32-bit access width may not have a writable field with
# bits in both the upper and lower half of the register).
#
# Interpreting this further - this rule applies any time a field is
# software-modifiable by any means, including rclr, rset, ruser
# TODO: suppress this check for registers that have the appropriate
# buffer_writes/buffer_reads UDP set
parent_accesswidth = node.parent.get_property('accesswidth')
parent_regwidth = node.parent.get_property('regwidth')
if ((parent_accesswidth < parent_regwidth)
and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth)
and (node.is_sw_writable or node.get_property('onread') is not None)):
# Field spans across sub-words
self.msg.error(
f"Software-modifiable field '{node.inst_name}' shall not span "
"multiple software-accessible subwords.",
node.inst.inst_src_ref
)