Implement read buffering. (#22)
This commit is contained in:
@@ -17,6 +17,7 @@ from .cpuif import CpuifBase
|
||||
from .cpuif.apb4 import APB4_Cpuif
|
||||
from .hwif import Hwif
|
||||
from .write_buffering import WriteBuffering
|
||||
from .read_buffering import ReadBuffering
|
||||
|
||||
class RegblockExporter:
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
@@ -31,7 +32,8 @@ class RegblockExporter:
|
||||
self.address_decode = AddressDecode(self)
|
||||
self.field_logic = FieldLogic(self)
|
||||
self.readback = None # type: Readback
|
||||
self.write_buffering = None # type: WriteBuffering
|
||||
self.write_buffering = WriteBuffering(self)
|
||||
self.read_buffering = ReadBuffering(self)
|
||||
self.dereferencer = Dereferencer(self)
|
||||
self.min_read_latency = 0
|
||||
self.min_write_latency = 0
|
||||
@@ -145,7 +147,6 @@ class RegblockExporter:
|
||||
self,
|
||||
retime_read_fanin
|
||||
)
|
||||
self.write_buffering = WriteBuffering(self)
|
||||
|
||||
# Validate that there are no unsupported constructs
|
||||
validator = DesignValidator(self)
|
||||
@@ -161,6 +162,7 @@ class RegblockExporter:
|
||||
"cpuif": self.cpuif,
|
||||
"hwif": self.hwif,
|
||||
"write_buffering": self.write_buffering,
|
||||
"read_buffering": self.read_buffering,
|
||||
"get_resetsignal": self.dereferencer.get_resetsignal,
|
||||
"address_decode": self.address_decode,
|
||||
"field_logic": self.field_logic,
|
||||
|
||||
@@ -175,8 +175,13 @@ class FieldLogic:
|
||||
"""
|
||||
Asserted when field is software accessed (read)
|
||||
"""
|
||||
strb = self.exp.dereferencer.get_access_strobe(field)
|
||||
return f"{strb} && !decoded_req_is_wr"
|
||||
buffer_reads = field.parent.get_property('buffer_reads')
|
||||
if buffer_reads:
|
||||
rstrb = self.exp.read_buffering.get_trigger(field.parent)
|
||||
return rstrb
|
||||
else:
|
||||
strb = self.exp.dereferencer.get_access_strobe(field)
|
||||
return f"{strb} && !decoded_req_is_wr"
|
||||
|
||||
def get_swmod_identifier(self, field: 'FieldNode') -> str:
|
||||
"""
|
||||
@@ -186,6 +191,7 @@ class FieldLogic:
|
||||
w_modifiable = field.is_sw_writable
|
||||
r_modifiable = (field.get_property('onread') is not None)
|
||||
buffer_writes = field.parent.get_property('buffer_writes')
|
||||
buffer_reads = field.parent.get_property('buffer_reads')
|
||||
|
||||
if w_modifiable and not r_modifiable:
|
||||
# assert swmod only on sw write
|
||||
@@ -200,10 +206,18 @@ class FieldLogic:
|
||||
|
||||
if w_modifiable and r_modifiable:
|
||||
# assert swmod on both sw read and write
|
||||
if buffer_writes:
|
||||
astrb = self.exp.dereferencer.get_access_strobe(field)
|
||||
wstrb = self.exp.write_buffering.get_write_strobe(field)
|
||||
rstrb = f"{astrb} && !decoded_req_is_wr"
|
||||
astrb = self.exp.dereferencer.get_access_strobe(field)
|
||||
if buffer_writes or buffer_reads:
|
||||
if buffer_reads:
|
||||
rstrb = self.exp.read_buffering.get_trigger(field.parent)
|
||||
else:
|
||||
rstrb = f"{astrb} && !decoded_req_is_wr"
|
||||
|
||||
if buffer_writes:
|
||||
wstrb = self.exp.write_buffering.get_write_strobe(field)
|
||||
else:
|
||||
wstrb = f"{astrb} && decoded_req_is_wr"
|
||||
|
||||
return f"{wstrb} || {rstrb}"
|
||||
else:
|
||||
# Unbuffered. Use decoder strobe directly
|
||||
@@ -213,7 +227,11 @@ class FieldLogic:
|
||||
if not w_modifiable and r_modifiable:
|
||||
# assert swmod only on sw read
|
||||
astrb = self.exp.dereferencer.get_access_strobe(field)
|
||||
return f"{astrb} && !decoded_req_is_wr"
|
||||
if buffer_reads:
|
||||
rstrb = self.exp.read_buffering.get_trigger(field.parent)
|
||||
else:
|
||||
rstrb = f"{astrb} && !decoded_req_is_wr"
|
||||
return rstrb
|
||||
|
||||
# Not sw modifiable
|
||||
return "1'b0"
|
||||
|
||||
@@ -13,8 +13,14 @@ class _OnRead(NextStateConditional):
|
||||
return field.get_property('onread') == self.onreadtype
|
||||
|
||||
def get_predicate(self, field: 'FieldNode') -> str:
|
||||
strb = self.exp.dereferencer.get_access_strobe(field)
|
||||
return f"{strb} && !decoded_req_is_wr"
|
||||
if field.parent.get_property('buffer_reads'):
|
||||
# Is buffered read. Use alternate strobe
|
||||
rstrb = self.exp.read_buffering.get_trigger(field.parent)
|
||||
return rstrb
|
||||
else:
|
||||
# is regular register
|
||||
strb = self.exp.dereferencer.get_access_strobe(field)
|
||||
return f"{strb} && !decoded_req_is_wr"
|
||||
|
||||
|
||||
class ClearOnRead(_OnRead):
|
||||
|
||||
@@ -109,6 +109,7 @@ module {{module_name}} (
|
||||
assign cpuif_wr_err = '0;
|
||||
|
||||
{%- if has_buffered_write_regs %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Write double-buffers
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -116,7 +117,6 @@ module {{module_name}} (
|
||||
|
||||
{{write_buffering.get_implementation()|indent}}
|
||||
{%- endif %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Field logic
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -126,6 +126,15 @@ module {{module_name}} (
|
||||
|
||||
{{field_logic.get_implementation()|indent}}
|
||||
|
||||
{%- if has_buffered_read_regs %}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Read double-buffers
|
||||
//--------------------------------------------------------------------------
|
||||
{{read_buffering.get_storage_struct()|indent}}
|
||||
|
||||
{{read_buffering.get_implementation()|indent}}
|
||||
{%- endif %}
|
||||
//--------------------------------------------------------------------------
|
||||
// Readback
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
58
src/peakrdl_regblock/read_buffering/__init__.py
Normal file
58
src/peakrdl_regblock/read_buffering/__init__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from systemrdl.node import AddrmapNode, RegNode, FieldNode, SignalNode
|
||||
|
||||
from .storage_generator import RBufStorageStructGenerator
|
||||
from .implementation_generator import RBufLogicGenerator
|
||||
from ..utils import get_indexed_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..exporter import RegblockExporter
|
||||
|
||||
|
||||
class ReadBuffering:
|
||||
def __init__(self, exp:'RegblockExporter'):
|
||||
self.exp = exp
|
||||
|
||||
@property
|
||||
def top_node(self) -> 'AddrmapNode':
|
||||
return self.exp.top_node
|
||||
|
||||
def get_storage_struct(self) -> str:
|
||||
struct_gen = RBufStorageStructGenerator()
|
||||
s = struct_gen.get_struct(self.top_node, "rbuf_storage_t")
|
||||
assert s is not None
|
||||
return s + "\nrbuf_storage_t rbuf_storage;"
|
||||
|
||||
def get_implementation(self) -> str:
|
||||
gen = RBufLogicGenerator(self)
|
||||
s = gen.get_content(self.top_node)
|
||||
assert s is not None
|
||||
return s
|
||||
|
||||
def get_trigger(self, node: RegNode) -> str:
|
||||
trigger = node.get_property('rbuffer_trigger')
|
||||
|
||||
if isinstance(trigger, RegNode):
|
||||
# Trigger is a register.
|
||||
# trigger when lowermost address of the register is written
|
||||
regwidth = node.get_property('regwidth')
|
||||
accesswidth = node.get_property('accesswidth')
|
||||
strb_prefix = self.exp.dereferencer.get_access_strobe(trigger, reduce_substrobes=False)
|
||||
|
||||
if accesswidth < regwidth:
|
||||
return f"{strb_prefix}[0] && !decoded_req_is_wr"
|
||||
else:
|
||||
return f"{strb_prefix} && !decoded_req_is_wr"
|
||||
elif isinstance(trigger, SignalNode):
|
||||
s = self.exp.dereferencer.get_value(trigger)
|
||||
if trigger.get_property('activehigh'):
|
||||
return s
|
||||
else:
|
||||
return f"~{s}"
|
||||
else:
|
||||
# Trigger is a field or propref bit
|
||||
return self.exp.dereferencer.get_value(trigger)
|
||||
|
||||
def get_rbuf_data(self, node: RegNode) -> str:
|
||||
return "rbuf_storage." + get_indexed_path(self.top_node, node) + ".data"
|
||||
@@ -0,0 +1,59 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from systemrdl.component import Reg
|
||||
from systemrdl.node import RegNode
|
||||
|
||||
from ..forloop_generator import RDLForLoopGenerator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ReadBuffering
|
||||
|
||||
class RBufLogicGenerator(RDLForLoopGenerator):
|
||||
i_type = "genvar"
|
||||
def __init__(self, rbuf: 'ReadBuffering') -> None:
|
||||
super().__init__()
|
||||
self.rbuf = rbuf
|
||||
self.exp = rbuf.exp
|
||||
self.template = self.exp.jj_env.get_template(
|
||||
"read_buffering/template.sv"
|
||||
)
|
||||
|
||||
def enter_Reg(self, node: RegNode) -> None:
|
||||
super().enter_Reg(node)
|
||||
assert isinstance(node.inst, Reg)
|
||||
|
||||
if not node.get_property('buffer_reads'):
|
||||
return
|
||||
|
||||
context = {
|
||||
'node': node,
|
||||
'rbuf': self.rbuf,
|
||||
'get_assignments': self.get_assignments,
|
||||
}
|
||||
self.add_content(self.template.render(context))
|
||||
|
||||
|
||||
|
||||
def get_assignments(self, node: RegNode) -> str:
|
||||
data = self.rbuf.get_rbuf_data(node)
|
||||
bidx = 0
|
||||
s = []
|
||||
for field in node.fields():
|
||||
if bidx < field.low:
|
||||
# zero padding before field
|
||||
s.append(f"{data}[{field.low-1}:{bidx}] = '0;")
|
||||
|
||||
value = self.exp.dereferencer.get_value(field)
|
||||
if field.msb < field.lsb:
|
||||
# Field gets bitswapped since it is in [low:high] orientation
|
||||
value = f"{{<<{{{value}}}}}"
|
||||
s.append(f"{data}[{field.high}:{field.low}] = {value};")
|
||||
|
||||
bidx = field.high + 1
|
||||
|
||||
regwidth = node.get_property('regwidth')
|
||||
if bidx < regwidth:
|
||||
# zero padding after last field
|
||||
s.append(f"{data}[{regwidth-1}:{bidx}] = '0;")
|
||||
|
||||
return "\n".join(s)
|
||||
18
src/peakrdl_regblock/read_buffering/storage_generator.py
Normal file
18
src/peakrdl_regblock/read_buffering/storage_generator.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from systemrdl.node import FieldNode, RegNode
|
||||
|
||||
from ..struct_generator import RDLStructGenerator
|
||||
|
||||
class RBufStorageStructGenerator(RDLStructGenerator):
|
||||
|
||||
def enter_Field(self, node: FieldNode) -> None:
|
||||
# suppress parent class's field behavior
|
||||
pass
|
||||
|
||||
def enter_Reg(self, node: RegNode) -> None:
|
||||
super().enter_Reg(node)
|
||||
|
||||
if not node.get_property('buffer_reads'):
|
||||
return
|
||||
|
||||
regwidth = node.get_property('regwidth')
|
||||
self.add_member("data", regwidth)
|
||||
5
src/peakrdl_regblock/read_buffering/template.sv
Normal file
5
src/peakrdl_regblock/read_buffering/template.sv
Normal file
@@ -0,0 +1,5 @@
|
||||
always_ff @(posedge clk) begin
|
||||
if({{rbuf.get_trigger(node)}}) begin
|
||||
{{get_assignments(node)|indent(8)}}
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from systemrdl.node import RegNode
|
||||
|
||||
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:
|
||||
@@ -76,19 +77,31 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
|
||||
self.current_offset = start_offset + n_regs * dim
|
||||
|
||||
|
||||
def enter_Reg(self, node: 'RegNode') -> None:
|
||||
def enter_Reg(self, node: RegNode) -> None:
|
||||
if not node.has_sw_readable:
|
||||
return
|
||||
|
||||
accesswidth = node.get_property('accesswidth')
|
||||
regwidth = node.get_property('regwidth')
|
||||
if accesswidth < 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)
|
||||
|
||||
|
||||
def process_reg(self, node: 'RegNode') -> None:
|
||||
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
|
||||
@@ -100,11 +113,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
|
||||
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 = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
|
||||
else:
|
||||
value = self.exp.dereferencer.get_value(field)
|
||||
value = f"{{<<{{{value}}}}}"
|
||||
|
||||
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
|
||||
|
||||
@@ -118,7 +130,98 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
|
||||
self.current_offset += 1
|
||||
|
||||
|
||||
def process_wide_reg(self, node: 'RegNode', accesswidth: int) -> None:
|
||||
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 = 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;")
|
||||
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 = f"{{<<{{{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
|
||||
@@ -162,11 +265,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
|
||||
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 = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
|
||||
else:
|
||||
value = self.exp.dereferencer.get_value(field)
|
||||
value = f"{{<<{{{value}}}}}"
|
||||
|
||||
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;")
|
||||
|
||||
|
||||
@@ -90,8 +90,8 @@ class DesignScanner(RDLListener):
|
||||
accesswidth = node.get_property('accesswidth')
|
||||
self.cpuif_data_width = max(self.cpuif_data_width, accesswidth)
|
||||
|
||||
self.has_buffered_write_regs = self.has_buffered_write_regs or node.get_property('buffer_writes')
|
||||
self.has_buffered_read_regs = self.has_buffered_read_regs or node.get_property('buffer_reads')
|
||||
self.has_buffered_write_regs = self.has_buffered_write_regs or bool(node.get_property('buffer_writes'))
|
||||
self.has_buffered_read_regs = self.has_buffered_read_regs or bool(node.get_property('buffer_reads'))
|
||||
|
||||
def enter_Signal(self, node: 'SignalNode') -> None:
|
||||
if node.get_property('field_reset'):
|
||||
|
||||
Reference in New Issue
Block a user