First read/write!

This commit is contained in:
Alex Mykyta
2021-11-16 23:29:58 -08:00
parent d5c5d42390
commit 249fc2df7c
33 changed files with 1332 additions and 202 deletions

View File

@@ -1,17 +1,17 @@
from ..base import CpuifBase
class APB4_Cpuif(CpuifBase):
template_path = "cpuif/apb4/apb4_tmpl.sv"
class APB3_Cpuif(CpuifBase):
template_path = "cpuif/apb3/apb3_tmpl.sv"
@property
def port_declaration(self) -> str:
return "apb4_intf.slave s_apb"
return "apb3_intf.slave s_apb"
def signal(self, name:str) -> str:
return "s_apb." + name
return "s_apb." + name.upper()
class APB4_Cpuif_flattened(APB4_Cpuif):
class APB3_Cpuif_flattened(APB3_Cpuif):
@property
def port_declaration(self) -> str:
# TODO: Reference data/addr width from verilog parameter perhaps?
@@ -19,10 +19,8 @@ class APB4_Cpuif_flattened(APB4_Cpuif):
"input wire " + self.signal("psel"),
"input wire " + self.signal("penable"),
"input wire " + self.signal("pwrite"),
"input wire " + self.signal("pprot"),
f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"),
f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"),
f"input wire [{(self.data_width / 8)-1}:0] " + self.signal("pstrb"),
"output logic " + self.signal("pready"),
f"output logic [{self.data_width-1}:0] " + self.signal("prdata"),
"output logic " + self.signal("pslverr"),

View File

@@ -10,7 +10,6 @@ always_ff {{get_always_ff_event(cpuif_reset)}} begin
cpuif_req_is_wr <= '0;
cpuif_addr <= '0;
cpuif_wr_data <= '0;
cpuif_wr_bitstrb <= '0;
end else begin
if(~is_active) begin
if({{cpuif.signal("psel")}}) begin
@@ -19,9 +18,6 @@ always_ff {{get_always_ff_event(cpuif_reset)}} begin
cpuif_req_is_wr <= {{cpuif.signal("pwrite")}};
cpuif_addr <= {{cpuif.signal("paddr")}}[ADDR_WIDTH-1:0];
cpuif_wr_data <= {{cpuif.signal("pwdata")}};
for(int i=0; i<DATA_WIDTH/8; i++) begin
cpuif_wr_bitstrb[i*8 +: 8] <= {{"{8{"}}{{cpuif.signal("pstrb")}}[i]{{"}}"}};
end
end
end else begin
cpuif_req <= '0;
@@ -31,6 +27,7 @@ always_ff {{get_always_ff_event(cpuif_reset)}} begin
end
end
end
assign cpuif_wr_bitstrb = '0;
// Response
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;

View File

@@ -68,104 +68,130 @@ class Dereferencer:
return self.hwif.get_input_identifier(obj)
if isinstance(obj, PropertyReference):
if isinstance(obj.node, FieldNode):
return self.get_field_propref_value(obj.node, obj.name)
elif isinstance(obj.node, RegNode):
return self.get_reg_propref_value(obj.node, obj.name)
else:
raise RuntimeError
# Value reduction properties.
# Wrap with the appropriate Verilog reduction operator
val = self.get_value(obj.node)
if obj.name == "anded":
return f"&({val})"
elif obj.name == "ored":
return f"|({val})"
elif obj.name == "xored":
return f"^({val})"
raise RuntimeError("Unhandled reference to: %s" % obj)
# references that directly access a property value
if obj.name in {
'decrvalue',
'enable',
'haltenable',
'haltmask',
'hwenable',
'hwmask',
'incrvalue',
'mask',
'reset',
'resetsignal',
}:
return self.get_value(obj.node.get_property(obj.name))
elif obj.name in {'incr', 'decr'}:
prop_value = obj.node.get_property(obj.name)
if prop_value is None:
# unset by the user, points to the implied internal signal
return self.field_logic.get_counter_control_identifier(obj)
else:
return self.get_value(prop_value)
elif obj.name == "next":
prop_value = obj.node.get_property(obj.name)
if prop_value is None:
# unset by the user, points to the implied internal signal
return self.field_logic.get_field_next_identifier(obj.node)
else:
return self.get_value(prop_value)
# References to another component value, or an implied input
if obj.name in {'hwclr', 'hwset'}:
prop_value = obj.node.get_property(obj.name)
def get_field_propref_value(self, field: FieldNode, prop_name: str) -> str:
# Value reduction properties.
# Wrap with the appropriate Verilog reduction operator
val = self.get_value(field)
if prop_name == "anded":
return f"&({val})"
elif prop_name == "ored":
return f"|({val})"
elif prop_name == "xored":
return f"^({val})"
# references that directly access a property value
if prop_name in {
'decrvalue',
'enable',
'haltenable',
'haltmask',
'hwenable',
'hwmask',
'incrvalue',
'mask',
'reset',
'resetsignal',
}:
return self.get_value(field.get_property(prop_name))
# Counter properties
if prop_name == 'incr':
prop_value = field.get_property(prop_name)
if prop_value is None:
# unset by the user, points to the implied internal signal
return self.field_logic.get_counter_incr_identifier(field)
else:
return self.get_value(prop_value)
elif prop_name == 'decr':
prop_value = field.get_property(prop_name)
if prop_value is None:
# unset by the user, points to the implied internal signal
return self.field_logic.get_counter_decr_identifier(field)
else:
return self.get_value(prop_value)
# Field Next
if prop_name == "next":
prop_value = field.get_property(prop_name)
if prop_value is None:
# unset by the user, points to the implied internal signal
return self.field_logic.get_field_next_identifier(field)
else:
return self.get_value(prop_value)
# References to another component value, or an implied input
if prop_name in {'hwclr', 'hwset'}:
prop_value = field.get_property(prop_name)
if prop_value is True:
# Points to inferred hwif input
return self.hwif.get_implied_prop_input_identifier(field, prop_name)
elif prop_value is False:
# This should never happen, as this is checked by the compiler's validator
raise RuntimeError
else:
return self.get_value(prop_value)
# References to another component value, or an implied input
# May have a complementary partner property
complementary_pairs = {
"we": "wel",
"wel": "we",
"swwe": "swwel",
"swwel": "swwe",
}
if prop_name in complementary_pairs:
prop_value = field.get_property(prop_name)
if prop_value is True:
# Points to inferred hwif input
return self.hwif.get_implied_prop_input_identifier(field, prop_name)
elif prop_value is False:
# Try complementary property
prop_value = field.get_property(complementary_pairs[prop_name])
if prop_value is True:
# Points to inferred hwif input
return self.hwif.get_input_identifier(obj)
return f"!({self.hwif.get_implied_prop_input_identifier(field, prop_name)})"
elif prop_value is False:
# This should never happen, as this is checked by the compiler's validator
raise RuntimeError
else:
return self.get_value(prop_value)
return f"!({self.get_value(prop_value)})"
else:
return self.get_value(prop_value)
# References to another component value, or an implied input
# May have a complementary partner property
complementary_pairs = {
"we": "wel",
"wel": "we",
"swwe": "swwel",
"swwel": "swwe",
}
if obj.name in complementary_pairs:
prop_value = obj.node.get_property(obj.name)
if prop_value is True:
# Points to inferred hwif input
return self.hwif.get_input_identifier(obj)
elif prop_value is False:
# Try complementary property
prop_value = obj.node.get_property(complementary_pairs[obj.name])
if prop_value is True:
# Points to inferred hwif input
return f"!({self.hwif.get_input_identifier(obj)})"
elif prop_value is False:
# This should never happen, as this is checked by the compiler's validator
raise RuntimeError
else:
return f"!({self.get_value(prop_value)})"
else:
return self.get_value(prop_value)
if prop_name == "swacc":
return self.field_logic.get_swacc_identifier(field)
if prop_name == "swmod":
return self.field_logic.get_swmod_identifier(field)
"""
TODO:
Resolves to an internal signal used in the field's logic
decrsaturate
decrthreshold
halt
incrsaturate
incrthreshold
intr
overflow
saturate
swacc
swmod
threshold
"""
raise RuntimeError("Unhandled reference to: %s->%s" % (field, prop_name))
raise RuntimeError("Unhandled reference to: %s", obj)
"""
TODO:
Resolves to an internal signal used in the field's logic
decrsaturate
decrthreshold
incrsaturate
incrthreshold
overflow
saturate
threshold
"""
def get_reg_propref_value(self, reg: RegNode, prop_name: str) -> str:
# TODO: halt, intr
raise NotImplementedError
def get_access_strobe(self, obj: Union[RegNode, FieldNode]) -> str:
"""

View File

@@ -7,10 +7,11 @@ from systemrdl.node import AddrmapNode, RootNode
from .addr_decode import AddressDecode
from .field_logic import FieldLogic
from .dereferencer import Dereferencer
from .readback_mux import ReadbackMux
from .readback import Readback
from .signals import InferredSignal, SignalBase
from .cpuif.apb4 import APB4_Cpuif
from .cpuif import CpuifBase
from .cpuif.apb3 import APB3_Cpuif
from .hwif import Hwif
from .utils import get_always_ff_event
@@ -25,11 +26,13 @@ class RegblockExporter:
self.top_node = None # type: AddrmapNode
self.hwif = None # type: Hwif
self.cpuif = None # type: CpuifBase
self.address_decode = AddressDecode(self)
self.field_logic = FieldLogic(self)
self.readback_mux = ReadbackMux(self)
self.readback = Readback(self)
self.dereferencer = Dereferencer(self)
self.default_resetsignal = InferredSignal("rst")
self.cpuif_reset = self.default_resetsignal
if user_template_dir:
@@ -63,7 +66,7 @@ class RegblockExporter:
self.top_node = node
cpuif_cls = kwargs.pop("cpuif_cls", APB4_Cpuif)
cpuif_cls = kwargs.pop("cpuif_cls", APB3_Cpuif)
hwif_cls = kwargs.pop("hwif_cls", Hwif)
module_name = kwargs.pop("module_name", self.top_node.inst_name)
package_name = kwargs.pop("package_name", module_name + "_pkg")
@@ -79,13 +82,13 @@ class RegblockExporter:
# TODO: Scan design...
# TODO: derive this from somewhere
cpuif_reset = self.default_resetsignal
reset_signals = set([cpuif_reset, self.default_resetsignal])
self.cpuif_reset = self.default_resetsignal
reset_signals = set([self.cpuif_reset, self.default_resetsignal])
cpuif = cpuif_cls(
self.cpuif = cpuif_cls(
self,
cpuif_reset=cpuif_reset, # TODO:
data_width=32, # TODO: derive from the accesswidth used by regs
cpuif_reset=self.cpuif_reset, # TODO:
data_width=32, # TODO: derive from the regwidth used by regs
addr_width=32 # TODO:
)
@@ -100,15 +103,13 @@ class RegblockExporter:
"data_width": 32, # TODO:
"addr_width": 32, # TODO:
"reset_signals": reset_signals,
"cpuif_reset": cpuif_reset,
"user_signals": [], # TODO:
"interrupts": [], # TODO:
"cpuif": cpuif,
"cpuif": self.cpuif,
"hwif": self.hwif,
"address_decode": self.address_decode,
"field_logic": self.field_logic,
"readback_mux": self.readback_mux,
"get_always_ff_event": get_always_ff_event,
"readback": self.readback,
}
# Write out design

View File

@@ -78,7 +78,7 @@ class FieldLogic:
path = get_indexed_path(self.top_node, node)
return f"field_combo.{path}.next"
def get_counter_control_identifier(self, prop_ref: PropertyReference) -> str:
def get_counter_incr_identifier(self, field: 'FieldNode') -> str:
"""
Return the Veriog string that represents the field's inferred incr/decr strobe signal.
prop_ref will be either an incr or decr property reference, and it is already known that
@@ -87,6 +87,47 @@ class FieldLogic:
# TODO: Implement this
raise NotImplementedError
def get_counter_decr_identifier(self, field: 'FieldNode') -> str:
"""
Return the Veriog string that represents the field's inferred incr/decr strobe signal.
prop_ref will be either an incr or decr property reference, and it is already known that
the incr/decr properties are not explicitly set by the user and are therefore inferred.
"""
# TODO: Implement this
raise NotImplementedError
def get_swacc_identifier(self, field: 'FieldNode') -> str:
"""
Asserted when field is software accessed (read)
"""
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
def get_swmod_identifier(self, field: 'FieldNode') -> str:
"""
Asserted when field is modified by software (written or read with a
set or clear side effect).
"""
w_modifiable = field.is_sw_writable
r_modifiable = (field.get_property("onread") is not None)
strb = self.exp.dereferencer.get_access_strobe(field)
if w_modifiable and not r_modifiable:
# assert swmod only on sw write
return f"{strb} && decoded_req_is_wr"
if w_modifiable and r_modifiable:
# assert swmod on all sw actions
return strb
if not w_modifiable and r_modifiable:
# assert swmod only on sw read
return f"{strb} && !decoded_req_is_wr"
# Not sw modifiable
return "1'b0"
#---------------------------------------------------------------------------
# Field Logic Conditionals
#---------------------------------------------------------------------------

View File

@@ -54,16 +54,20 @@ class FieldLogicGenerator(RDLForLoopGenerator):
def __init__(self, field_logic: 'FieldLogic'):
super().__init__()
self.field_logic = field_logic
self.template = self.field_logic.exp.jj_env.get_template(
self.exp = field_logic.exp
self.field_storage_template = self.field_logic.exp.jj_env.get_template(
"field_logic/templates/field_storage.sv"
)
def enter_Field(self, node: 'FieldNode') -> None:
# If a field doesn't implement storage, it is not relevant here
if not node.implements_storage:
return
if node.implements_storage:
self.generate_field_storage(node)
self.assign_field_outputs(node)
def generate_field_storage(self, node: 'FieldNode') -> None:
conditionals = self.field_logic.get_conditionals(node)
extra_combo_signals = OrderedDict()
for conditional in conditionals:
@@ -74,11 +78,11 @@ class FieldLogicGenerator(RDLForLoopGenerator):
if sig is not None:
resetsignal = RDLSignal(sig)
else:
resetsignal = self.field_logic.exp.default_resetsignal
resetsignal = self.exp.default_resetsignal
reset_value = node.get_property("reset")
if reset_value is not None:
reset_value_str = self.field_logic.exp.dereferencer.get_value(reset_value)
reset_value_str = self.exp.dereferencer.get_value(reset_value)
else:
# 5.9.1-g: If no reset value given, the field is not reset, even if it has a resetsignal.
reset_value_str = None
@@ -87,13 +91,55 @@ class FieldLogicGenerator(RDLForLoopGenerator):
context = {
'node': node,
'reset': reset_value_str,
'field_path': get_indexed_path(self.field_logic.top_node, node),
'field_path': get_indexed_path(self.exp.top_node, node),
'extra_combo_signals': extra_combo_signals,
'conditionals': conditionals,
'resetsignal': resetsignal,
'get_always_ff_event': get_always_ff_event,
'has_value_output': self.field_logic.exp.hwif.has_value_output,
'get_output_identifier': self.field_logic.exp.hwif.get_output_identifier,
}
self.add_content(self.template.render(context))
self.add_content(self.field_storage_template.render(context))
def assign_field_outputs(self, node: 'FieldNode') -> None:
field_path = get_indexed_path(self.exp.top_node, node)
# Field value output
if self.exp.hwif.has_value_output(node):
output_identifier = self.exp.hwif.get_output_identifier(node)
self.add_content(
f"assign {output_identifier} = field_storage.{field_path};"
)
# Inferred logical reduction outputs
if node.get_property("anded"):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "anded")
value = self.exp.dereferencer.get_field_propref_value(node, "anded")
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property("ored"):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "ored")
value = self.exp.dereferencer.get_field_propref_value(node, "ored")
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property("xored"):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "xored")
value = self.exp.dereferencer.get_field_propref_value(node, "xored")
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property("swmod"):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swmod")
value = self.field_logic.get_swmod_identifier(node)
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property("swacc"):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swacc")
value = self.field_logic.get_swacc_identifier(node)
self.add_content(
f"assign {output_identifier} = {value};"
)

View File

@@ -14,7 +14,8 @@ class _OnRead(NextStateConditional):
def get_predicate(self, field: 'FieldNode') -> str:
strb = self.exp.dereferencer.get_access_strobe(field)
return f"decoded_req && !decoded_req_is_wr && {strb}"
return f"{strb} && !decoded_req_is_wr"
class ClearOnRead(_OnRead):
comment = "SW clear on read"

View File

@@ -8,6 +8,7 @@ if TYPE_CHECKING:
from systemrdl.node import FieldNode
# TODO: implement sw=w1 "write once" fields
# TODO: Implement swwe/swwel masking properties
class _OnWrite(NextStateConditional):
onwritetype = None
@@ -16,7 +17,15 @@ class _OnWrite(NextStateConditional):
def get_predicate(self, field: 'FieldNode') -> str:
strb = self.exp.dereferencer.get_access_strobe(field)
return f"decoded_req && decoded_req_is_wr && {strb}"
return f"{strb} && decoded_req_is_wr"
def _wr_data(self, field: 'FieldNode') -> str:
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{decoded_wr_data[{field.high}:{field.low}]}}}}"
else:
value = f"decoded_wr_data[{field.high}:{field.low}]"
return value
class WriteOneSet(_OnWrite):
comment = "SW write 1 set"
@@ -25,7 +34,7 @@ class WriteOneSet(_OnWrite):
def get_assignments(self, field: 'FieldNode') -> List[str]:
field_path = self.get_field_path(field)
return [
f"field_combo.{field_path}.next = field_storage.{field_path} | decoded_wr_data;",
f"field_combo.{field_path}.next = field_storage.{field_path} | {self._wr_data(field)}];",
f"field_combo.{field_path}.load_next = '1;",
]
@@ -36,7 +45,7 @@ class WriteOneClear(_OnWrite):
def get_assignments(self, field: 'FieldNode') -> List[str]:
field_path = self.get_field_path(field)
return [
f"field_combo.{field_path}.next = field_storage.{field_path} & ~decoded_wr_data;",
f"field_combo.{field_path}.next = field_storage.{field_path} & ~{self._wr_data(field)};",
f"field_combo.{field_path}.load_next = '1;",
]
@@ -47,7 +56,7 @@ class WriteOneToggle(_OnWrite):
def get_assignments(self, field: 'FieldNode') -> List[str]:
field_path = self.get_field_path(field)
return [
f"field_combo.{field_path}.next = field_storage.{field_path} ^ decoded_wr_data;",
f"field_combo.{field_path}.next = field_storage.{field_path} ^ {self._wr_data(field)};",
f"field_combo.{field_path}.load_next = '1;",
]
@@ -58,7 +67,7 @@ class WriteZeroSet(_OnWrite):
def get_assignments(self, field: 'FieldNode') -> List[str]:
field_path = self.get_field_path(field)
return [
f"field_combo.{field_path}.next = field_storage.{field_path} | ~decoded_wr_data;",
f"field_combo.{field_path}.next = field_storage.{field_path} | ~{self._wr_data(field)};",
f"field_combo.{field_path}.load_next = '1;",
]
@@ -69,7 +78,7 @@ class WriteZeroClear(_OnWrite):
def get_assignments(self, field: 'FieldNode') -> List[str]:
field_path = self.get_field_path(field)
return [
f"field_combo.{field_path}.next = field_storage.{field_path} & decoded_wr_data;",
f"field_combo.{field_path}.next = field_storage.{field_path} & {self._wr_data(field)};",
f"field_combo.{field_path}.load_next = '1;",
]
@@ -80,7 +89,7 @@ class WriteZeroToggle(_OnWrite):
def get_assignments(self, field: 'FieldNode') -> List[str]:
field_path = self.get_field_path(field)
return [
f"field_combo.{field_path}.next = field_storage.{field_path} ^ ~decoded_wr_data;",
f"field_combo.{field_path}.next = field_storage.{field_path} ^ ~{self._wr_data(field)};",
f"field_combo.{field_path}.load_next = '1;",
]
@@ -113,6 +122,6 @@ class Write(_OnWrite):
def get_assignments(self, field: 'FieldNode') -> List[str]:
field_path = self.get_field_path(field)
return [
f"field_combo.{field_path}.next = decoded_wr_data;",
f"field_combo.{field_path}.next = {self._wr_data(field)};",
f"field_combo.{field_path}.load_next = '1;",
]

View File

@@ -21,6 +21,3 @@ always_ff {{get_always_ff_event(resetsignal)}} begin
field_storage.{{field_path}} <= field_combo.{{field_path}}.next;
end
end
{% if has_value_output(node) -%}
assign {{get_output_identifier(node)}} = field_storage.{{field_path}};
{%- endif -%}

View File

@@ -31,9 +31,9 @@ class LoopBody(Body):
)
class ForLoopGenerator:
i_type = "int"
loop_body_cls = LoopBody
def __init__(self) -> None:
self._loop_level = 0
@@ -45,7 +45,7 @@ class ForLoopGenerator:
def push_loop(self, dim: int) -> None:
i = f"i{self._loop_level}"
b = LoopBody(dim, i, self.i_type)
b = self.loop_body_cls(dim, i, self.i_type)
self._stack.append(b)
self._loop_level += 1

View File

@@ -201,7 +201,6 @@ class Hwif:
for prop_name in ["anded", "ored", "xored", "swmod", "swacc"]:
if node.get_property(prop_name):
contents.append(f"logic {prop_name};")
# TODO: Are there was_written/was_read strobes too?
return contents
@@ -247,9 +246,7 @@ class Hwif:
# TODO: Implement this
raise NotImplementedError()
elif isinstance(obj, PropertyReference):
assert obj.name in {'hwclr', 'hwset', 'swwe', 'swwel', 'we', 'wel'}
path = get_indexed_path(self.top_node, obj.node)
return "hwif_in." + path + "." + obj.name
return self.get_implied_prop_input_identifier(obj.node, obj.name)
raise RuntimeError("Unhandled reference to: %s", obj)
@@ -274,10 +271,11 @@ class Hwif:
path = get_indexed_path(self.top_node, obj)
return "hwif_out." + path + ".value"
elif isinstance(obj, PropertyReference):
assert obj.name in {"anded", "ored", "xored", "swmod", "swacc"}
# TODO: this might be dead code.
# not sure when anything would call this function with a prop ref
# when dereferencer's get_value is more useful here
assert obj.node.get_property(obj.name)
path = get_indexed_path(self.top_node, obj.node)
return "hwif_out." + path + "." + obj.name
return self.get_implied_prop_output_identifier(obj.node, obj.name)
raise RuntimeError("Unhandled reference to: %s", obj)

View File

@@ -13,7 +13,8 @@ module {{module_name}} (
{{interrupt.port_declaration}},
{%- endfor %}
{{cpuif.port_declaration|indent(8)}},
{{cpuif.port_declaration|indent(8)}}
{%- if hwif.has_input_struct or hwif.has_output_struct %},{% endif %}
{{hwif.port_declaration|indent(8)}}
);
@@ -71,27 +72,10 @@ module {{module_name}} (
{{field_logic.get_storage_struct()|indent}}
{{field_logic.get_implementation()|indent}}
// TODO: output port signal assignment (aka output mapping layer)
//--------------------------------------------------------------------------
// Readback mux
// Readback
//--------------------------------------------------------------------------
logic readback_err;
logic readback_done;
logic [DATA_WIDTH-1:0] readback_data;
{{readback_mux.get_implementation()|indent}}
always_ff {{get_always_ff_event(cpuif_reset)}} begin
if({{cpuif_reset.activehigh_identifier}}) begin
cpuif_rd_ack <= '0;
cpuif_rd_data <= '0;
cpuif_rd_err <= '0;
end else begin
cpuif_rd_ack <= readback_done;
cpuif_rd_data <= readback_data;
cpuif_rd_err <= readback_err;
end
end
{{readback.get_implementation()|indent}}
endmodule

View File

@@ -0,0 +1,31 @@
from typing import TYPE_CHECKING
from .generators import ReadbackAssignmentGenerator
from ..utils import get_always_ff_event
if TYPE_CHECKING:
from ..exporter import RegblockExporter
from systemrdl.node import AddrmapNode
class Readback:
def __init__(self, exp:'RegblockExporter'):
self.exp = exp
@property
def top_node(self) -> 'AddrmapNode':
return self.exp.top_node
def get_implementation(self) -> str:
gen = ReadbackAssignmentGenerator(self.exp)
array_assignments = gen.get_content(self.top_node)
context = {
"array_assignments" : array_assignments,
"array_size" : gen.current_offset,
"get_always_ff_event": get_always_ff_event,
"cpuif_reset": self.exp.cpuif_reset,
}
template = self.exp.jj_env.get_template(
"readback/templates/readback.sv"
)
return template.render(context)

View File

@@ -0,0 +1,107 @@
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

View File

@@ -0,0 +1,45 @@
{% if array_assignments is not none %}
logic readback_err;
logic readback_done;
logic [DATA_WIDTH-1:0] readback_data;
logic [DATA_WIDTH-1:0] readback_array[{{array_size}}];
{{array_assignments}}
always_comb begin
automatic logic [DATA_WIDTH-1:0] readback_data_var;
readback_done = decoded_req & ~decoded_req_is_wr;
readback_err = '0;
readback_data_var = '0;
for(int i=0; i<{{array_size}}; i++) begin
readback_data_var |= readback_array[i];
end
readback_data = readback_data_var;
end
always_ff {{get_always_ff_event(cpuif_reset)}} begin
if({{cpuif_reset.activehigh_identifier}}) begin
cpuif_rd_ack <= '0;
cpuif_rd_data <= '0;
cpuif_rd_err <= '0;
end else begin
cpuif_rd_ack <= readback_done;
cpuif_rd_data <= readback_data;
cpuif_rd_err <= readback_err;
end
end
{%- else %}
always_ff {{get_always_ff_event(cpuif_reset)}} begin
if({{cpuif_reset.activehigh_identifier}}) begin
cpuif_rd_ack <= '0;
end else begin
cpuif_rd_ack <= decoded_req & ~decoded_req_is_wr;
end
end
assign cpuif_rd_data = '0;
assign cpuif_rd_err = '0;
{% endif %}

View File

@@ -1,23 +0,0 @@
import re
from typing import TYPE_CHECKING, List
from systemrdl.node import AddrmapNode
if TYPE_CHECKING:
from .exporter import RegblockExporter
class ReadbackMux:
def __init__(self, exp:'RegblockExporter'):
self.exp = exp
@property
def top_node(self) -> AddrmapNode:
return self.exp.top_node
def get_implementation(self) -> str:
# TODO: Count the number of readable registers
# TODO: Emit the declaration for the readback array
# TODO: Always comb block to assign & mask all elements
# TODO: Separate always_comb block to OR reduce down
return "//TODO"