parent
3fbed68365
commit
877a1062a6
|
@ -9,6 +9,7 @@ from ._cd import DomainError, ClockDomain
|
||||||
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
|
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
|
||||||
from ._ir import Instance, IOBufferInstance
|
from ._ir import Instance, IOBufferInstance
|
||||||
from ._mem import FrozenMemory, MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
|
from ._mem import FrozenMemory, MemoryData, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
|
||||||
|
from ._nir import CombinationalCycle
|
||||||
from ._rec import Record
|
from ._rec import Record
|
||||||
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
|
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ __all__ = [
|
||||||
# _ir
|
# _ir
|
||||||
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
|
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
|
||||||
"Instance", "IOBufferInstance",
|
"Instance", "IOBufferInstance",
|
||||||
|
# _nir
|
||||||
|
"CombinationalCycle",
|
||||||
# _mem
|
# _mem
|
||||||
"FrozenMemory", "MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
|
"FrozenMemory", "MemoryData", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
|
||||||
# _rec
|
# _rec
|
||||||
|
|
|
@ -709,7 +709,7 @@ class NetlistEmitter:
|
||||||
def emit_signal(self, signal) -> _nir.Value:
|
def emit_signal(self, signal) -> _nir.Value:
|
||||||
if signal in self.netlist.signals:
|
if signal in self.netlist.signals:
|
||||||
return self.netlist.signals[signal]
|
return self.netlist.signals[signal]
|
||||||
value = self.netlist.alloc_late_value(len(signal))
|
value = self.netlist.alloc_late_value(signal)
|
||||||
self.netlist.signals[signal] = value
|
self.netlist.signals[signal] = value
|
||||||
for bit, net in enumerate(value):
|
for bit, net in enumerate(value):
|
||||||
self.late_net_to_signal[net] = (signal, bit)
|
self.late_net_to_signal[net] = (signal, bit)
|
||||||
|
@ -1738,6 +1738,7 @@ def build_netlist(fragment, ports=(), *, name="top", all_undef_to_ff=False, **kw
|
||||||
design = fragment.prepare(ports=ports, hierarchy=(name,), **kwargs)
|
design = fragment.prepare(ports=ports, hierarchy=(name,), **kwargs)
|
||||||
netlist = _nir.Netlist()
|
netlist = _nir.Netlist()
|
||||||
_emit_netlist(netlist, design, all_undef_to_ff=all_undef_to_ff)
|
_emit_netlist(netlist, design, all_undef_to_ff=all_undef_to_ff)
|
||||||
|
netlist.check_comb_cycles()
|
||||||
netlist.resolve_all_nets()
|
netlist.resolve_all_nets()
|
||||||
_compute_net_flows(netlist)
|
_compute_net_flows(netlist)
|
||||||
_compute_ports(netlist)
|
_compute_ports(netlist)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Iterable
|
from typing import Iterable, Any
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
from ._ast import SignalDict
|
from ._ast import SignalDict
|
||||||
|
@ -7,8 +7,9 @@ from . import _ast
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Netlist core
|
# Netlist core
|
||||||
|
"CombinationalCycle",
|
||||||
"Net", "Value", "IONet", "IOValue",
|
"Net", "Value", "IONet", "IOValue",
|
||||||
"FormatValue", "Format",
|
"FormatValue", "Format", "SignalField",
|
||||||
"Netlist", "ModuleNetFlow", "IODirection", "Module", "Cell", "Top",
|
"Netlist", "ModuleNetFlow", "IODirection", "Module", "Cell", "Top",
|
||||||
# Computation cells
|
# Computation cells
|
||||||
"Operator", "Part",
|
"Operator", "Part",
|
||||||
|
@ -25,6 +26,10 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CombinationalCycle(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Net(int):
|
class Net(int):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
@ -335,6 +340,7 @@ class Netlist:
|
||||||
modules : list of ``Module``
|
modules : list of ``Module``
|
||||||
cells : list of ``Cell``
|
cells : list of ``Cell``
|
||||||
connections : dict of (negative) int to int
|
connections : dict of (negative) int to int
|
||||||
|
late_to_signal : dict of (late) Net to its Signal and bit number
|
||||||
io_ports : list of ``IOPort``
|
io_ports : list of ``IOPort``
|
||||||
signals : dict of Signal to ``Value``
|
signals : dict of Signal to ``Value``
|
||||||
signal_fields: dict of Signal to dict of tuple[str | int] to SignalField
|
signal_fields: dict of Signal to dict of tuple[str | int] to SignalField
|
||||||
|
@ -344,6 +350,7 @@ class Netlist:
|
||||||
self.modules: list[Module] = []
|
self.modules: list[Module] = []
|
||||||
self.cells: list[Cell] = [Top()]
|
self.cells: list[Cell] = [Top()]
|
||||||
self.connections: dict[Net, Net] = {}
|
self.connections: dict[Net, Net] = {}
|
||||||
|
self.late_to_signal: dict[Net, (_ast.Signal, int)] = {}
|
||||||
self.io_ports: list[_ast.IOPort] = []
|
self.io_ports: list[_ast.IOPort] = []
|
||||||
self.signals = SignalDict()
|
self.signals = SignalDict()
|
||||||
self.signal_fields = SignalDict()
|
self.signal_fields = SignalDict()
|
||||||
|
@ -405,9 +412,12 @@ class Netlist:
|
||||||
cell_idx = self.add_cell(cell)
|
cell_idx = self.add_cell(cell)
|
||||||
return Value(Net.from_cell(cell_idx, bit) for bit in range(width))
|
return Value(Net.from_cell(cell_idx, bit) for bit in range(width))
|
||||||
|
|
||||||
def alloc_late_value(self, width: int):
|
def alloc_late_value(self, signal: _ast.Signal):
|
||||||
self.last_late_net -= width
|
self.last_late_net -= len(signal)
|
||||||
return Value(Net.from_late(self.last_late_net + bit) for bit in range(width))
|
value = Value(Net.from_late(self.last_late_net + bit) for bit in range(len(signal)))
|
||||||
|
for bit, net in enumerate(value):
|
||||||
|
self.late_to_signal[net] = signal, bit
|
||||||
|
return value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def top(self):
|
def top(self):
|
||||||
|
@ -415,6 +425,62 @@ class Netlist:
|
||||||
assert isinstance(top, Top)
|
assert isinstance(top, Top)
|
||||||
return top
|
return top
|
||||||
|
|
||||||
|
def check_comb_cycles(self):
|
||||||
|
class Cycle:
|
||||||
|
def __init__(self, start):
|
||||||
|
self.start = start
|
||||||
|
self.path = []
|
||||||
|
|
||||||
|
checked = set()
|
||||||
|
busy = set()
|
||||||
|
|
||||||
|
def traverse(net):
|
||||||
|
if net in checked:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if net in busy:
|
||||||
|
return Cycle(net)
|
||||||
|
busy.add(net)
|
||||||
|
|
||||||
|
cycle = None
|
||||||
|
if net.is_const:
|
||||||
|
pass
|
||||||
|
elif net.is_late:
|
||||||
|
cycle = traverse(self.connections[net])
|
||||||
|
if cycle is not None:
|
||||||
|
sig, bit = self.late_to_signal[net]
|
||||||
|
cycle.path.append((sig, bit, sig.src_loc))
|
||||||
|
else:
|
||||||
|
for src, src_loc in self.cells[net.cell].comb_edges_to(net.bit):
|
||||||
|
cycle = traverse(src)
|
||||||
|
if cycle is not None:
|
||||||
|
cycle.path.append((self.cells[net.cell], net.bit, src_loc))
|
||||||
|
break
|
||||||
|
|
||||||
|
if cycle is not None and cycle.start == net:
|
||||||
|
msg = ["Combinational cycle detected, path:\n"]
|
||||||
|
for obj, bit, src_loc in reversed(cycle.path):
|
||||||
|
if isinstance(obj, _ast.Signal):
|
||||||
|
obj = f"signal {obj.name}"
|
||||||
|
elif isinstance(obj, Operator):
|
||||||
|
obj = f"operator {obj.operator}"
|
||||||
|
else:
|
||||||
|
obj = f"cell {obj.__class__.__name__}"
|
||||||
|
src_loc = "<unknown>:0" if src_loc is None else f"{src_loc[0]}:{src_loc[1]}"
|
||||||
|
msg.append(f" {src_loc}: {obj} bit {bit}\n")
|
||||||
|
raise CombinationalCycle("".join(msg))
|
||||||
|
|
||||||
|
busy.remove(net)
|
||||||
|
checked.add(net)
|
||||||
|
return cycle
|
||||||
|
|
||||||
|
for cell_idx, cell in enumerate(self.cells):
|
||||||
|
for net in cell.output_nets(cell_idx):
|
||||||
|
assert traverse(net) is None
|
||||||
|
for value in self.signals.values():
|
||||||
|
for net in value:
|
||||||
|
assert traverse(net) is None
|
||||||
|
|
||||||
|
|
||||||
class ModuleNetFlow(enum.Enum):
|
class ModuleNetFlow(enum.Enum):
|
||||||
"""Describes how a given Net flows into or out of a Module.
|
"""Describes how a given Net flows into or out of a Module.
|
||||||
|
@ -509,6 +575,9 @@ class Cell:
|
||||||
def resolve_nets(self, netlist: Netlist):
|
def resolve_nets(self, netlist: Netlist):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit: int) -> "Iterable[(Net, Any)]":
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Top(Cell):
|
class Top(Cell):
|
||||||
"""A special cell type representing top-level non-IO ports. Must be present in the netlist exactly
|
"""A special cell type representing top-level non-IO ports. Must be present in the netlist exactly
|
||||||
|
@ -558,6 +627,9 @@ class Top(Cell):
|
||||||
ports = "".join(ports)
|
ports = "".join(ports)
|
||||||
return f"(top{ports})"
|
return f"(top{ports})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class Operator(Cell):
|
class Operator(Cell):
|
||||||
"""Roughly corresponds to ``hdl.ast.Operator``.
|
"""Roughly corresponds to ``hdl.ast.Operator``.
|
||||||
|
@ -627,6 +699,28 @@ class Operator(Cell):
|
||||||
inputs = " ".join(repr(input) for input in self.inputs)
|
inputs = " ".join(repr(input) for input in self.inputs)
|
||||||
return f"({self.operator} {inputs})"
|
return f"({self.operator} {inputs})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
if len(self.inputs) == 1:
|
||||||
|
if self.operator == "~":
|
||||||
|
yield (self.inputs[0][bit], self.src_loc)
|
||||||
|
else:
|
||||||
|
for net in self.inputs[0]:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
elif len(self.inputs) == 2:
|
||||||
|
if self.operator in ("&", "|", "^"):
|
||||||
|
yield (self.inputs[0][bit], self.src_loc)
|
||||||
|
yield (self.inputs[1][bit], self.src_loc)
|
||||||
|
else:
|
||||||
|
for net in self.inputs[0]:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
for net in self.inputs[1]:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
else:
|
||||||
|
assert self.operator == "m"
|
||||||
|
yield (self.inputs[0][0], self.src_loc)
|
||||||
|
yield (self.inputs[1][bit], self.src_loc)
|
||||||
|
yield (self.inputs[2][bit], self.src_loc)
|
||||||
|
|
||||||
|
|
||||||
class Part(Cell):
|
class Part(Cell):
|
||||||
"""Corresponds to ``hdl.ast.Part``.
|
"""Corresponds to ``hdl.ast.Part``.
|
||||||
|
@ -666,6 +760,12 @@ class Part(Cell):
|
||||||
value_signed = "signed" if self.value_signed else "unsigned"
|
value_signed = "signed" if self.value_signed else "unsigned"
|
||||||
return f"(part {self.value} {value_signed} {self.offset} {self.width} {self.stride})"
|
return f"(part {self.value} {value_signed} {self.offset} {self.width} {self.stride})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
for net in self.value:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
for net in self.offset:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
|
||||||
|
|
||||||
class Matches(Cell):
|
class Matches(Cell):
|
||||||
"""A combinatorial cell performing a comparison like ``Value.matches``
|
"""A combinatorial cell performing a comparison like ``Value.matches``
|
||||||
|
@ -698,6 +798,10 @@ class Matches(Cell):
|
||||||
patterns = " ".join(self.patterns)
|
patterns = " ".join(self.patterns)
|
||||||
return f"(matches {self.value} {patterns})"
|
return f"(matches {self.value} {patterns})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
for net in self.value:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
|
||||||
|
|
||||||
class PriorityMatch(Cell):
|
class PriorityMatch(Cell):
|
||||||
"""Used to represent a single switch on the control plane of processes.
|
"""Used to represent a single switch on the control plane of processes.
|
||||||
|
@ -733,6 +837,11 @@ class PriorityMatch(Cell):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"(priority_match {self.en} {self.inputs})"
|
return f"(priority_match {self.en} {self.inputs})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
yield (self.en, self.src_loc)
|
||||||
|
for net in self.inputs[:bit + 1]:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
|
||||||
|
|
||||||
class Assignment:
|
class Assignment:
|
||||||
"""A single assignment in an ``AssignmentList``.
|
"""A single assignment in an ``AssignmentList``.
|
||||||
|
@ -809,6 +918,13 @@ class AssignmentList(Cell):
|
||||||
assignments = " ".join(repr(assign) for assign in self.assignments)
|
assignments = " ".join(repr(assign) for assign in self.assignments)
|
||||||
return f"(assignment_list {self.default} {assignments})"
|
return f"(assignment_list {self.default} {assignments})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
yield (self.default[bit], self.src_loc)
|
||||||
|
for assign in self.assignments:
|
||||||
|
yield (assign.cond, assign.src_loc)
|
||||||
|
if bit >= assign.start and bit < assign.start + len(assign.value):
|
||||||
|
yield (assign.value[bit - assign.start], assign.src_loc)
|
||||||
|
|
||||||
|
|
||||||
class FlipFlop(Cell):
|
class FlipFlop(Cell):
|
||||||
"""A flip-flop. ``data`` is the data input. ``init`` is the initial and async reset value.
|
"""A flip-flop. ``data`` is the data input. ``init`` is the initial and async reset value.
|
||||||
|
@ -853,6 +969,10 @@ class FlipFlop(Cell):
|
||||||
attributes = "".join(f" (attr {key} {val!r})" for key, val in self.attributes.items())
|
attributes = "".join(f" (attr {key} {val!r})" for key, val in self.attributes.items())
|
||||||
return f"(flipflop {self.data} {self.init} {self.clk_edge} {self.clk} {self.arst}{attributes})"
|
return f"(flipflop {self.data} {self.init} {self.clk_edge} {self.clk} {self.arst}{attributes})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
yield (self.clk, self.src_loc)
|
||||||
|
yield (self.arst, self.src_loc)
|
||||||
|
|
||||||
|
|
||||||
class Memory(Cell):
|
class Memory(Cell):
|
||||||
"""Corresponds to ``Memory``. ``init`` must have length equal to ``depth``.
|
"""Corresponds to ``Memory``. ``init`` must have length equal to ``depth``.
|
||||||
|
@ -960,6 +1080,10 @@ class AsyncReadPort(Cell):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"(read_port {self.memory} {self.width} {self.addr})"
|
return f"(read_port {self.memory} {self.width} {self.addr})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
for net in self.addr:
|
||||||
|
yield (net, self.src_loc)
|
||||||
|
|
||||||
|
|
||||||
class SyncReadPort(Cell):
|
class SyncReadPort(Cell):
|
||||||
"""A single synchronous read port of a memory. The cell output is the data port.
|
"""A single synchronous read port of a memory. The cell output is the data port.
|
||||||
|
@ -1004,6 +1128,9 @@ class SyncReadPort(Cell):
|
||||||
transparent_for = " ".join(str(port) for port in self.transparent_for)
|
transparent_for = " ".join(str(port) for port in self.transparent_for)
|
||||||
return f"(read_port {self.memory} {self.width} {self.addr} {self.en} {self.clk_edge} {self.clk} ({transparent_for}))"
|
return f"(read_port {self.memory} {self.width} {self.addr} {self.en} {self.clk_edge} {self.clk} ({transparent_for}))"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class AsyncPrint(Cell):
|
class AsyncPrint(Cell):
|
||||||
"""Corresponds to ``Print`` in the "comb" domain.
|
"""Corresponds to ``Print`` in the "comb" domain.
|
||||||
|
@ -1087,6 +1214,9 @@ class Initial(Cell):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"(initial)"
|
return f"(initial)"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class AnyValue(Cell):
|
class AnyValue(Cell):
|
||||||
"""Corresponds to ``AnyConst`` or ``AnySeq``. ``kind`` must be either ``'anyconst'``
|
"""Corresponds to ``AnyConst`` or ``AnySeq``. ``kind`` must be either ``'anyconst'``
|
||||||
|
@ -1117,6 +1247,9 @@ class AnyValue(Cell):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"({self.kind} {self.width})"
|
return f"({self.kind} {self.width})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class AsyncProperty(Cell):
|
class AsyncProperty(Cell):
|
||||||
"""Corresponds to ``Assert``, ``Assume``, or ``Cover`` in the "comb" domain.
|
"""Corresponds to ``Assert``, ``Assume``, or ``Cover`` in the "comb" domain.
|
||||||
|
@ -1274,6 +1407,10 @@ class Instance(Cell):
|
||||||
items = " ".join(items)
|
items = " ".join(items)
|
||||||
return f"(instance {self.type!r} {self.name!r} {items})"
|
return f"(instance {self.type!r} {self.name!r} {items})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
# don't ask me, I'm a housecat
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class IOBuffer(Cell):
|
class IOBuffer(Cell):
|
||||||
"""An IO buffer cell. This cell does two things:
|
"""An IO buffer cell. This cell does two things:
|
||||||
|
@ -1328,3 +1465,8 @@ class IOBuffer(Cell):
|
||||||
return f"(iob {self.dir.value} {self.port})"
|
return f"(iob {self.dir.value} {self.port})"
|
||||||
else:
|
else:
|
||||||
return f"(iob {self.dir.value} {self.port} {self.o} {self.oe})"
|
return f"(iob {self.dir.value} {self.port} {self.o} {self.oe})"
|
||||||
|
|
||||||
|
def comb_edges_to(self, bit):
|
||||||
|
if self.dir is not IODirection.Input:
|
||||||
|
yield (self.o[bit], self.src_loc)
|
||||||
|
yield (self.oe, self.src_loc)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from amaranth.hdl._cd import *
|
||||||
from amaranth.hdl._dsl import *
|
from amaranth.hdl._dsl import *
|
||||||
from amaranth.hdl._ir import *
|
from amaranth.hdl._ir import *
|
||||||
from amaranth.hdl._mem import *
|
from amaranth.hdl._mem import *
|
||||||
from amaranth.hdl._nir import SignalField
|
from amaranth.hdl._nir import SignalField, CombinationalCycle
|
||||||
|
|
||||||
from amaranth.lib import enum, data
|
from amaranth.lib import enum, data
|
||||||
|
|
||||||
|
@ -3542,3 +3542,22 @@ class FieldsTestCase(FHDLTestCase):
|
||||||
self.assertEqual(nl.signal_fields[s4], {
|
self.assertEqual(nl.signal_fields[s4], {
|
||||||
(): SignalField(nl.signals[s4], signed=False),
|
(): SignalField(nl.signals[s4], signed=False),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class CycleTestCase(FHDLTestCase):
|
||||||
|
def test_cycle(self):
|
||||||
|
a = Signal()
|
||||||
|
b = Signal()
|
||||||
|
m = Module()
|
||||||
|
m.d.comb += [
|
||||||
|
a.eq(~b),
|
||||||
|
b.eq(~a),
|
||||||
|
]
|
||||||
|
with self.assertRaisesRegex(CombinationalCycle,
|
||||||
|
r"^Combinational cycle detected, path:\n"
|
||||||
|
r".*test_hdl_ir.py:\d+: operator ~ bit 0\n"
|
||||||
|
r".*test_hdl_ir.py:\d+: signal b bit 0\n"
|
||||||
|
r".*test_hdl_ir.py:\d+: operator ~ bit 0\n"
|
||||||
|
r".*test_hdl_ir.py:\d+: signal a bit 0\n"
|
||||||
|
r"$"):
|
||||||
|
build_netlist(Fragment.get(m, None), [])
|
||||||
|
|
Loading…
Reference in a new issue