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

@@ -18,18 +18,41 @@ Registers instantiated using the ``alias`` keyword are not supported yet.
Unaligned Registers
-------------------
All address offsets & strides shall be a multiple of the accesswidth used. Specifically:
All address offsets & strides shall be a multiple of the cpuif bus width used. Specifically:
* Each register's address and array stride shall be aligned to it's accesswidth.
* Each regfile or addrmap shall use an offset and stride that is a multiple of the largest accesswidth it encloses.
* Bus width is inferred by the maximum accesswidth used in the regblock.
* Each component's address and array stride shall be aligned to the bus width.
Register width, Access width and CPUIF bus width
------------------------------------------------
To keep the initial architecture simpler, currently ``regwidth``, ``accesswidth``
and the resulting CPU bus width has some limitations:
Uniform accesswidth
-------------------
All registers within a register block shall use the same accesswidth.
* All registers shall have ``regwidth`` == ``accesswidth``
* ``regwidth`` shall be the same across all registers within the block being exported.
One exception is that registers with regwidth that is narrower than the cpuif
bus width are permitted, provided that their regwidth is equal to their accesswidth.
I have plans to remove these restrictions and allow for more flexibility in the future.
For example:
.. code-block:: systemrdl
// (Largest accesswidth used is 32, therefore the CPUIF bus width is 32)
reg {
regwidth = 32;
accesswidth = 32;
} reg_a @ 0x00; // OK. Regular 32-bit register
reg {
regwidth = 64;
accesswidth = 32;
} reg_b @ 0x08; // OK. "Wide" register of 64-bits, but is accessed using 32-bit subwords
reg {
regwidth = 8;
accesswidth = 8;
} reg_c @ 0x10; // OK. Is aligned to the cpuif bus width
reg {
regwidth = 32;
accesswidth = 8;
} bad_reg @ 0x14; // NOT OK. accesswidth conflicts with cpuif width

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)
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:
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,17 +57,46 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
offset_parts.append(str(self.current_offset))
return " + ".join(offset_parts)
def push_loop(self, dim: int) -> None:
super().push_loop(dim)
self.start_offset_stack.append(self.current_offset)
self.dim_stack.append(dim)
def pop_loop(self) -> None:
start_offset = self.start_offset_stack.pop()
dim = self.dim_stack.pop()
# Number of registers enclosed in this loop
n_regs = self.current_offset - start_offset
self.current_loop.n_regs = n_regs # type: ignore
super().pop_loop()
# 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:
# 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:
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 field.is_sw_readable:
# insert reserved assignment before if needed
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;")
@@ -88,20 +117,130 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
self.current_offset += 1
def push_loop(self, dim: int) -> None:
super().push_loop(dim)
self.start_offset_stack.append(self.current_offset)
self.dim_stack.append(dim)
def pop_loop(self) -> None:
start_offset = self.start_offset_stack.pop()
dim = self.dim_stack.pop()
def process_wide_reg(self, node: 'RegNode', accesswidth: int) -> None:
bus_width = self.exp.cpuif.data_width
# Number of registers enclosed in this loop
n_regs = self.current_offset - start_offset
self.current_loop.n_regs = n_regs # type: ignore
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
super().pop_loop()
# 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 current scope's offset to account for loop's contents
self.current_offset = start_offset + n_regs * dim
# 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
)

18
tests/.coveragerc Normal file
View File

@@ -0,0 +1,18 @@
[run]
branch = True
#relative_files = True
[paths]
source =
../src/peakrdl_regblock/
*/site-packages/*/peakrdl_regblock
*/site-packages/peakrdl_regblock
[report]
exclude_lines =
pragma: no cover
raise RuntimeError
raise NotImplementedError
if TYPE_CHECKING:
precision = 1

View File

@@ -4,3 +4,5 @@ pytest-parallel
jinja2-simple-tags
pylint
mypy
pytest-cov
coverage

View File

@@ -15,12 +15,17 @@ pip install -r $this_dir/requirements.txt
# Install dut
cd $this_dir/../
pip install .
python $this_dir/../setup.py install
cd $this_dir
# Run unit tests
export SKIP_SYNTH_TESTS=1
pytest --workers auto
#export STUB_SIMULATOR=1
export NO_XSIM=1
pytest --workers auto --cov=peakrdl_regblock
# Generate coverage report
coverage html -i -d $this_dir/htmlcov
# Run lint
pylint --rcfile $this_dir/pylint.rc ../src/peakrdl_regblock

View File

@@ -31,4 +31,14 @@ addrmap regblock {
};
subrf sub2[2] @ 0x2000 += 0x40;
subreg r3 @ 0x2080;
reg {
field {} f1[19:12] = 0;
field {} f2[30:20] = 0;
} rw_reg @ 0x3000;
reg {
field {} f1[12:19] = 0;
field {} f2[20:30] = 0;
} rw_reg_lsb0 @ 0x3004;
};

View File

@@ -61,4 +61,20 @@
assert(cb.hwif_out.r2.a.value == 'h0);
assert(cb.hwif_out.r2.b.value == 'h0);
assert(cb.hwif_out.r2.c.value == 'h0);
// rw_reg
cpuif.assert_read('h3000, 0);
cpuif.write('h3000, 'h4DEAB000);
@cb;
assert(cb.hwif_out.rw_reg.f1.value == 8'hAB);
assert(cb.hwif_out.rw_reg.f2.value == 11'h4DE);
cpuif.assert_read('h3000, 'h4DEAB000);
// rw_reg_lsb0
cpuif.assert_read('h3004, 0);
cpuif.write('h3004, 'h4DEAB000);
@cb;
assert({<<{cb.hwif_out.rw_reg_lsb0.f1.value}} == 8'hAB);
assert({<<{cb.hwif_out.rw_reg_lsb0.f2.value}} == 11'h4DE);
cpuif.assert_read('h3004, 'h4DEAB000);
{% endblock %}

View File

@@ -19,7 +19,7 @@ class TestSynth(SynthTestCase):
self.run_synth()
@pytest.mark.skipif(os.environ.get("STUB_SIMULATOR", False), reason="user skipped")
@pytest.mark.skipif(os.environ.get("STUB_SIMULATOR", False) or os.environ.get("NO_XSIM", False), reason="user skipped")
@parameterized_class(TEST_PARAMS)
class TestVivado(SimTestCase):
"""

View File

View File

@@ -0,0 +1,93 @@
addrmap top {
reg {
regwidth = 64;
accesswidth = 16;
default sw=rw;
default hw=r;
field {} f1[7:0] = 0;
field {} f2[14:12] = 0;
field {} f3[36:36] = 0;
field {} f4[47:40] = 0;
} rw_reg1;
reg {
regwidth = 64;
accesswidth = 16;
default sw=rw;
default hw=r;
field {} f1[19:16] = 0;
field {} f2[63:48] = 0;
} rw_reg2;
reg {
regwidth = 64;
accesswidth = 16;
default sw=rw;
default hw=r;
field {} f1[0:7] = 0;
field {} f2[12:14] = 0;
field {} f3[36:36] = 0;
field {} f4[40:47] = 0;
} rw_reg1_lsb0;
reg {
regwidth = 64;
accesswidth = 16;
default sw=rw;
default hw=r;
field {} f1[16:19] = 0;
field {} f2[48:63] = 0;
} rw_reg2_lsb0;
reg {
regwidth = 32;
accesswidth = 16;
default sw=r;
default hw=w;
field {
sw=w; hw=r;
} f0[3:3] = 0;
field {} f1[19:12];
field {} f2[30:20];
} r_reg;
reg {
regwidth = 32;
accesswidth = 16;
default sw=r;
default hw=w;
field {} f1[12:19];
field {} f2[20:30];
} r_reg_lsb0;
reg {
regwidth = 64;
accesswidth = 16;
default sw=r;
default hw=w;
field {} f1[31:12];
field {} f2[49:48];
} r_reg2;
reg {
regwidth=16;
field {
sw=r; hw=na;
counter;
} f1_cnt[7:0] = 0;
field {
sw=r; hw=na;
counter;
} f2_cnt[15:8] = 0;
} counter_reg;
counter_reg.f1_cnt->incr = r_reg2.f1->swacc;
counter_reg.f2_cnt->incr = r_reg2.f2->swacc;
};

View File

@@ -0,0 +1,111 @@
{% extends "lib/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;
// rw_reg1
assert(cb.hwif_out.rw_reg1.f1.value == 0);
assert(cb.hwif_out.rw_reg1.f2.value == 0);
assert(cb.hwif_out.rw_reg1.f3.value == 0);
assert(cb.hwif_out.rw_reg1.f4.value == 0);
cpuif.write('h0, 'h1234);
cpuif.write('h2, 'h5678);
cpuif.write('h4, 'h9ABC);
cpuif.write('h6, 'hDEF1);
@cb;
assert(cb.hwif_out.rw_reg1.f1.value == 8'h34);
assert(cb.hwif_out.rw_reg1.f2.value == 3'h1);
assert(cb.hwif_out.rw_reg1.f3.value == 1'h1);
assert(cb.hwif_out.rw_reg1.f4.value == 8'h9A);
cpuif.assert_read('h0, 'h1034);
cpuif.assert_read('h2, 'h0000);
cpuif.assert_read('h4, 'h9A10);
cpuif.assert_read('h6, 'h0000);
// rw_reg2
assert(cb.hwif_out.rw_reg2.f1.value == 0);
assert(cb.hwif_out.rw_reg2.f2.value == 0);
cpuif.write('h8, 'h1234);
cpuif.write('hA, 'h5678);
cpuif.write('hC, 'h9ABC);
cpuif.write('hE, 'hDEF1);
@cb;
assert(cb.hwif_out.rw_reg2.f1.value == 4'h8);
assert(cb.hwif_out.rw_reg2.f2.value == 16'hDEF1);
cpuif.assert_read('h8, 'h0000);
cpuif.assert_read('hA, 'h0008);
cpuif.assert_read('hC, 'h0000);
cpuif.assert_read('hE, 'hDEF1);
// rw_reg1_lsb0
assert(cb.hwif_out.rw_reg1_lsb0.f1.value == 0);
assert(cb.hwif_out.rw_reg1_lsb0.f2.value == 0);
assert(cb.hwif_out.rw_reg1_lsb0.f3.value == 0);
assert(cb.hwif_out.rw_reg1_lsb0.f4.value == 0);
cpuif.write('h10, 'h1234);
cpuif.write('h12, 'h5678);
cpuif.write('h14, 'h9ABC);
cpuif.write('h16, 'hDEF1);
@cb;
assert({<<{cb.hwif_out.rw_reg1_lsb0.f1.value}} == 8'h34);
assert({<<{cb.hwif_out.rw_reg1_lsb0.f2.value}} == 3'h1);
assert({<<{cb.hwif_out.rw_reg1_lsb0.f3.value}} == 1'h1);
assert({<<{cb.hwif_out.rw_reg1_lsb0.f4.value}} == 8'h9A);
cpuif.assert_read('h10, 'h1034);
cpuif.assert_read('h12, 'h0000);
cpuif.assert_read('h14, 'h9A10);
cpuif.assert_read('h16, 'h0000);
// rw_reg2_lsb0
assert(cb.hwif_out.rw_reg2_lsb0.f1.value == 0);
assert(cb.hwif_out.rw_reg2_lsb0.f2.value == 0);
cpuif.write('h18, 'h1234);
cpuif.write('h1A, 'h5678);
cpuif.write('h1C, 'h9ABC);
cpuif.write('h1E, 'hDEF1);
@cb;
assert({<<{cb.hwif_out.rw_reg2_lsb0.f1.value}} == 4'h8);
assert({<<{cb.hwif_out.rw_reg2_lsb0.f2.value}} == 16'hDEF1);
cpuif.assert_read('h18, 'h0000);
cpuif.assert_read('h1A, 'h0008);
cpuif.assert_read('h1C, 'h0000);
cpuif.assert_read('h1E, 'hDEF1);
// r_reg
cpuif.assert_read('h20, 0);
cpuif.assert_read('h22, 0);
cb.hwif_in.r_reg.f1.next <= 8'hAB;
cb.hwif_in.r_reg.f2.next <= 11'h4DE;
@cb;
cpuif.assert_read('h20, 'hB000);
cpuif.assert_read('h22, 'h4DEA);
// r_reg_lsb0
cpuif.assert_read('h24, 0);
cpuif.assert_read('h26, 0);
cb.hwif_in.r_reg_lsb0.f1.next <= {<<{8'hAB}};
cb.hwif_in.r_reg_lsb0.f2.next <= {<<{11'h4DE}};
@cb;
cpuif.assert_read('h24, 'hB000);
cpuif.assert_read('h26, 'h4DEA);
// r_reg2
cpuif.assert_read('h28, 0);
cpuif.assert_read('h2a, 0);
cpuif.assert_read('h2c, 0);
cpuif.assert_read('h2e, 0);
cb.hwif_in.r_reg2.f1.next <= 20'hABCDE;
cb.hwif_in.r_reg2.f2.next <= 2'h3;
@cb;
cpuif.assert_read('h28, 'hE000);
cpuif.assert_read('h2a, 'hABCD);
cpuif.assert_read('h2c, 'h0000);
cpuif.assert_read('h2e, 'h0003);
// counter_reg
cpuif.assert_read('h30, 16'h0204);
{% endblock %}

View File

@@ -0,0 +1,5 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
def test_dut(self):
self.run_test()