108 lines
4.2 KiB
Python
108 lines
4.2 KiB
Python
from typing import TYPE_CHECKING
|
|
|
|
from ..forloop_generator import RDLForLoopGenerator, LoopBody
|
|
|
|
if TYPE_CHECKING:
|
|
from ..exporter import RegblockExporter
|
|
from systemrdl.node import RegNode
|
|
|
|
class ReadbackLoopBody(LoopBody):
|
|
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
|
|
super().__init__(dim, iterator, i_type)
|
|
self.n_regs = 0
|
|
|
|
def __str__(self) -> str:
|
|
# replace $i#sz token when stringifying
|
|
s = super().__str__()
|
|
token = f"${self.iterator}sz"
|
|
s = s.replace(token, str(self.n_regs))
|
|
return s
|
|
|
|
class ReadbackAssignmentGenerator(RDLForLoopGenerator):
|
|
i_type = "genvar"
|
|
loop_body_cls = ReadbackLoopBody
|
|
|
|
def __init__(self, exp:'RegblockExporter') -> None:
|
|
super().__init__()
|
|
self.exp = exp
|
|
|
|
# The readback array collects all possible readback values into a flat
|
|
# array. The array width is equal to the CPUIF bus width. Each entry in
|
|
# the array represents an aligned read access.
|
|
self.current_offset = 0
|
|
self.start_offset_stack = []
|
|
self.dim_stack = []
|
|
|
|
@property
|
|
def current_offset_str(self) -> str:
|
|
"""
|
|
Derive a string that represents the current offset being assigned.
|
|
This consists of:
|
|
- The current integer offset
|
|
- multiplied index of any enclosing loop
|
|
|
|
The integer offset from "current_offset" is static and is monotonically
|
|
incremented as more register assignments are processed.
|
|
|
|
The component of the offset from loops is added by multiplying the current
|
|
loop index by the loop size.
|
|
Since the loop's size is not known at this time, it is emitted as a
|
|
placeholder token like: $i0sz, $i1sz, $i2sz, etc
|
|
These tokens can be replaced once the loop body has been completed and the
|
|
size of its contents is known.
|
|
"""
|
|
offset_parts = []
|
|
for i in range(self._loop_level):
|
|
offset_parts.append(f"i{i}*$i{i}sz")
|
|
offset_parts.append(str(self.current_offset))
|
|
return " + ".join(offset_parts)
|
|
|
|
def enter_Reg(self, node: 'RegNode') -> None:
|
|
# TODO: account for smaller regs that are not aligned to the bus width
|
|
# - offset the field bit slice as appropriate
|
|
# - do not always increment the current offset
|
|
if node.has_sw_readable:
|
|
current_bit = 0
|
|
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
|
|
# Fields are sorted by ascending low bit
|
|
for field in node.fields():
|
|
if field.is_sw_readable:
|
|
# insert reserved assignment before if needed
|
|
if field.low != current_bit:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;")
|
|
|
|
if field.msb < field.lsb:
|
|
# Field gets bitswapped since it is in [low:high] orientation
|
|
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
|
|
else:
|
|
value = self.exp.dereferencer.get_value(field)
|
|
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
|
|
|
|
current_bit = field.high + 1
|
|
|
|
# Insert final reserved assignment if needed
|
|
bus_width = self.exp.cpuif.data_width
|
|
if current_bit < bus_width:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{current_bit}] = '0;")
|
|
|
|
self.current_offset += 1
|
|
|
|
def push_loop(self, dim: int) -> None:
|
|
super().push_loop(dim)
|
|
self.start_offset_stack.append(self.current_offset)
|
|
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
|
|
|
|
super().pop_loop()
|
|
|
|
# Advance current scope's offset to account for loop's contents
|
|
self.current_offset += n_regs * dim - 1
|