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 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. * Bus width is inferred by the maximum accesswidth used in the regblock.
* Each regfile or addrmap shall use an offset and stride that is a multiple of the largest accesswidth it encloses. * Each component's address and array stride shall be aligned to the bus width.
Register width, Access width and CPUIF bus width Uniform accesswidth
------------------------------------------------ -------------------
To keep the initial architecture simpler, currently ``regwidth``, ``accesswidth`` All registers within a register block shall use the same accesswidth.
and the resulting CPU bus width has some limitations:
* All registers shall have ``regwidth`` == ``accesswidth`` One exception is that registers with regwidth that is narrower than the cpuif
* ``regwidth`` shall be the same across all registers within the block being exported. 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 assert s is not None
return s 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. Returns the Verilog string that represents the register/field's access strobe.
""" """
if isinstance(node, FieldNode): 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 return "decoded_reg_strb." + path
class DecodeStructGenerator(RDLStructGenerator): class DecodeStructGenerator(RDLStructGenerator):
def enter_Reg(self, node: 'RegNode') -> None: 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 # Stub out
def exit_Reg(self, node: 'RegNode') -> None: def exit_Reg(self, node: 'RegNode') -> None:
@@ -79,16 +105,26 @@ class DecodeLogicGenerator(RDLForLoopGenerator):
self._array_stride_stack.extend(strides) self._array_stride_stack.extend(strides)
def _get_address_str(self, node:AddressableNode) -> str: 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):x}" 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): for i, stride in enumerate(self._array_stride_stack):
a += f" + i{i}*'h{stride:x}" a += f" + i{i}*'h{stride:x}"
return a return a
def enter_Reg(self, node: RegNode) -> None: 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)});" s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});"
self.add_content(s) 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: def exit_AddressableComponent(self, node: 'AddressableNode') -> None:

View File

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

View File

@@ -24,101 +24,107 @@ class _OnWrite(NextStateConditional):
return f"{strb} && decoded_req_is_wr" 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: if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation # 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: else:
value = f"decoded_wr_data[{field.high}:{field.low}]" value = f"decoded_wr_data{bslice}"
return value 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: if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation # 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: else:
value = f"decoded_wr_biten[{field.high}:{field.low}]" value = f"decoded_wr_biten{bslice}"
return value 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): class WriteOneSet(_OnWrite):
comment = "SW write 1 set" comment = "SW write 1 set"
onwritetype = OnWriteType.woset onwritetype = OnWriteType.woset
def get_assignments(self, field: 'FieldNode') -> List[str]: def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
R = self.exp.field_logic.get_storage_identifier(field) return f"{reg} | ({data} & {strb})"
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} | ({D} & {S});",
"load_next_c = '1;",
]
class WriteOneClear(_OnWrite): class WriteOneClear(_OnWrite):
comment = "SW write 1 clear" comment = "SW write 1 clear"
onwritetype = OnWriteType.woclr onwritetype = OnWriteType.woclr
def get_assignments(self, field: 'FieldNode') -> List[str]: def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
R = self.exp.field_logic.get_storage_identifier(field) return f"{reg} & ~({data} & {strb})"
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} & ~({D} & {S});",
"load_next_c = '1;",
]
class WriteOneToggle(_OnWrite): class WriteOneToggle(_OnWrite):
comment = "SW write 1 toggle" comment = "SW write 1 toggle"
onwritetype = OnWriteType.wot onwritetype = OnWriteType.wot
def get_assignments(self, field: 'FieldNode') -> List[str]: def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
R = self.exp.field_logic.get_storage_identifier(field) return f"{reg} ^ ({data} & {strb})"
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} ^ ({D} & {S});",
"load_next_c = '1;",
]
class WriteZeroSet(_OnWrite): class WriteZeroSet(_OnWrite):
comment = "SW write 0 set" comment = "SW write 0 set"
onwritetype = OnWriteType.wzs onwritetype = OnWriteType.wzs
def get_assignments(self, field: 'FieldNode') -> List[str]: def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
R = self.exp.field_logic.get_storage_identifier(field) return f"{reg} | (~{data} & {strb})"
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} | (~{D} & {S});",
"load_next_c = '1;",
]
class WriteZeroClear(_OnWrite): class WriteZeroClear(_OnWrite):
comment = "SW write 0 clear" comment = "SW write 0 clear"
onwritetype = OnWriteType.wzc onwritetype = OnWriteType.wzc
def get_assignments(self, field: 'FieldNode') -> List[str]: def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
R = self.exp.field_logic.get_storage_identifier(field) return f"{reg} & ({data} | ~{strb})"
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} & ({D} | ~{S});",
"load_next_c = '1;",
]
class WriteZeroToggle(_OnWrite): class WriteZeroToggle(_OnWrite):
comment = "SW write 0 toggle" comment = "SW write 0 toggle"
onwritetype = OnWriteType.wzt onwritetype = OnWriteType.wzt
def get_assignments(self, field: 'FieldNode') -> List[str]: def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
R = self.exp.field_logic.get_storage_identifier(field) return f"{reg} ^ (~{data} & {strb})"
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = {R} ^ (~{D} & {S});",
"load_next_c = '1;",
]
class WriteClear(_OnWrite): class WriteClear(_OnWrite):
comment = "SW write clear" comment = "SW write clear"
@@ -144,11 +150,5 @@ class Write(_OnWrite):
comment = "SW write" comment = "SW write"
onwritetype = None onwritetype = None
def get_assignments(self, field: 'FieldNode') -> List[str]: def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
R = self.exp.field_logic.get_storage_identifier(field) return f"({reg} & ~{strb}) | ({data} & {strb})"
D = self._wr_data(field)
S = self._wr_biten(field)
return [
f"next_c = ({R} & ~{S}) | ({D} & {S});",
"load_next_c = '1;",
]

View File

@@ -57,17 +57,46 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
offset_parts.append(str(self.current_offset)) offset_parts.append(str(self.current_offset))
return " + ".join(offset_parts) 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: def enter_Reg(self, node: 'RegNode') -> None:
# TODO: account for smaller regs that are not aligned to the bus width if not node.has_sw_readable:
# - offset the field bit slice as appropriate return
# - do not always increment the current offset
if node.has_sw_readable: 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 current_bit = 0
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)" rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
# Fields are sorted by ascending low bit # Fields are sorted by ascending low bit
for field in node.fields(): for field in node.fields():
if field.is_sw_readable: if not field.is_sw_readable:
# insert reserved assignment before if needed continue
# insert reserved assignment before this field if needed
if field.low != current_bit: if field.low != current_bit:
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;") 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 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: def process_wide_reg(self, node: 'RegNode', accesswidth: int) -> None:
start_offset = self.start_offset_stack.pop() bus_width = self.exp.cpuif.data_width
dim = self.dim_stack.pop()
# Number of registers enclosed in this loop subword_idx = 0
n_regs = self.current_offset - start_offset current_bit = 0 # Bit-offset within the wide register
self.current_loop.n_regs = n_regs # type: ignore 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 # Advance to subword that contains the start of the field
self.current_offset = start_offset + n_regs * dim 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 collections import OrderedDict
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
from systemrdl.node import SignalNode, AddressableNode from systemrdl.node import SignalNode
if TYPE_CHECKING: if TYPE_CHECKING:
from systemrdl.node import Node, RegNode, FieldNode from systemrdl.node import Node, RegNode, FieldNode
@@ -21,9 +21,6 @@ class DesignScanner(RDLListener):
self.cpuif_data_width = 0 self.cpuif_data_width = 0
self.msg = exp.top_node.env.msg 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 # Collections of signals that were actually referenced by the design
self.in_hier_signal_paths = set() # type: Set[str] self.in_hier_signal_paths = set() # type: Set[str]
self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode] self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode]
@@ -65,73 +62,19 @@ class DesignScanner(RDLListener):
self.msg.fatal( self.msg.fatal(
"Unable to export due to previous errors" "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]: def enter_Component(self, node: 'Node') -> Optional[WalkerAction]:
if node.external and (node != self.exp.top_node): 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 # Do not inspect external components. None of my business
return WalkerAction.SkipDescendants return WalkerAction.SkipDescendants
return None return None
def enter_Signal(self, node: 'SignalNode') -> None: def enter_Reg(self, node: 'RegNode') -> None:
# If encountering a CPUIF reset that is nested within the register model, # The CPUIF's bus width is sized according to the largest accesswidth in the design
# warn that it will be ignored. accesswidth = node.get_property('accesswidth')
# Only cpuif resets in the top-level node or above will be honored self.cpuif_data_width = max(self.cpuif_data_width, accesswidth)
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_Signal(self, node: 'SignalNode') -> None:
if node.get_property('field_reset'): if node.get_property('field_reset'):
path = node.get_path() path = node.get_path()
self.in_hier_signal_paths.add(path) self.in_hier_signal_paths.add(path)
@@ -146,25 +89,3 @@ class DesignScanner(RDLListener):
self.out_of_hier_signals[path] = value self.out_of_hier_signals[path] = value
else: else:
self.in_hier_signal_paths.add(path) 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 jinja2-simple-tags
pylint pylint
mypy mypy
pytest-cov
coverage

View File

@@ -15,12 +15,17 @@ pip install -r $this_dir/requirements.txt
# Install dut # Install dut
cd $this_dir/../ cd $this_dir/../
pip install . python $this_dir/../setup.py install
cd $this_dir cd $this_dir
# Run unit tests # Run unit tests
export SKIP_SYNTH_TESTS=1 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 # Run lint
pylint --rcfile $this_dir/pylint.rc ../src/peakrdl_regblock pylint --rcfile $this_dir/pylint.rc ../src/peakrdl_regblock

View File

@@ -31,4 +31,14 @@ addrmap regblock {
}; };
subrf sub2[2] @ 0x2000 += 0x40; subrf sub2[2] @ 0x2000 += 0x40;
subreg r3 @ 0x2080; 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.a.value == 'h0);
assert(cb.hwif_out.r2.b.value == 'h0); assert(cb.hwif_out.r2.b.value == 'h0);
assert(cb.hwif_out.r2.c.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 %} {% endblock %}

View File

@@ -19,7 +19,7 @@ class TestSynth(SynthTestCase):
self.run_synth() 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) @parameterized_class(TEST_PARAMS)
class TestVivado(SimTestCase): 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()