382 lines
16 KiB
Python
382 lines
16 KiB
Python
from typing import TYPE_CHECKING, List
|
|
|
|
from systemrdl.node import RegNode, AddressableNode
|
|
from systemrdl.walker import WalkerAction
|
|
|
|
from ..forloop_generator import RDLForLoopGenerator, LoopBody
|
|
|
|
from ..utils import do_bitswap, do_slice
|
|
|
|
if TYPE_CHECKING:
|
|
from ..exporter import RegblockExporter
|
|
|
|
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 = [] # type: List[int]
|
|
self.dim_stack = [] # type: List[int]
|
|
|
|
@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 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_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction:
|
|
super().enter_AddressableComponent(node)
|
|
|
|
if node.external and not isinstance(node, RegNode):
|
|
# External block
|
|
strb = self.exp.hwif.get_external_rd_ack(node)
|
|
data = self.exp.hwif.get_external_rd_data(node)
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;")
|
|
self.current_offset += 1
|
|
return WalkerAction.SkipDescendants
|
|
|
|
return WalkerAction.Continue
|
|
|
|
def enter_Reg(self, node: RegNode) -> WalkerAction:
|
|
if not node.has_sw_readable:
|
|
return WalkerAction.SkipDescendants
|
|
|
|
if node.external:
|
|
self.process_external_reg(node)
|
|
return WalkerAction.SkipDescendants
|
|
|
|
accesswidth = node.get_property('accesswidth')
|
|
regwidth = node.get_property('regwidth')
|
|
rbuf = node.get_property('buffer_reads')
|
|
if rbuf:
|
|
trigger = node.get_property('rbuffer_trigger')
|
|
is_own_trigger = (isinstance(trigger, RegNode) and trigger == node)
|
|
if is_own_trigger:
|
|
if accesswidth < regwidth:
|
|
self.process_buffered_reg_with_bypass(node, regwidth, accesswidth)
|
|
else:
|
|
# bypass cancels out. Behaves like a normal reg
|
|
self.process_reg(node)
|
|
else:
|
|
self.process_buffered_reg(node, regwidth, accesswidth)
|
|
elif accesswidth < regwidth:
|
|
self.process_wide_reg(node, accesswidth)
|
|
else:
|
|
self.process_reg(node)
|
|
|
|
return WalkerAction.SkipDescendants
|
|
|
|
def process_external_reg(self, node: RegNode) -> None:
|
|
strb = self.exp.hwif.get_external_rd_ack(node)
|
|
data = self.exp.hwif.get_external_rd_data(node)
|
|
regwidth = node.get_property('regwidth')
|
|
if regwidth < self.exp.cpuif.data_width:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{self.exp.cpuif.data_width-1}:{regwidth}] = '0;")
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{regwidth-1}:0] = {strb} ? {data} : '0;")
|
|
else:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;")
|
|
|
|
self.current_offset += 1
|
|
|
|
def process_reg(self, node: RegNode) -> None:
|
|
current_bit = 0
|
|
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
|
|
# Fields are sorted by ascending low bit
|
|
for field in node.fields():
|
|
if not field.is_sw_readable:
|
|
continue
|
|
|
|
# insert reserved assignment before this field if needed
|
|
if field.low != current_bit:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;")
|
|
|
|
value = self.exp.dereferencer.get_value(field)
|
|
if field.msb < field.lsb:
|
|
# Field gets bitswapped since it is in [low:high] orientation
|
|
value = do_bitswap(value)
|
|
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
|
|
|
|
current_bit = field.high + 1
|
|
|
|
# Insert final reserved assignment if needed
|
|
bus_width = self.exp.cpuif.data_width
|
|
if current_bit < bus_width:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{current_bit}] = '0;")
|
|
|
|
self.current_offset += 1
|
|
|
|
|
|
def process_buffered_reg(self, node: RegNode, regwidth: int, accesswidth: int) -> None:
|
|
rbuf = self.exp.read_buffering.get_rbuf_data(node)
|
|
|
|
if accesswidth < regwidth:
|
|
# Is wide reg
|
|
n_subwords = regwidth // accesswidth
|
|
astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
|
|
for i in range(n_subwords):
|
|
rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)"
|
|
bslice = f"[{(i + 1) * accesswidth - 1}:{i*accesswidth}]"
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;")
|
|
self.current_offset += 1
|
|
|
|
else:
|
|
# Is regular reg
|
|
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{regwidth-1}:0] = {rd_strb} ? {rbuf} : '0;")
|
|
|
|
bus_width = self.exp.cpuif.data_width
|
|
if regwidth < bus_width:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{regwidth}] = '0;")
|
|
|
|
self.current_offset += 1
|
|
|
|
|
|
def process_buffered_reg_with_bypass(self, node: RegNode, regwidth: int, accesswidth: int) -> None:
|
|
"""
|
|
Special case for a buffered register when the register is its own trigger.
|
|
First sub-word shall bypass the read buffer and assign directly.
|
|
Subsequent subwords assign from the buffer.
|
|
Caller guarantees this is a wide reg
|
|
"""
|
|
astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
|
|
|
|
# Generate assignments for first sub-word
|
|
bidx = 0
|
|
rd_strb = f"({astrb}[0] && !decoded_req_is_wr)"
|
|
for field in node.fields():
|
|
if not field.is_sw_readable:
|
|
continue
|
|
|
|
if field.low >= accesswidth:
|
|
# field is not in this subword.
|
|
break
|
|
|
|
if bidx < field.low:
|
|
# insert padding before
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low - 1}:{bidx}] = '0;")
|
|
|
|
if field.high >= accesswidth:
|
|
# field gets truncated
|
|
r_low = field.low
|
|
r_high = accesswidth - 1
|
|
f_low = 0
|
|
f_high = accesswidth - 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 = do_bitswap(do_slice(self.exp.dereferencer.get_value(field), f_high, f_low))
|
|
else:
|
|
value = do_slice(self.exp.dereferencer.get_value(field), f_high, f_low)
|
|
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;")
|
|
bidx = accesswidth
|
|
else:
|
|
# field fits in subword
|
|
value = self.exp.dereferencer.get_value(field)
|
|
if field.msb < field.lsb:
|
|
# Field gets bitswapped since it is in [low:high] orientation
|
|
value = do_bitswap(value)
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
|
|
bidx = field.high + 1
|
|
|
|
# pad up remainder of subword
|
|
if bidx < accesswidth:
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{accesswidth-1}:{bidx}] = '0;")
|
|
self.current_offset += 1
|
|
|
|
# Assign remainder of subwords from read buffer
|
|
n_subwords = regwidth // accesswidth
|
|
rbuf = self.exp.read_buffering.get_rbuf_data(node)
|
|
for i in range(1, n_subwords):
|
|
rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)"
|
|
bslice = f"[{(i + 1) * accesswidth - 1}:{i*accesswidth}]"
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;")
|
|
self.current_offset += 1
|
|
|
|
def process_wide_reg(self, node: RegNode, accesswidth: int) -> None:
|
|
bus_width = self.exp.cpuif.data_width
|
|
|
|
subword_idx = 0
|
|
current_bit = 0 # Bit-offset within the wide register
|
|
access_strb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
|
|
# Fields are sorted by ascending low bit
|
|
for field in node.fields():
|
|
if not field.is_sw_readable:
|
|
continue
|
|
|
|
# insert zero assignment before this field if needed
|
|
if field.low >= accesswidth*(subword_idx+1):
|
|
# field does not start in this subword
|
|
if current_bit > accesswidth * subword_idx:
|
|
# current subword had content. Assign remainder
|
|
low = current_bit % accesswidth
|
|
high = bus_width - 1
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
|
|
self.current_offset += 1
|
|
|
|
# Advance to subword that contains the start of the field
|
|
subword_idx = field.low // accesswidth
|
|
current_bit = accesswidth * subword_idx
|
|
|
|
if current_bit != field.low:
|
|
# assign zero up to start of this field
|
|
low = current_bit % accesswidth
|
|
high = (field.low % accesswidth) - 1
|
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
|
|
current_bit = field.low
|
|
|
|
|
|
# Assign field
|
|
# loop until the entire field's assignments have been generated
|
|
field_pos = field.low
|
|
while current_bit <= field.high:
|
|
# Assign the field
|
|
rd_strb = f"({access_strb}[{subword_idx}] && !decoded_req_is_wr)"
|
|
if (field_pos == field.low) and (field.high < accesswidth*(subword_idx+1)):
|
|
# entire field fits into this subword
|
|
low = field.low - accesswidth * subword_idx
|
|
high = field.high - accesswidth * subword_idx
|
|
|
|
value = self.exp.dereferencer.get_value(field)
|
|
if field.msb < field.lsb:
|
|
# Field gets bitswapped since it is in [low:high] orientation
|
|
value = do_bitswap(value)
|
|
|
|
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 = do_bitswap(do_slice(self.exp.dereferencer.get_value(field), f_high, f_low))
|
|
else:
|
|
value = do_slice(self.exp.dereferencer.get_value(field), 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 = do_bitswap(do_slice(self.exp.dereferencer.get_value(field), f_high, f_low))
|
|
else:
|
|
value = do_slice(self.exp.dereferencer.get_value(field), 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
|