Add support for wide registers (where accesswidth < regwidth)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
|
|||||||
99
src/peakrdl_regblock/validate_design.py
Normal file
99
src/peakrdl_regblock/validate_design.py
Normal 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
18
tests/.coveragerc
Normal 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
|
||||||
@@ -4,3 +4,5 @@ pytest-parallel
|
|||||||
jinja2-simple-tags
|
jinja2-simple-tags
|
||||||
pylint
|
pylint
|
||||||
mypy
|
mypy
|
||||||
|
pytest-cov
|
||||||
|
coverage
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
0
tests/test_wide_regs/__init__.py
Normal file
0
tests/test_wide_regs/__init__.py
Normal file
93
tests/test_wide_regs/regblock.rdl
Normal file
93
tests/test_wide_regs/regblock.rdl
Normal 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;
|
||||||
|
};
|
||||||
111
tests/test_wide_regs/tb_template.sv
Normal file
111
tests/test_wide_regs/tb_template.sv
Normal 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 %}
|
||||||
5
tests/test_wide_regs/testcase.py
Normal file
5
tests/test_wide_regs/testcase.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from ..lib.sim_testcase import SimTestCase
|
||||||
|
|
||||||
|
class Test(SimTestCase):
|
||||||
|
def test_dut(self):
|
||||||
|
self.run_test()
|
||||||
Reference in New Issue
Block a user