Files
PeakRDL-BusDecoder/src/peakrdl_regblock/field_logic/__init__.py
2025-10-10 22:30:59 -07:00

577 lines
22 KiB
Python

from typing import TYPE_CHECKING, Union
from systemrdl.rdltypes import PrecedenceType, InterruptType
from .bases import AssignmentPrecedence, NextStateConditional
from . import sw_onread
from . import sw_onwrite
from . import sw_singlepulse
from . import hw_write
from . import hw_set_clr
from . import hw_interrupts
from . import hw_interrupts_with_write
from ..utils import get_indexed_path
from ..sv_int import SVInt
from .generators import (
CombinationalStructGenerator,
FieldStorageStructGenerator,
FieldLogicGenerator,
)
if TYPE_CHECKING:
from typing import Dict, List
from systemrdl.node import AddrmapNode, FieldNode
from ..exporter import BusDecoderExporter, DesignState
class FieldLogic:
def __init__(self, exp: "BusDecoderExporter"):
self.exp = exp
self._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
self.init_conditionals()
@property
def ds(self) -> "DesignState":
return self.exp.ds
@property
def top_node(self) -> "AddrmapNode":
return self.exp.ds.top_node
def get_storage_struct(self) -> str:
struct_gen = FieldStorageStructGenerator(self)
s = struct_gen.get_struct(self.top_node, "field_storage_t")
# Only declare the storage struct if it exists
if s is None:
return ""
return s + "\nfield_storage_t field_storage;"
def get_combo_struct(self) -> str:
struct_gen = CombinationalStructGenerator(self)
s = struct_gen.get_struct(self.top_node, "field_combo_t")
# Only declare the storage struct if it exists
if s is None:
return ""
return s + "\nfield_combo_t field_combo;"
def get_implementation(self) -> str:
gen = FieldLogicGenerator(self)
s = gen.get_content(self.top_node)
if s is None:
return ""
return s
# ---------------------------------------------------------------------------
# Field utility functions
# ---------------------------------------------------------------------------
def get_storage_identifier(self, field: "FieldNode") -> str:
"""
Returns the Verilog string that represents the storage register element
for the referenced field
"""
assert field.implements_storage
path = get_indexed_path(self.top_node, field)
return f"field_storage.{path}.value"
def get_next_q_identifier(self, field: "FieldNode") -> str:
"""
Returns the Verilog string that represents the storage register element
for the delayed 'next' input value
"""
assert field.implements_storage
path = get_indexed_path(self.top_node, field)
return f"field_storage.{path}.next_q"
def get_field_combo_identifier(self, field: "FieldNode", name: str) -> str:
"""
Returns a Verilog string that represents a field's internal combinational
signal.
"""
assert field.implements_storage
path = get_indexed_path(self.top_node, field)
return f"field_combo.{path}.{name}"
def get_counter_incr_strobe(self, field: "FieldNode") -> str:
"""
Return the Verilog string that represents the field's incr strobe signal.
"""
prop_value = field.get_property("incr")
if prop_value:
return str(self.exp.dereferencer.get_value(prop_value))
# unset by the user, points to the implied input signal
return self.exp.hwif.get_implied_prop_input_identifier(field, "incr")
def get_counter_incrvalue(self, field: "FieldNode") -> Union[SVInt, str]:
"""
Return the string that represents the field's increment value
"""
incrvalue = field.get_property("incrvalue")
if incrvalue is not None:
return self.exp.dereferencer.get_value(incrvalue, field.width)
if field.get_property("incrwidth"):
return self.exp.hwif.get_implied_prop_input_identifier(field, "incrvalue")
return "1'b1"
def get_counter_incrsaturate_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("incrsaturate")
if prop_value is True:
return self.exp.dereferencer.get_value(2**field.width - 1, field.width)
return self.exp.dereferencer.get_value(prop_value, field.width)
def counter_incrsaturates(self, field: "FieldNode") -> bool:
"""
Returns True if the counter saturates
"""
return field.get_property("incrsaturate") is not False
def get_counter_incrthreshold_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("incrthreshold")
if isinstance(prop_value, bool):
# No explicit value set. use max
return self.exp.dereferencer.get_value(2**field.width - 1, field.width)
return self.exp.dereferencer.get_value(prop_value, field.width)
def get_counter_decr_strobe(self, field: "FieldNode") -> str:
"""
Return the Verilog string that represents the field's incr strobe signal.
"""
prop_value = field.get_property("decr")
if prop_value:
return str(self.exp.dereferencer.get_value(prop_value))
# unset by the user, points to the implied input signal
return self.exp.hwif.get_implied_prop_input_identifier(field, "decr")
def get_counter_decrvalue(self, field: "FieldNode") -> Union[SVInt, str]:
"""
Return the string that represents the field's decrement value
"""
decrvalue = field.get_property("decrvalue")
if decrvalue is not None:
return self.exp.dereferencer.get_value(decrvalue, field.width)
if field.get_property("decrwidth"):
return self.exp.hwif.get_implied_prop_input_identifier(field, "decrvalue")
return "1'b1"
def get_counter_decrsaturate_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("decrsaturate")
if prop_value is True:
return f"{field.width}'d0"
return self.exp.dereferencer.get_value(prop_value, field.width)
def counter_decrsaturates(self, field: "FieldNode") -> bool:
"""
Returns True if the counter saturates
"""
return field.get_property("decrsaturate") is not False
def get_counter_decrthreshold_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("decrthreshold")
if isinstance(prop_value, bool):
# No explicit value set. use min
return f"{field.width}'d0"
return self.exp.dereferencer.get_value(prop_value, field.width)
def get_swacc_identifier(self, field: "FieldNode") -> str:
"""
Asserted when field is software accessed (read or write)
"""
buffer_reads = field.parent.get_property("buffer_reads")
buffer_writes = field.parent.get_property("buffer_writes")
if buffer_reads and buffer_writes:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
wstrb = self.exp.write_buffering.get_write_strobe(field)
return f"{rstrb} || {wstrb}"
elif buffer_reads and not buffer_writes:
strb = self.exp.dereferencer.get_access_strobe(field)
rstrb = self.exp.read_buffering.get_trigger(field.parent)
return f"{rstrb} || ({strb} && decoded_req_is_wr)"
elif not buffer_reads and buffer_writes:
strb = self.exp.dereferencer.get_access_strobe(field)
wstrb = self.exp.write_buffering.get_write_strobe(field)
return f"{wstrb} || ({strb} && !decoded_req_is_wr)"
else:
strb = self.exp.dereferencer.get_access_strobe(field)
return strb
def get_rd_swacc_identifier(self, field: "FieldNode") -> str:
"""
Asserted when field is software accessed (read)
"""
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_wr_swacc_identifier(self, field: "FieldNode") -> str:
"""
Asserted when field is software accessed (write)
"""
buffer_writes = field.parent.get_property("buffer_writes")
if buffer_writes:
wstrb = self.exp.write_buffering.get_write_strobe(field)
return wstrb
else:
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
buffer_writes = field.parent.get_property("buffer_writes")
buffer_reads = field.parent.get_property("buffer_reads")
accesswidth = field.parent.get_property("accesswidth")
astrb = self.exp.dereferencer.get_access_strobe(field)
conditions = []
if r_modifiable:
if buffer_reads:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
else:
rstrb = f"{astrb} && !decoded_req_is_wr"
conditions.append(rstrb)
if w_modifiable:
if buffer_writes:
wstrb = self.exp.write_buffering.get_write_strobe(field)
else:
wstrb = f"{astrb} && decoded_req_is_wr"
# Due to 10.6.1-f, it is impossible for a field that is sw-writable to
# be split across subwords.
# Therefore it is ok to get the subword idx from only one of the bit offsets
# in order to compute the biten range
sidx = field.low // accesswidth
biten = self.get_wr_biten(field, sidx)
wstrb += f" && |({biten})"
conditions.append(wstrb)
if not conditions:
# Not sw modifiable
return "1'b0"
else:
return " || ".join(conditions)
def get_parity_identifier(self, field: "FieldNode") -> str:
"""
Returns the identifier for the stored 'golden' parity value of the field
"""
path = get_indexed_path(self.top_node, field)
return f"field_storage.{path}.parity"
def get_parity_error_identifier(self, field: "FieldNode") -> str:
"""
Returns the identifier for whether the field currently has a parity error
"""
path = get_indexed_path(self.top_node, field)
return f"field_combo.{path}.parity_error"
def has_next_q(self, field: "FieldNode") -> bool:
"""
Some fields require a delayed version of their 'next' input signal in
order to do edge-detection.
Returns True if this is the case.
"""
if field.get_property("intr type") in {
InterruptType.posedge,
InterruptType.negedge,
InterruptType.bothedge,
}:
return True
return False
def get_wbus_bitslice(self, field: "FieldNode", subword_idx: int = 0) -> str:
"""
Get the bitslice range string of the internal cpuif's data/biten bus
that corresponds to this field
"""
if field.parent.get_property("buffer_writes"):
# register is buffered.
# write buffer is the full width of the register. no need to deal with subwords
high = field.high
low = field.low
if field.msb < field.lsb:
# slice is for an msb0 field.
# mirror it
regwidth = field.parent.get_property("regwidth")
low = regwidth - 1 - low
high = regwidth - 1 - high
low, high = high, low
else:
# Regular non-buffered register
# For normal fields this ends up passing-through the field's low/high
# values unchanged.
# For fields within a wide register (accesswidth < regwidth), low/high
# may be shifted down and clamped depending on which sub-word is being accessed
accesswidth = field.parent.get_property("accesswidth")
# Shift based on subword
high = field.high - (subword_idx * accesswidth)
low = field.low - (subword_idx * accesswidth)
# clamp to accesswidth
high = max(min(high, accesswidth), 0)
low = max(min(low, accesswidth), 0)
if field.msb < field.lsb:
# slice is for an msb0 field.
# mirror it
bus_width = self.exp.cpuif.data_width
low = bus_width - 1 - low
high = bus_width - 1 - high
low, high = high, low
return f"[{high}:{low}]"
def get_wr_biten(self, field: "FieldNode", subword_idx: int = 0) -> str:
"""
Get the bit-enable slice that corresponds to this field
"""
if field.parent.get_property("buffer_writes"):
# Is buffered. Use value from write buffer
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
# accounts for it
bslice = self.get_wbus_bitslice(field)
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
return wbuf_prefix + ".biten" + bslice
else:
# Regular non-buffered register
bslice = self.get_wbus_bitslice(field, subword_idx)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = "decoded_wr_biten_bswap" + bslice
else:
value = "decoded_wr_biten" + bslice
return value
def get_wr_data(self, field: "FieldNode", subword_idx: int = 0) -> str:
"""
Get the write data slice that corresponds to this field
"""
if field.parent.get_property("buffer_writes"):
# Is buffered. Use value from write buffer
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
# accounts for it
bslice = self.get_wbus_bitslice(field)
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
return wbuf_prefix + ".data" + bslice
else:
# Regular non-buffered register
bslice = self.get_wbus_bitslice(field, subword_idx)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = "decoded_wr_data_bswap" + bslice
else:
value = "decoded_wr_data" + bslice
return value
# ---------------------------------------------------------------------------
# Field Logic Conditionals
# ---------------------------------------------------------------------------
def add_hw_conditional(
self, conditional: NextStateConditional, precedence: AssignmentPrecedence
) -> None:
"""
Register a NextStateConditional action for hardware-triggered field updates.
Categorizing conditionals correctly by hw/sw ensures that the RDL precedence
property can be reliably honored.
The ``precedence`` argument determines the conditional assignment's priority over
other assignments of differing precedence.
If multiple conditionals of the same precedence are registered, they are
searched sequentially and only the first to match the given field is used.
"""
if precedence not in self._hw_conditionals:
self._hw_conditionals[precedence] = []
self._hw_conditionals[precedence].append(conditional)
def add_sw_conditional(
self, conditional: NextStateConditional, precedence: AssignmentPrecedence
) -> None:
"""
Register a NextStateConditional action for software-triggered field updates.
Categorizing conditionals correctly by hw/sw ensures that the RDL precedence
property can be reliably honored.
The ``precedence`` argument determines the conditional assignment's priority over
other assignments of differing precedence.
If multiple conditionals of the same precedence are registered, they are
searched sequentially and only the first to match the given field is used.
"""
if precedence not in self._sw_conditionals:
self._sw_conditionals[precedence] = []
self._sw_conditionals[precedence].append(conditional)
def init_conditionals(self) -> None:
"""
Initialize all possible conditionals here.
Remember: The order in which conditionals are added matters within the
same assignment precedence.
"""
self.add_sw_conditional(
sw_onread.ClearOnRead(self.exp), AssignmentPrecedence.SW_ONREAD
)
self.add_sw_conditional(
sw_onread.SetOnRead(self.exp), AssignmentPrecedence.SW_ONREAD
)
self.add_sw_conditional(
sw_onwrite.Write(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteSet(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteClear(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteZeroToggle(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteZeroClear(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteZeroSet(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteOneToggle(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteOneClear(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteOneSet(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_singlepulse.Singlepulse(self.exp), AssignmentPrecedence.SW_SINGLEPULSE
)
self.add_hw_conditional(
hw_interrupts_with_write.PosedgeStickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.PosedgeStickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.NegedgeStickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.NegedgeStickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.BothedgeStickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.BothedgeStickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.StickyWE(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts_with_write.StickyWEL(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts_with_write.StickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.StickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts.PosedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.NegedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.BothedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.Sticky(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.Stickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_write.WEWrite(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_write.WELWrite(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_write.AlwaysWrite(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_set_clr.HWClear(self.exp), AssignmentPrecedence.HWCLR
)
self.add_hw_conditional(hw_set_clr.HWSet(self.exp), AssignmentPrecedence.HWSET)
def _get_X_conditionals(
self, conditionals: "Dict[int, List[NextStateConditional]]", field: "FieldNode"
) -> "List[NextStateConditional]":
result = []
precedences = sorted(conditionals.keys(), reverse=True)
for precedence in precedences:
for conditional in conditionals[precedence]:
if conditional.is_match(field):
result.append(conditional)
break
return result
def get_conditionals(self, field: "FieldNode") -> "List[NextStateConditional]":
"""
Get a list of NextStateConditional objects that apply to the given field.
The returned list is sorted in priority order - the conditional with highest
precedence is first in the list.
"""
sw_precedence = field.get_property("precedence") == PrecedenceType.sw
result = []
if sw_precedence:
result.extend(self._get_X_conditionals(self._sw_conditionals, field))
result.extend(self._get_X_conditionals(self._hw_conditionals, field))
if not sw_precedence:
result.extend(self._get_X_conditionals(self._sw_conditionals, field))
return result