First read/write!
This commit is contained in:
@@ -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"),
|
||||
@@ -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;
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
@@ -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};"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;",
|
||||
]
|
||||
|
||||
@@ -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 -%}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
31
peakrdl/regblock/readback/__init__.py
Normal file
31
peakrdl/regblock/readback/__init__.py
Normal 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)
|
||||
107
peakrdl/regblock/readback/generators.py
Normal file
107
peakrdl/regblock/readback/generators.py
Normal 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
|
||||
45
peakrdl/regblock/readback/templates/readback.sv
Normal file
45
peakrdl/regblock/readback/templates/readback.sv
Normal 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 %}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user