ir: kill Fragment.ports
This commit is contained in:
parent
a725282751
commit
751e0f4b57
|
@ -401,7 +401,7 @@ class ModuleEmitter:
|
||||||
port_id=port_id, port_kind=flow.value,
|
port_id=port_id, port_kind=flow.value,
|
||||||
name=name, attrs=self.value_attrs.get(value, {}))
|
name=name, attrs=self.value_attrs.get(value, {}))
|
||||||
self.sigport_wires[name] = (wire, value)
|
self.sigport_wires[name] = (wire, value)
|
||||||
if flow == _nir.ModuleNetFlow.OUTPUT:
|
if flow == _nir.ModuleNetFlow.Output:
|
||||||
continue
|
continue
|
||||||
# If we just emitted an input or inout port, it is driving the value.
|
# If we just emitted an input or inout port, it is driving the value.
|
||||||
self.driven_sigports.add(name)
|
self.driven_sigports.add(name)
|
||||||
|
@ -463,7 +463,7 @@ class ModuleEmitter:
|
||||||
for submodule_idx in self.module.submodules:
|
for submodule_idx in self.module.submodules:
|
||||||
submodule = self.netlist.modules[submodule_idx]
|
submodule = self.netlist.modules[submodule_idx]
|
||||||
for _name, (value, flow) in submodule.ports.items():
|
for _name, (value, flow) in submodule.ports.items():
|
||||||
if flow == _nir.ModuleNetFlow.OUTPUT:
|
if flow == _nir.ModuleNetFlow.Output:
|
||||||
self.emit_driven_wire(value)
|
self.emit_driven_wire(value)
|
||||||
|
|
||||||
def sigspec(self, *parts: '_nir.Net | Iterable[_nir.Net]'):
|
def sigspec(self, *parts: '_nir.Net | Iterable[_nir.Net]'):
|
||||||
|
@ -989,10 +989,10 @@ class EmptyModuleChecker:
|
||||||
return module_idx in self.empty
|
return module_idx in self.empty
|
||||||
|
|
||||||
|
|
||||||
def convert_fragment(fragment, name="top", *, emit_src=True):
|
def convert_fragment(fragment, ports, name="top", *, emit_src=True, **kwargs):
|
||||||
assert isinstance(fragment, _ir.Fragment)
|
assert isinstance(fragment, _ir.Fragment)
|
||||||
name_map = _ast.SignalDict()
|
name_map = _ast.SignalDict()
|
||||||
netlist = _ir.build_netlist(fragment, name=name)
|
netlist = _ir.build_netlist(fragment, ports=ports, name=name, **kwargs)
|
||||||
empty_checker = EmptyModuleChecker(netlist)
|
empty_checker = EmptyModuleChecker(netlist)
|
||||||
builder = _Builder(emit_src=emit_src)
|
builder = _Builder(emit_src=emit_src)
|
||||||
for module_idx, module in enumerate(netlist.modules):
|
for module_idx, module in enumerate(netlist.modules):
|
||||||
|
@ -1011,14 +1011,18 @@ def convert(elaboratable, name="top", platform=None, *, ports=None, emit_src=Tru
|
||||||
if (ports is None and
|
if (ports is None and
|
||||||
hasattr(elaboratable, "signature") and
|
hasattr(elaboratable, "signature") and
|
||||||
isinstance(elaboratable.signature, wiring.Signature)):
|
isinstance(elaboratable.signature, wiring.Signature)):
|
||||||
ports = []
|
ports = {}
|
||||||
for _path, _member, value in elaboratable.signature.flatten(elaboratable):
|
for path, member, value in elaboratable.signature.flatten(elaboratable):
|
||||||
if isinstance(value, _ast.ValueCastable):
|
if isinstance(value, _ast.ValueCastable):
|
||||||
value = value.as_value()
|
value = value.as_value()
|
||||||
if isinstance(value, _ast.Value):
|
if isinstance(value, _ast.Value):
|
||||||
ports.append(value)
|
if member.flow == wiring.In:
|
||||||
|
dir = _ir.PortDirection.Input
|
||||||
|
else:
|
||||||
|
dir = _ir.PortDirection.Output
|
||||||
|
ports["__".join(path)] = (value, dir)
|
||||||
elif ports is None:
|
elif ports is None:
|
||||||
raise TypeError("The `convert()` function requires a `ports=` argument")
|
raise TypeError("The `convert()` function requires a `ports=` argument")
|
||||||
fragment = _ir.Fragment.get(elaboratable, platform).prepare(ports=ports, **kwargs)
|
fragment = _ir.Fragment.get(elaboratable, platform)
|
||||||
il_text, _name_map = convert_fragment(fragment, name, emit_src=emit_src)
|
il_text, _name_map = convert_fragment(fragment, ports, name, emit_src=emit_src, **kwargs)
|
||||||
return il_text
|
return il_text
|
||||||
|
|
|
@ -45,14 +45,18 @@ def convert(elaboratable, name="top", platform=None, *, ports=None, emit_src=Tru
|
||||||
if (ports is None and
|
if (ports is None and
|
||||||
hasattr(elaboratable, "signature") and
|
hasattr(elaboratable, "signature") and
|
||||||
isinstance(elaboratable.signature, wiring.Signature)):
|
isinstance(elaboratable.signature, wiring.Signature)):
|
||||||
ports = []
|
ports = {}
|
||||||
for path, member, value in elaboratable.signature.flatten(elaboratable):
|
for path, member, value in elaboratable.signature.flatten(elaboratable):
|
||||||
if isinstance(value, _ast.ValueCastable):
|
if isinstance(value, _ast.ValueCastable):
|
||||||
value = value.as_value()
|
value = value.as_value()
|
||||||
if isinstance(value, _ast.Value):
|
if isinstance(value, _ast.Value):
|
||||||
ports.append(value)
|
if member.flow == wiring.In:
|
||||||
|
dir = _ir.PortDirection.Input
|
||||||
|
else:
|
||||||
|
dir = _ir.PortDirection.Output
|
||||||
|
ports["__".join(path)] = (value, dir)
|
||||||
elif ports is None:
|
elif ports is None:
|
||||||
raise TypeError("The `convert()` function requires a `ports=` argument")
|
raise TypeError("The `convert()` function requires a `ports=` argument")
|
||||||
fragment = _ir.Fragment.get(elaboratable, platform).prepare(ports=ports, **kwargs)
|
fragment = _ir.Fragment.get(elaboratable, platform)
|
||||||
verilog_text, name_map = convert_fragment(fragment, name, emit_src=emit_src, strip_internal_attrs=strip_internal_attrs)
|
verilog_text, name_map = convert_fragment(fragment, ports, name, emit_src=emit_src, strip_internal_attrs=strip_internal_attrs, **kwargs)
|
||||||
return verilog_text
|
return verilog_text
|
||||||
|
|
|
@ -163,11 +163,11 @@ class Platform(ResourceManager, metaclass=ABCMeta):
|
||||||
if pin.dir == "io":
|
if pin.dir == "io":
|
||||||
add_pin_fragment(pin, self.get_diff_input_output(pin, port, attrs, invert))
|
add_pin_fragment(pin, self.get_diff_input_output(pin, port, attrs, invert))
|
||||||
|
|
||||||
fragment._propagate_ports(ports=self.iter_ports(), all_undef_as_ports=False)
|
ports = list(self.iter_ports())
|
||||||
return self.toolchain_prepare(fragment, name, **kwargs)
|
return self.toolchain_prepare(fragment, ports, name, **kwargs)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def toolchain_prepare(self, fragment, name, **kwargs):
|
def toolchain_prepare(self, fragment, ports, name, **kwargs):
|
||||||
"""
|
"""
|
||||||
Convert the ``fragment`` and constraints recorded in this :class:`Platform` into
|
Convert the ``fragment`` and constraints recorded in this :class:`Platform` into
|
||||||
a :class:`BuildPlan`.
|
a :class:`BuildPlan`.
|
||||||
|
@ -290,7 +290,7 @@ class TemplatedPlatform(Platform):
|
||||||
continue
|
continue
|
||||||
yield net_signal, port_signal, frequency
|
yield net_signal, port_signal, frequency
|
||||||
|
|
||||||
def toolchain_prepare(self, fragment, name, *, emit_src=True, **kwargs):
|
def toolchain_prepare(self, fragment, ports, name, *, emit_src=True, **kwargs):
|
||||||
# Restrict the name of the design to a strict alphanumeric character set. Platforms will
|
# Restrict the name of the design to a strict alphanumeric character set. Platforms will
|
||||||
# interpolate the name of the design in many different contexts: filesystem paths, Python
|
# interpolate the name of the design in many different contexts: filesystem paths, Python
|
||||||
# scripts, Tcl scripts, ad-hoc constraint files, and so on. It is not practical to add
|
# scripts, Tcl scripts, ad-hoc constraint files, and so on. It is not practical to add
|
||||||
|
@ -306,7 +306,8 @@ class TemplatedPlatform(Platform):
|
||||||
# and to incorporate the Amaranth version into generated code.
|
# and to incorporate the Amaranth version into generated code.
|
||||||
autogenerated = f"Automatically generated by Amaranth {__version__}. Do not edit."
|
autogenerated = f"Automatically generated by Amaranth {__version__}. Do not edit."
|
||||||
|
|
||||||
rtlil_text, self._name_map = rtlil.convert_fragment(fragment, name=name, emit_src=emit_src)
|
rtlil_text, self._name_map = rtlil.convert_fragment(fragment, ports=ports, name=name,
|
||||||
|
emit_src=emit_src)
|
||||||
|
|
||||||
# Retrieve an override specified in either the environment or as a kwarg.
|
# Retrieve an override specified in either the environment or as a kwarg.
|
||||||
# expected_type parameter is used to assert the type of kwargs, passing `None` will disable
|
# expected_type parameter is used to assert the type of kwargs, passing `None` will disable
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
import enum
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from .._utils import flatten, memoize
|
from .._utils import flatten, memoize
|
||||||
|
@ -8,7 +9,10 @@ from .. import tracer, _unused
|
||||||
from . import _ast, _cd, _ir, _nir
|
from . import _ast, _cd, _ir, _nir
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance"]
|
__all__ = [
|
||||||
|
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance",
|
||||||
|
"PortDirection", "Design", "build_netlist",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class UnusedElaboratable(_unused.UnusedMustUse):
|
class UnusedElaboratable(_unused.UnusedMustUse):
|
||||||
|
@ -65,7 +69,6 @@ class Fragment:
|
||||||
obj = new_obj
|
obj = new_obj
|
||||||
|
|
||||||
def __init__(self, *, src_loc=None):
|
def __init__(self, *, src_loc=None):
|
||||||
self.ports = _ast.SignalDict()
|
|
||||||
self.drivers = OrderedDict()
|
self.drivers = OrderedDict()
|
||||||
self.statements = {}
|
self.statements = {}
|
||||||
self.domains = OrderedDict()
|
self.domains = OrderedDict()
|
||||||
|
@ -76,19 +79,6 @@ class Fragment:
|
||||||
self.src_loc = src_loc
|
self.src_loc = src_loc
|
||||||
self.origins = None
|
self.origins = None
|
||||||
|
|
||||||
def add_ports(self, *ports, dir):
|
|
||||||
assert dir in ("i", "o", "io")
|
|
||||||
for port in flatten(ports):
|
|
||||||
self.ports[port] = dir
|
|
||||||
|
|
||||||
def iter_ports(self, dir=None):
|
|
||||||
if dir is None:
|
|
||||||
yield from self.ports
|
|
||||||
else:
|
|
||||||
for port, port_dir in self.ports.items():
|
|
||||||
if port_dir == dir:
|
|
||||||
yield port
|
|
||||||
|
|
||||||
def add_driver(self, signal, domain="comb"):
|
def add_driver(self, signal, domain="comb"):
|
||||||
assert isinstance(domain, str)
|
assert isinstance(domain, str)
|
||||||
if domain not in self.drivers:
|
if domain not in self.drivers:
|
||||||
|
@ -111,18 +101,6 @@ class Fragment:
|
||||||
for signal in signals:
|
for signal in signals:
|
||||||
yield domain, signal
|
yield domain, signal
|
||||||
|
|
||||||
def iter_signals(self):
|
|
||||||
signals = _ast.SignalSet()
|
|
||||||
signals |= self.ports.keys()
|
|
||||||
for domain, domain_signals in self.drivers.items():
|
|
||||||
if domain != "comb":
|
|
||||||
cd = self.domains[domain]
|
|
||||||
signals.add(cd.clk)
|
|
||||||
if cd.rst is not None:
|
|
||||||
signals.add(cd.rst)
|
|
||||||
signals |= domain_signals
|
|
||||||
return signals
|
|
||||||
|
|
||||||
def add_domains(self, *domains):
|
def add_domains(self, *domains):
|
||||||
for domain in flatten(domains):
|
for domain in flatten(domains):
|
||||||
assert isinstance(domain, _cd.ClockDomain)
|
assert isinstance(domain, _cd.ClockDomain)
|
||||||
|
@ -169,7 +147,6 @@ class Fragment:
|
||||||
# Merge subfragment's everything except clock domains into this fragment.
|
# Merge subfragment's everything except clock domains into this fragment.
|
||||||
# Flattening is done after clock domain propagation, so we can assume the domains
|
# Flattening is done after clock domain propagation, so we can assume the domains
|
||||||
# are already the same in every involved fragment in the first place.
|
# are already the same in every involved fragment in the first place.
|
||||||
self.ports.update(subfragment.ports)
|
|
||||||
for domain, signal in subfragment.iter_drivers():
|
for domain, signal in subfragment.iter_drivers():
|
||||||
self.add_driver(signal, domain)
|
self.add_driver(signal, domain)
|
||||||
for domain, statements in subfragment.statements.items():
|
for domain, statements in subfragment.statements.items():
|
||||||
|
@ -372,277 +349,78 @@ class Fragment:
|
||||||
self._propagate_domains_down()
|
self._propagate_domains_down()
|
||||||
return new_domains
|
return new_domains
|
||||||
|
|
||||||
def _prepare_use_def_graph(self, parent, level, uses, defs, ios, top):
|
def _prepare_ports(self, ports):
|
||||||
from ._mem import MemoryInstance
|
# Normalize ports to a list.
|
||||||
|
new_ports = []
|
||||||
def add_uses(*sigs, self=self):
|
if isinstance(ports, dict):
|
||||||
for sig in flatten(sigs):
|
for port_name, (signal, dir) in ports.items():
|
||||||
if sig not in uses:
|
new_ports.append((port_name, signal, dir))
|
||||||
uses[sig] = set()
|
elif isinstance(ports, (list, tuple)):
|
||||||
uses[sig].add(self)
|
for port in ports:
|
||||||
|
if isinstance(port, tuple):
|
||||||
def add_defs(*sigs):
|
port_name, signal, dir = port
|
||||||
for sig in flatten(sigs):
|
new_ports.append((port_name, signal, dir))
|
||||||
if sig not in defs:
|
|
||||||
defs[sig] = self
|
|
||||||
else:
|
else:
|
||||||
assert defs[sig] is self
|
new_ports.append((None, port, None))
|
||||||
|
else:
|
||||||
|
msg = f"`ports` must be a dict, a list or a tuple, not {ports!r}"
|
||||||
|
if isinstance(ports, _ast.Value):
|
||||||
|
msg += " (did you mean `ports=(<signal>,)`, rather than `ports=<signal>`?)"
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
def add_io(*sigs):
|
# Validate ports.
|
||||||
for sig in flatten(sigs):
|
prenamed_ports = set()
|
||||||
if sig not in ios:
|
for (port_name, signal, dir) in new_ports:
|
||||||
ios[sig] = self
|
if isinstance(port_name, str):
|
||||||
|
if port_name in prenamed_ports:
|
||||||
|
raise TypeError(f"Duplicate port name {port_name!r}")
|
||||||
else:
|
else:
|
||||||
assert ios[sig] is self
|
prenamed_ports.add(port_name)
|
||||||
|
elif port_name is not None:
|
||||||
|
raise TypeError(f"Port name must be a string, not {port_name!r}")
|
||||||
|
if dir is not None and not isinstance(dir, PortDirection):
|
||||||
|
raise TypeError(
|
||||||
|
f"Port direction must be a `PortDirection` instance or None, not {dir!r}")
|
||||||
|
if not isinstance(signal, (_ast.Signal, _ast.ClockSignal, _ast.ResetSignal)):
|
||||||
|
raise TypeError(f"Only signals may be added as ports, not {signal!r}")
|
||||||
|
|
||||||
# Collect all signals we're driving (on LHS of statements), and signals we're using
|
return new_ports
|
||||||
# (on RHS of statements, or in clock domains).
|
|
||||||
for stmts in self.statements.values():
|
|
||||||
for stmt in stmts:
|
|
||||||
add_uses(stmt._rhs_signals())
|
|
||||||
add_defs(stmt._lhs_signals())
|
|
||||||
|
|
||||||
for domain, _ in self.iter_sync():
|
def prepare(self, ports=(), *, hierarchy=("top",), legalize_assignments=False, missing_domain=lambda name: _cd.ClockDomain(name)):
|
||||||
cd = self.domains[domain]
|
|
||||||
add_uses(cd.clk)
|
|
||||||
if cd.rst is not None:
|
|
||||||
add_uses(cd.rst)
|
|
||||||
|
|
||||||
# Repeat for subfragments.
|
|
||||||
for subfrag, name, src_loc in self.subfragments:
|
|
||||||
if isinstance(subfrag, Instance):
|
|
||||||
for port_name, (value, dir) in subfrag.named_ports.items():
|
|
||||||
if dir == "i":
|
|
||||||
# Prioritize defs over uses.
|
|
||||||
rhs_without_outputs = value._rhs_signals() - subfrag.iter_ports(dir="o")
|
|
||||||
subfrag.add_ports(rhs_without_outputs, dir=dir)
|
|
||||||
add_uses(value._rhs_signals())
|
|
||||||
if dir == "o":
|
|
||||||
subfrag.add_ports(value._lhs_signals(), dir=dir)
|
|
||||||
add_defs(value._lhs_signals())
|
|
||||||
if dir == "io":
|
|
||||||
subfrag.add_ports(value._lhs_signals(), dir=dir)
|
|
||||||
add_io(value._lhs_signals())
|
|
||||||
elif isinstance(subfrag, MemoryInstance):
|
|
||||||
for port in subfrag._read_ports:
|
|
||||||
subfrag.add_ports(port._data._lhs_signals(), dir="o")
|
|
||||||
add_defs(port._data._lhs_signals())
|
|
||||||
for value in [port._addr, port._en]:
|
|
||||||
subfrag.add_ports(value._rhs_signals(), dir="i")
|
|
||||||
add_uses(value._rhs_signals())
|
|
||||||
for port in subfrag._write_ports:
|
|
||||||
for value in [port._addr, port._en, port._data]:
|
|
||||||
subfrag.add_ports(value._rhs_signals(), dir="i")
|
|
||||||
add_uses(value._rhs_signals())
|
|
||||||
for domain, _ in subfrag.iter_sync():
|
|
||||||
cd = subfrag.domains[domain]
|
|
||||||
add_uses(cd.clk)
|
|
||||||
if cd.rst is not None:
|
|
||||||
add_uses(cd.rst)
|
|
||||||
else:
|
|
||||||
parent[subfrag] = self
|
|
||||||
level [subfrag] = level[self] + 1
|
|
||||||
|
|
||||||
subfrag._prepare_use_def_graph(parent, level, uses, defs, ios, top)
|
|
||||||
|
|
||||||
def _propagate_ports(self, ports, all_undef_as_ports):
|
|
||||||
# Take this fragment graph:
|
|
||||||
#
|
|
||||||
# __ B (def: q, use: p r)
|
|
||||||
# /
|
|
||||||
# A (def: p, use: q r)
|
|
||||||
# \
|
|
||||||
# \_ C (def: r, use: p q)
|
|
||||||
#
|
|
||||||
# We need to consider three cases.
|
|
||||||
# 1. Signal p requires an input port in B;
|
|
||||||
# 2. Signal r requires an output port in C;
|
|
||||||
# 3. Signal r requires an output port in C and an input port in B.
|
|
||||||
#
|
|
||||||
# Adding these ports can be in general done in three steps for each signal:
|
|
||||||
# 1. Find the least common ancestor of all uses and defs.
|
|
||||||
# 2. Going upwards from the single def, add output ports.
|
|
||||||
# 3. Going upwards from all uses, add input ports.
|
|
||||||
|
|
||||||
parent = {self: None}
|
|
||||||
level = {self: 0}
|
|
||||||
uses = _ast.SignalDict()
|
|
||||||
defs = _ast.SignalDict()
|
|
||||||
ios = _ast.SignalDict()
|
|
||||||
self._prepare_use_def_graph(parent, level, uses, defs, ios, self)
|
|
||||||
|
|
||||||
ports = _ast.SignalSet(ports)
|
|
||||||
if all_undef_as_ports:
|
|
||||||
for sig in uses:
|
|
||||||
if sig in defs:
|
|
||||||
continue
|
|
||||||
ports.add(sig)
|
|
||||||
for sig in ports:
|
|
||||||
if sig not in uses:
|
|
||||||
uses[sig] = set()
|
|
||||||
uses[sig].add(self)
|
|
||||||
|
|
||||||
@memoize
|
|
||||||
def lca_of(fragu, fragv):
|
|
||||||
# Normalize fragu to be deeper than fragv.
|
|
||||||
if level[fragu] < level[fragv]:
|
|
||||||
fragu, fragv = fragv, fragu
|
|
||||||
# Find ancestor of fragu on the same level as fragv.
|
|
||||||
for _ in range(level[fragu] - level[fragv]):
|
|
||||||
fragu = parent[fragu]
|
|
||||||
# If fragv was the ancestor of fragv, we're done.
|
|
||||||
if fragu == fragv:
|
|
||||||
return fragu
|
|
||||||
# Otherwise, they are at the same level but in different branches. Step both fragu
|
|
||||||
# and fragv until we find the common ancestor.
|
|
||||||
while parent[fragu] != parent[fragv]:
|
|
||||||
fragu = parent[fragu]
|
|
||||||
fragv = parent[fragv]
|
|
||||||
return parent[fragu]
|
|
||||||
|
|
||||||
for sig in uses:
|
|
||||||
if sig in defs:
|
|
||||||
lca = reduce(lca_of, uses[sig], defs[sig])
|
|
||||||
else:
|
|
||||||
lca = reduce(lca_of, uses[sig])
|
|
||||||
|
|
||||||
for frag in uses[sig]:
|
|
||||||
if sig in defs and frag is defs[sig]:
|
|
||||||
continue
|
|
||||||
while frag != lca:
|
|
||||||
frag.add_ports(sig, dir="i")
|
|
||||||
frag = parent[frag]
|
|
||||||
|
|
||||||
if sig in defs:
|
|
||||||
frag = defs[sig]
|
|
||||||
while frag != lca:
|
|
||||||
frag.add_ports(sig, dir="o")
|
|
||||||
frag = parent[frag]
|
|
||||||
|
|
||||||
for sig in ios:
|
|
||||||
frag = ios[sig]
|
|
||||||
while frag is not None:
|
|
||||||
frag.add_ports(sig, dir="io")
|
|
||||||
frag = parent[frag]
|
|
||||||
|
|
||||||
for sig in ports:
|
|
||||||
if sig in ios:
|
|
||||||
continue
|
|
||||||
if sig in defs:
|
|
||||||
self.add_ports(sig, dir="o")
|
|
||||||
else:
|
|
||||||
self.add_ports(sig, dir="i")
|
|
||||||
|
|
||||||
def prepare(self, ports=None, missing_domain=lambda name: _cd.ClockDomain(name)):
|
|
||||||
from ._xfrm import DomainLowerer
|
from ._xfrm import DomainLowerer
|
||||||
|
|
||||||
|
ports = self._prepare_ports(ports)
|
||||||
|
|
||||||
new_domains = self._propagate_domains(missing_domain)
|
new_domains = self._propagate_domains(missing_domain)
|
||||||
|
for domain in new_domains:
|
||||||
|
ports.append((None, domain.clk, PortDirection.Input))
|
||||||
|
if domain.rst is not None:
|
||||||
|
ports.append((None, domain.rst, PortDirection.Input))
|
||||||
|
|
||||||
|
def resolve_signal(signal):
|
||||||
|
if isinstance(signal, _ast.ClockSignal):
|
||||||
|
domain = self.domains[signal.domain]
|
||||||
|
return domain.clk
|
||||||
|
elif isinstance(signal, _ast.ResetSignal):
|
||||||
|
domain = self.domains[signal.domain]
|
||||||
|
if domain.rst is None:
|
||||||
|
raise ValueError(f"Using ResetSignal for a reset-less domain {signal.domain}")
|
||||||
|
return domain.rst
|
||||||
|
else:
|
||||||
|
return signal
|
||||||
|
|
||||||
|
ports = [
|
||||||
|
(name, resolve_signal(signal), dir)
|
||||||
|
for name, signal, dir in ports
|
||||||
|
]
|
||||||
|
|
||||||
fragment = DomainLowerer()(self)
|
fragment = DomainLowerer()(self)
|
||||||
if ports is None:
|
if legalize_assignments:
|
||||||
fragment._propagate_ports(ports=(), all_undef_as_ports=True)
|
from ._xfrm import AssignmentLegalizer
|
||||||
else:
|
fragment = AssignmentLegalizer()(fragment)
|
||||||
if not isinstance(ports, tuple) and not isinstance(ports, list):
|
|
||||||
msg = "`ports` must be either a list or a tuple, not {!r}"\
|
|
||||||
.format(ports)
|
|
||||||
if isinstance(ports, _ast.Value):
|
|
||||||
msg += " (did you mean `ports=(<signal>,)`, rather than `ports=<signal>`?)"
|
|
||||||
raise TypeError(msg)
|
|
||||||
mapped_ports = []
|
|
||||||
# Lower late bound signals like ClockSignal() to ports.
|
|
||||||
port_lowerer = DomainLowerer(fragment.domains)
|
|
||||||
for port in ports:
|
|
||||||
if not isinstance(port, (_ast.Signal, _ast.ClockSignal, _ast.ResetSignal)):
|
|
||||||
raise TypeError("Only signals may be added as ports, not {!r}"
|
|
||||||
.format(port))
|
|
||||||
mapped_ports.append(port_lowerer.on_value(port))
|
|
||||||
# Add ports for all newly created missing clock domains, since not doing so defeats
|
|
||||||
# the purpose of domain auto-creation. (It's possible to refer to these ports before
|
|
||||||
# the domain actually exists through late binding, but it's inconvenient.)
|
|
||||||
for cd in new_domains:
|
|
||||||
mapped_ports.append(cd.clk)
|
|
||||||
if cd.rst is not None:
|
|
||||||
mapped_ports.append(cd.rst)
|
|
||||||
fragment._propagate_ports(ports=mapped_ports, all_undef_as_ports=False)
|
|
||||||
return fragment
|
|
||||||
|
|
||||||
def _assign_names_to_signals(self):
|
# Create design and let it do the rest.
|
||||||
"""Assign names to signals used in this fragment.
|
return Design(fragment, ports, hierarchy=hierarchy)
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
SignalDict of Signal to str
|
|
||||||
A mapping from signals used in this fragment to their local names. Because names are
|
|
||||||
deduplicated using local information only, the same signal used in a different fragment
|
|
||||||
may get a different name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
signal_names = _ast.SignalDict()
|
|
||||||
assigned_names = set()
|
|
||||||
|
|
||||||
def add_signal_name(signal):
|
|
||||||
if signal not in signal_names:
|
|
||||||
if signal.name not in assigned_names:
|
|
||||||
name = signal.name
|
|
||||||
else:
|
|
||||||
name = f"{signal.name}${len(assigned_names)}"
|
|
||||||
assert name not in assigned_names
|
|
||||||
signal_names[signal] = name
|
|
||||||
assigned_names.add(name)
|
|
||||||
|
|
||||||
for port in self.ports.keys():
|
|
||||||
add_signal_name(port)
|
|
||||||
|
|
||||||
for domain_name, domain_signals in self.drivers.items():
|
|
||||||
if domain_name != "comb":
|
|
||||||
domain = self.domains[domain_name]
|
|
||||||
add_signal_name(domain.clk)
|
|
||||||
if domain.rst is not None:
|
|
||||||
add_signal_name(domain.rst)
|
|
||||||
|
|
||||||
for statements in self.statements.values():
|
|
||||||
for statement in statements:
|
|
||||||
for signal in statement._lhs_signals() | statement._rhs_signals():
|
|
||||||
if not isinstance(signal, (_ast.ClockSignal, _ast.ResetSignal)):
|
|
||||||
add_signal_name(signal)
|
|
||||||
|
|
||||||
return signal_names
|
|
||||||
|
|
||||||
def _assign_names_to_fragments(self, hierarchy=("top",), *, _names=None):
|
|
||||||
"""Assign names to this fragment and its subfragments.
|
|
||||||
|
|
||||||
Subfragments may not necessarily have a name. This method assigns every such subfragment
|
|
||||||
a name, ``U$<number>``, where ``<number>`` is based on its location in the hierarchy.
|
|
||||||
|
|
||||||
Subfragment names may collide with signal names safely in Amaranth, but this may confuse
|
|
||||||
backends. This method assigns every such subfragment a name, ``<name>$U$<number>``, where
|
|
||||||
``name`` is its original name, and ``<number>`` is based on its location in the hierarchy.
|
|
||||||
|
|
||||||
Arguments
|
|
||||||
---------
|
|
||||||
hierarchy : tuple of str
|
|
||||||
Name of this fragment.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
dict of Fragment to tuple of str
|
|
||||||
A mapping from this fragment and its subfragments to their full hierarchical names.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if _names is None:
|
|
||||||
_names = dict()
|
|
||||||
_names[self] = hierarchy
|
|
||||||
|
|
||||||
signal_names = set(self._assign_names_to_signals().values())
|
|
||||||
for subfragment_index, (subfragment, subfragment_name, subfragment_src_loc) in enumerate(self.subfragments):
|
|
||||||
if subfragment_name is None:
|
|
||||||
subfragment_name = f"U${subfragment_index}"
|
|
||||||
elif subfragment_name in signal_names:
|
|
||||||
subfragment_name = f"{subfragment_name}$U${subfragment_index}"
|
|
||||||
assert subfragment_name not in signal_names
|
|
||||||
subfragment._assign_names_to_fragments(hierarchy=(*hierarchy, subfragment_name),
|
|
||||||
_names=_names)
|
|
||||||
|
|
||||||
return _names
|
|
||||||
|
|
||||||
|
|
||||||
class Instance(Fragment):
|
class Instance(Fragment):
|
||||||
|
@ -682,9 +460,118 @@ class Instance(Fragment):
|
||||||
.format(kw, arg))
|
.format(kw, arg))
|
||||||
|
|
||||||
|
|
||||||
|
class Design:
|
||||||
|
"""Represents a design ready for simulation or netlist building.
|
||||||
|
|
||||||
|
Returned by ``Fragment.prepare``."""
|
||||||
|
|
||||||
|
def __init__(self, fragment, ports, *, hierarchy):
|
||||||
|
self.fragment = fragment
|
||||||
|
self.ports = ports
|
||||||
|
self.hierarchy = hierarchy
|
||||||
|
# dict of Fragment to SignalDict of Signal to name
|
||||||
|
self.signal_names = {}
|
||||||
|
self.fragment_names = {}
|
||||||
|
self._assign_names_to_signals(fragment, ports)
|
||||||
|
self._assign_names_to_fragments(fragment, hierarchy)
|
||||||
|
# Use just-assigned signal names to name all unnamed ports.
|
||||||
|
top_names = self.signal_names[fragment]
|
||||||
|
self.ports = [
|
||||||
|
(name or top_names[signal], signal, dir)
|
||||||
|
for (name, signal, dir) in self.ports
|
||||||
|
]
|
||||||
|
|
||||||
|
def _assign_names_to_signals(self, fragment, ports=None):
|
||||||
|
"""Assign names to signals used in a given fragment.
|
||||||
|
|
||||||
|
The mapping is set in ``self.signal_names``. Because names are deduplicated using local
|
||||||
|
information only, the same signal used in a different fragment may get a different name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
signal_names = _ast.SignalDict()
|
||||||
|
assigned_names = set()
|
||||||
|
|
||||||
|
def add_signal_name(signal):
|
||||||
|
if signal not in signal_names:
|
||||||
|
if signal.name not in assigned_names:
|
||||||
|
name = signal.name
|
||||||
|
else:
|
||||||
|
name = f"{signal.name}${len(assigned_names)}"
|
||||||
|
assert name not in assigned_names
|
||||||
|
signal_names[signal] = name
|
||||||
|
assigned_names.add(name)
|
||||||
|
|
||||||
|
if ports is not None:
|
||||||
|
# First pass: reserve names for pre-named top-level ports. If equal to the signal name, let the signal share it.
|
||||||
|
for name, signal, _dir in ports:
|
||||||
|
if name is not None:
|
||||||
|
assigned_names.add(name)
|
||||||
|
if signal.name == name:
|
||||||
|
signal_names[signal] = name
|
||||||
|
|
||||||
|
# Second pass: ensure non-pre-named top-level ports are named first.
|
||||||
|
for name, signal, _dir in ports:
|
||||||
|
if name is None:
|
||||||
|
add_signal_name(signal)
|
||||||
|
|
||||||
|
for domain_name, domain_signals in fragment.drivers.items():
|
||||||
|
if domain_name != "comb":
|
||||||
|
domain = fragment.domains[domain_name]
|
||||||
|
add_signal_name(domain.clk)
|
||||||
|
if domain.rst is not None:
|
||||||
|
add_signal_name(domain.rst)
|
||||||
|
|
||||||
|
for statements in fragment.statements.values():
|
||||||
|
for statement in statements:
|
||||||
|
for signal in statement._lhs_signals() | statement._rhs_signals():
|
||||||
|
if not isinstance(signal, (_ast.ClockSignal, _ast.ResetSignal)):
|
||||||
|
add_signal_name(signal)
|
||||||
|
|
||||||
|
self.signal_names[fragment] = signal_names
|
||||||
|
for subfragment, _name, _src_loc in fragment.subfragments:
|
||||||
|
self._assign_names_to_signals(subfragment)
|
||||||
|
|
||||||
|
def _assign_names_to_fragments(self, fragment, hierarchy):
|
||||||
|
"""Assign names to this fragment and its subfragments.
|
||||||
|
|
||||||
|
Subfragments may not necessarily have a name. This method assigns every such subfragment
|
||||||
|
a name, ``U$<number>``, where ``<number>`` is based on its location in the hierarchy.
|
||||||
|
|
||||||
|
Subfragment names may collide with signal names safely in Amaranth, but this may confuse
|
||||||
|
backends. This method assigns every such subfragment a name, ``<name>$U$<number>``, where
|
||||||
|
``name`` is its original name, and ``<number>`` is based on its location in the hierarchy.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
hierarchy : tuple of str
|
||||||
|
Name of this fragment.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict of Fragment to tuple of str
|
||||||
|
A mapping from this fragment and its subfragments to their full hierarchical names.
|
||||||
|
"""
|
||||||
|
self.fragment_names[fragment] = hierarchy
|
||||||
|
|
||||||
|
signal_names = set(self.signal_names[fragment].values())
|
||||||
|
for subfragment_index, (subfragment, subfragment_name, subfragment_src_loc) in enumerate(fragment.subfragments):
|
||||||
|
if subfragment_name is None:
|
||||||
|
subfragment_name = f"U${subfragment_index}"
|
||||||
|
elif subfragment_name in signal_names:
|
||||||
|
subfragment_name = f"{subfragment_name}$U${subfragment_index}"
|
||||||
|
assert subfragment_name not in signal_names
|
||||||
|
self._assign_names_to_fragments(subfragment, hierarchy=(*hierarchy, subfragment_name))
|
||||||
|
|
||||||
|
|
||||||
############################################################################################### >:3
|
############################################################################################### >:3
|
||||||
|
|
||||||
|
|
||||||
|
class PortDirection(enum.Enum):
|
||||||
|
Input = "input"
|
||||||
|
Output = "output"
|
||||||
|
Inout = "inout"
|
||||||
|
|
||||||
|
|
||||||
class NetlistDriver:
|
class NetlistDriver:
|
||||||
def __init__(self, module_idx: int, signal: _ast.Signal,
|
def __init__(self, module_idx: int, signal: _ast.Signal,
|
||||||
domain: '_cd.ClockDomain | None', *, src_loc):
|
domain: '_cd.ClockDomain | None', *, src_loc):
|
||||||
|
@ -710,9 +597,9 @@ class NetlistDriver:
|
||||||
|
|
||||||
|
|
||||||
class NetlistEmitter:
|
class NetlistEmitter:
|
||||||
def __init__(self, netlist: _nir.Netlist, fragment_names: 'dict[_ir.Fragment, str]'):
|
def __init__(self, netlist: _nir.Netlist, design):
|
||||||
self.netlist = netlist
|
self.netlist = netlist
|
||||||
self.fragment_names = fragment_names
|
self.design = design
|
||||||
self.drivers = _ast.SignalDict()
|
self.drivers = _ast.SignalDict()
|
||||||
self.rhs_cache: dict[int, Tuple[_nir.Value, bool, _ast.Value]] = {}
|
self.rhs_cache: dict[int, Tuple[_nir.Value, bool, _ast.Value]] = {}
|
||||||
|
|
||||||
|
@ -1155,30 +1042,54 @@ class NetlistEmitter:
|
||||||
self.connect(port_conn, _nir.Value(output_nets[start_bit:start_bit + len(port_conn)]),
|
self.connect(port_conn, _nir.Value(output_nets[start_bit:start_bit + len(port_conn)]),
|
||||||
src_loc=instance.src_loc)
|
src_loc=instance.src_loc)
|
||||||
|
|
||||||
def emit_top_ports(self, fragment: _ir.Fragment, signal_names: _ast.SignalDict):
|
def emit_top_ports(self, fragment: _ir.Fragment):
|
||||||
|
inouts = set()
|
||||||
|
for cell in self.netlist.cells:
|
||||||
|
if isinstance(cell, _nir.IOBuffer):
|
||||||
|
inouts.update(cell.pad)
|
||||||
|
if isinstance(cell, _nir.Instance):
|
||||||
|
for value in cell.ports_io.values():
|
||||||
|
inouts.update(value)
|
||||||
|
|
||||||
next_input_bit = 2 # 0 and 1 are reserved for constants
|
next_input_bit = 2 # 0 and 1 are reserved for constants
|
||||||
top = self.netlist.top
|
top = self.netlist.top
|
||||||
for signal, dir in fragment.ports.items():
|
|
||||||
assert signal not in self.netlist.signals
|
for name, signal, dir in self.design.ports:
|
||||||
name = signal_names[signal]
|
signal_value = self.emit_signal(signal)
|
||||||
if dir == 'i':
|
if dir is None:
|
||||||
|
is_driven = False
|
||||||
|
is_inout = False
|
||||||
|
for net in signal_value:
|
||||||
|
if net in self.netlist.connections:
|
||||||
|
is_driven = True
|
||||||
|
if net in inouts:
|
||||||
|
is_inout = True
|
||||||
|
if is_driven:
|
||||||
|
dir = PortDirection.Output
|
||||||
|
elif is_inout:
|
||||||
|
dir = PortDirection.Inout
|
||||||
|
else:
|
||||||
|
dir = PortDirection.Input
|
||||||
|
if dir == PortDirection.Input:
|
||||||
top.ports_i[name] = (next_input_bit, signal.width)
|
top.ports_i[name] = (next_input_bit, signal.width)
|
||||||
nets = _nir.Value(
|
value = _nir.Value(
|
||||||
_nir.Net.from_cell(0, bit)
|
_nir.Net.from_cell(0, bit)
|
||||||
for bit in range(next_input_bit, next_input_bit + signal.width)
|
for bit in range(next_input_bit, next_input_bit + signal.width)
|
||||||
)
|
)
|
||||||
next_input_bit += signal.width
|
next_input_bit += signal.width
|
||||||
self.netlist.signals[signal] = nets
|
self.connect(signal_value, value, src_loc=signal.src_loc)
|
||||||
elif dir == 'o':
|
elif dir == PortDirection.Output:
|
||||||
top.ports_o[name] = self.emit_signal(signal)
|
top.ports_o[name] = signal_value
|
||||||
elif dir == 'io':
|
elif dir == PortDirection.Inout:
|
||||||
top.ports_io[name] = (next_input_bit, signal.width)
|
top.ports_io[name] = (next_input_bit, signal.width)
|
||||||
nets = _nir.Value(
|
value = _nir.Value(
|
||||||
_nir.Net.from_cell(0, bit)
|
_nir.Net.from_cell(0, bit)
|
||||||
for bit in range(next_input_bit, next_input_bit + signal.width)
|
for bit in range(next_input_bit, next_input_bit + signal.width)
|
||||||
)
|
)
|
||||||
next_input_bit += signal.width
|
next_input_bit += signal.width
|
||||||
self.netlist.signals[signal] = nets
|
self.connect(signal_value, value, src_loc=signal.src_loc)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid port direction {dir!r}")
|
||||||
|
|
||||||
def emit_drivers(self):
|
def emit_drivers(self):
|
||||||
for driver in self.drivers.values():
|
for driver in self.drivers.values():
|
||||||
|
@ -1205,6 +1116,7 @@ class NetlistEmitter:
|
||||||
src_loc = driver.signal.src_loc
|
src_loc = driver.signal.src_loc
|
||||||
self.connect(self.emit_signal(driver.signal), value, src_loc=src_loc)
|
self.connect(self.emit_signal(driver.signal), value, src_loc=src_loc)
|
||||||
|
|
||||||
|
def emit_undriven(self):
|
||||||
# Connect all undriven signal bits to their initial values. This can only happen for entirely
|
# Connect all undriven signal bits to their initial values. This can only happen for entirely
|
||||||
# undriven signals, or signals that are partially driven by instances.
|
# undriven signals, or signals that are partially driven by instances.
|
||||||
for signal, value in self.netlist.signals.items():
|
for signal, value in self.netlist.signals.items():
|
||||||
|
@ -1215,7 +1127,7 @@ class NetlistEmitter:
|
||||||
def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None', *, cell_src_loc=None):
|
def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None', *, cell_src_loc=None):
|
||||||
from . import _mem
|
from . import _mem
|
||||||
|
|
||||||
fragment_name = self.fragment_names[fragment]
|
fragment_name = self.design.fragment_names[fragment]
|
||||||
if isinstance(fragment, _ir.Instance):
|
if isinstance(fragment, _ir.Instance):
|
||||||
assert parent_module_idx is not None
|
assert parent_module_idx is not None
|
||||||
if fragment.type == "$tribuf":
|
if fragment.type == "$tribuf":
|
||||||
|
@ -1232,10 +1144,8 @@ class NetlistEmitter:
|
||||||
self.emit_read_port(parent_module_idx, fragment, port, memory, write_ports)
|
self.emit_read_port(parent_module_idx, fragment, port, memory, write_ports)
|
||||||
elif type(fragment) is _ir.Fragment:
|
elif type(fragment) is _ir.Fragment:
|
||||||
module_idx = self.netlist.add_module(parent_module_idx, fragment_name, src_loc=fragment.src_loc, cell_src_loc=cell_src_loc)
|
module_idx = self.netlist.add_module(parent_module_idx, fragment_name, src_loc=fragment.src_loc, cell_src_loc=cell_src_loc)
|
||||||
signal_names = fragment._assign_names_to_signals()
|
signal_names = self.design.signal_names[fragment]
|
||||||
self.netlist.modules[module_idx].signal_names = signal_names
|
self.netlist.modules[module_idx].signal_names = signal_names
|
||||||
if parent_module_idx is None:
|
|
||||||
self.emit_top_ports(fragment, signal_names)
|
|
||||||
for signal in signal_names:
|
for signal in signal_names:
|
||||||
self.emit_signal(signal)
|
self.emit_signal(signal)
|
||||||
for domain, stmts in fragment.statements.items():
|
for domain, stmts in fragment.statements.items():
|
||||||
|
@ -1245,13 +1155,14 @@ class NetlistEmitter:
|
||||||
self.emit_fragment(subfragment, module_idx, cell_src_loc=sub_src_loc)
|
self.emit_fragment(subfragment, module_idx, cell_src_loc=sub_src_loc)
|
||||||
if parent_module_idx is None:
|
if parent_module_idx is None:
|
||||||
self.emit_drivers()
|
self.emit_drivers()
|
||||||
|
self.emit_top_ports(fragment)
|
||||||
|
self.emit_undriven()
|
||||||
else:
|
else:
|
||||||
assert False # :nocov:
|
assert False # :nocov:
|
||||||
|
|
||||||
|
|
||||||
def _emit_netlist(netlist: _nir.Netlist, fragment, hierarchy):
|
def _emit_netlist(netlist: _nir.Netlist, design):
|
||||||
fragment_names = fragment._assign_names_to_fragments(hierarchy)
|
NetlistEmitter(netlist, design).emit_fragment(design.fragment, None)
|
||||||
NetlistEmitter(netlist, fragment_names).emit_fragment(fragment, None)
|
|
||||||
|
|
||||||
|
|
||||||
def _compute_net_flows(netlist: _nir.Netlist):
|
def _compute_net_flows(netlist: _nir.Netlist):
|
||||||
|
@ -1260,39 +1171,39 @@ def _compute_net_flows(netlist: _nir.Netlist):
|
||||||
# The rules for net flows are as follows:
|
# The rules for net flows are as follows:
|
||||||
#
|
#
|
||||||
# - the modules that have a given net in their net_flow form a subtree of the hierarchy
|
# - the modules that have a given net in their net_flow form a subtree of the hierarchy
|
||||||
# - INTERNAL is used in the root of the subtree and nowhere else
|
# - Internal is used in the root of the subtree and nowhere else
|
||||||
# - OUTPUT is used for modules that contain the definition of the net, or are on the
|
# - Output is used for modules that contain the definition of the net, or are on the
|
||||||
# path from the definition to the root
|
# path from the definition to the root
|
||||||
# - remaining modules have a flow of INPUT (unless the net is a top-level inout port,
|
# - remaining modules have a flow of Input (unless the net is a top-level inout port,
|
||||||
# in which case it is INOUT)
|
# in which case it is Inout)
|
||||||
#
|
#
|
||||||
# In other words, the tree looks something like this:
|
# In other words, the tree looks something like this:
|
||||||
#
|
#
|
||||||
# - [no flow] <<< top
|
# - [no flow] <<< top
|
||||||
# - [no flow]
|
# - [no flow]
|
||||||
# - INTERNAL
|
# - Internal
|
||||||
# - INPUT << use
|
# - Input << use
|
||||||
# - [no flow]
|
# - [no flow]
|
||||||
# - INPUT
|
# - Input
|
||||||
# - INPUT << use
|
# - Input << use
|
||||||
# - OUTPUT
|
# - Output
|
||||||
# - INPUT << use
|
# - Input << use
|
||||||
# - [no flow]
|
# - [no flow]
|
||||||
# - OUTPUT << def
|
# - Output << def
|
||||||
# - INPUT
|
# - Input
|
||||||
# - INPUT
|
# - Input
|
||||||
# - [no flow]
|
# - [no flow]
|
||||||
# - [no flow]
|
# - [no flow]
|
||||||
# - [no flow]
|
# - [no flow]
|
||||||
#
|
#
|
||||||
# This function doesn't assign the INOUT flow — that is corrected later, in compute_ports.
|
# This function doesn't assign the Inout flow — that is corrected later, in compute_ports.
|
||||||
lca = {}
|
lca = {}
|
||||||
|
|
||||||
# Initialize by marking the definition point of every net.
|
# Initialize by marking the definition point of every net.
|
||||||
for cell_idx, cell in enumerate(netlist.cells):
|
for cell_idx, cell in enumerate(netlist.cells):
|
||||||
for net in cell.output_nets(cell_idx):
|
for net in cell.output_nets(cell_idx):
|
||||||
lca[net] = cell.module_idx
|
lca[net] = cell.module_idx
|
||||||
netlist.modules[cell.module_idx].net_flow[net] = _nir.ModuleNetFlow.INTERNAL
|
netlist.modules[cell.module_idx].net_flow[net] = _nir.ModuleNetFlow.Internal
|
||||||
|
|
||||||
# Marks a use of a net within a given module, and adjusts its netflows in all modules
|
# Marks a use of a net within a given module, and adjusts its netflows in all modules
|
||||||
# as required.
|
# as required.
|
||||||
|
@ -1309,7 +1220,7 @@ def _compute_net_flows(netlist: _nir.Netlist):
|
||||||
def_module = lca[net]
|
def_module = lca[net]
|
||||||
# While def_module deeper than use_module, go up with def_module.
|
# While def_module deeper than use_module, go up with def_module.
|
||||||
while len(modules[def_module].name) > len(modules[use_module].name):
|
while len(modules[def_module].name) > len(modules[use_module].name):
|
||||||
modules[def_module].net_flow[net] = _nir.ModuleNetFlow.OUTPUT
|
modules[def_module].net_flow[net] = _nir.ModuleNetFlow.Output
|
||||||
def_module = modules[def_module].parent
|
def_module = modules[def_module].parent
|
||||||
# While use_module deeper than def_module, go up with use_module.
|
# While use_module deeper than def_module, go up with use_module.
|
||||||
# If use_module is below def_module in the hierarchy, we may hit
|
# If use_module is below def_module in the hierarchy, we may hit
|
||||||
|
@ -1318,19 +1229,19 @@ def _compute_net_flows(netlist: _nir.Netlist):
|
||||||
while len(modules[def_module].name) < len(modules[use_module].name):
|
while len(modules[def_module].name) < len(modules[use_module].name):
|
||||||
if net in modules[use_module].net_flow:
|
if net in modules[use_module].net_flow:
|
||||||
return
|
return
|
||||||
modules[use_module].net_flow[net] = _nir.ModuleNetFlow.INPUT
|
modules[use_module].net_flow[net] = _nir.ModuleNetFlow.Input
|
||||||
use_module = modules[use_module].parent
|
use_module = modules[use_module].parent
|
||||||
# Now both pointers should be at the same depth within the hierarchy.
|
# Now both pointers should be at the same depth within the hierarchy.
|
||||||
assert len(modules[def_module].name) == len(modules[use_module].name)
|
assert len(modules[def_module].name) == len(modules[use_module].name)
|
||||||
# Move both pointers up until they meet.
|
# Move both pointers up until they meet.
|
||||||
while def_module != use_module:
|
while def_module != use_module:
|
||||||
modules[def_module].net_flow[net] = _nir.ModuleNetFlow.OUTPUT
|
modules[def_module].net_flow[net] = _nir.ModuleNetFlow.Output
|
||||||
def_module = modules[def_module].parent
|
def_module = modules[def_module].parent
|
||||||
modules[use_module].net_flow[net] = _nir.ModuleNetFlow.INPUT
|
modules[use_module].net_flow[net] = _nir.ModuleNetFlow.Input
|
||||||
use_module = modules[use_module].parent
|
use_module = modules[use_module].parent
|
||||||
assert len(modules[def_module].name) == len(modules[use_module].name)
|
assert len(modules[def_module].name) == len(modules[use_module].name)
|
||||||
# And mark the new LCA.
|
# And mark the new LCA.
|
||||||
modules[def_module].net_flow[net] = _nir.ModuleNetFlow.INTERNAL
|
modules[def_module].net_flow[net] = _nir.ModuleNetFlow.Internal
|
||||||
lca[net] = def_module
|
lca[net] = def_module
|
||||||
|
|
||||||
# Now mark all uses and flesh out the structure.
|
# Now mark all uses and flesh out the structure.
|
||||||
|
@ -1373,14 +1284,17 @@ def _compute_ports(netlist: _nir.Netlist):
|
||||||
if value not in name_table and not name.startswith('$'):
|
if value not in name_table and not name.startswith('$'):
|
||||||
name_table[value] = name
|
name_table[value] = name
|
||||||
|
|
||||||
|
# Adjust any input flows to inout as necessary.
|
||||||
|
for (net, flow) in module.net_flow.items():
|
||||||
|
if flow == _nir.ModuleNetFlow.Input and net in inouts:
|
||||||
|
module.net_flow[net] = _nir.ModuleNetFlow.Inout
|
||||||
|
|
||||||
# Gather together "adjacent" nets with the same flow into ports.
|
# Gather together "adjacent" nets with the same flow into ports.
|
||||||
visited = set()
|
visited = set()
|
||||||
for net in sorted(module.net_flow):
|
for net in sorted(module.net_flow):
|
||||||
flow = module.net_flow[net]
|
flow = module.net_flow[net]
|
||||||
if flow == _nir.ModuleNetFlow.INTERNAL:
|
if flow == _nir.ModuleNetFlow.Internal:
|
||||||
continue
|
continue
|
||||||
if flow == _nir.ModuleNetFlow.INPUT and net in inouts:
|
|
||||||
flow = module.net_flow[net] = _nir.ModuleNetFlow.INOUT
|
|
||||||
if net in visited:
|
if net in visited:
|
||||||
continue
|
continue
|
||||||
# We found a net that needs a port. Keep joining the next nets output by the same
|
# We found a net that needs a port. Keep joining the next nets output by the same
|
||||||
|
@ -1412,23 +1326,21 @@ def _compute_ports(netlist: _nir.Netlist):
|
||||||
for name, (start, width) in netlist.top.ports_i.items():
|
for name, (start, width) in netlist.top.ports_i.items():
|
||||||
top_module.ports[name] = (
|
top_module.ports[name] = (
|
||||||
_nir.Value(_nir.Net.from_cell(0, start + bit) for bit in range(width)),
|
_nir.Value(_nir.Net.from_cell(0, start + bit) for bit in range(width)),
|
||||||
_nir.ModuleNetFlow.INPUT
|
_nir.ModuleNetFlow.Input
|
||||||
)
|
)
|
||||||
for name, (start, width) in netlist.top.ports_io.items():
|
for name, (start, width) in netlist.top.ports_io.items():
|
||||||
top_module.ports[name] = (
|
top_module.ports[name] = (
|
||||||
_nir.Value(_nir.Net.from_cell(0, start + bit) for bit in range(width)),
|
_nir.Value(_nir.Net.from_cell(0, start + bit) for bit in range(width)),
|
||||||
_nir.ModuleNetFlow.INOUT
|
_nir.ModuleNetFlow.Inout
|
||||||
)
|
)
|
||||||
for name, value in netlist.top.ports_o.items():
|
for name, value in netlist.top.ports_o.items():
|
||||||
top_module.ports[name] = (value, _nir.ModuleNetFlow.OUTPUT)
|
top_module.ports[name] = (value, _nir.ModuleNetFlow.Output)
|
||||||
|
|
||||||
|
|
||||||
def build_netlist(fragment, *, name="top"):
|
def build_netlist(fragment, ports, *, name="top", **kwargs):
|
||||||
from ._xfrm import AssignmentLegalizer
|
design = fragment.prepare(ports=ports, hierarchy=(name,), legalize_assignments=True, **kwargs)
|
||||||
|
|
||||||
fragment = AssignmentLegalizer()(fragment)
|
|
||||||
netlist = _nir.Netlist()
|
netlist = _nir.Netlist()
|
||||||
_emit_netlist(netlist, fragment, hierarchy=(name,))
|
_emit_netlist(netlist, design)
|
||||||
netlist.resolve_all_nets()
|
netlist.resolve_all_nets()
|
||||||
_compute_net_flows(netlist)
|
_compute_net_flows(netlist)
|
||||||
_compute_ports(netlist)
|
_compute_ports(netlist)
|
||||||
|
|
|
@ -199,7 +199,7 @@ class Netlist:
|
||||||
self.signals[sig] = self.resolve_value(self.signals[sig])
|
self.signals[sig] = self.resolve_value(self.signals[sig])
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
result = []
|
result = ["("]
|
||||||
for module_idx, module in enumerate(self.modules):
|
for module_idx, module in enumerate(self.modules):
|
||||||
name = " ".join(repr(name) for name in module.name)
|
name = " ".join(repr(name) for name in module.name)
|
||||||
ports = " ".join(
|
ports = " ".join(
|
||||||
|
@ -209,6 +209,7 @@ class Netlist:
|
||||||
result.append(f"(module {module_idx} {module.parent} ({name}) {ports})")
|
result.append(f"(module {module_idx} {module.parent} ({name}) {ports})")
|
||||||
for cell_idx, cell in enumerate(self.cells):
|
for cell_idx, cell in enumerate(self.cells):
|
||||||
result.append(f"(cell {cell_idx} {cell.module_idx} {cell!r})")
|
result.append(f"(cell {cell_idx} {cell.module_idx} {cell!r})")
|
||||||
|
result.append(")")
|
||||||
return "\n".join(result)
|
return "\n".join(result)
|
||||||
|
|
||||||
def add_module(self, parent, name: str, *, src_loc=None, cell_src_loc=None):
|
def add_module(self, parent, name: str, *, src_loc=None, cell_src_loc=None):
|
||||||
|
@ -251,21 +252,21 @@ class ModuleNetFlow(enum.Enum):
|
||||||
#: The net is present in the module (used in the module or needs
|
#: The net is present in the module (used in the module or needs
|
||||||
#: to be routed through it between its submodules), but is not
|
#: to be routed through it between its submodules), but is not
|
||||||
#: present outside its subtree and thus is not a port of this module.
|
#: present outside its subtree and thus is not a port of this module.
|
||||||
INTERNAL = "internal"
|
Internal = "internal"
|
||||||
|
|
||||||
#: The net is present in the module, and is not driven from
|
#: The net is present in the module, and is not driven from
|
||||||
#: the module or any of its submodules. It is thus an input
|
#: the module or any of its submodules. It is thus an input
|
||||||
#: port of this module.
|
#: port of this module.
|
||||||
INPUT = "input"
|
Input = "input"
|
||||||
|
|
||||||
#: The net is present in the module, is driven from the module or
|
#: The net is present in the module, is driven from the module or
|
||||||
#: one of its submodules, and is also used outside of its subtree.
|
#: one of its submodules, and is also used outside of its subtree.
|
||||||
#: It is thus an output port of this module.
|
#: It is thus an output port of this module.
|
||||||
OUTPUT = "output"
|
Output = "output"
|
||||||
|
|
||||||
#: The net is a special top-level inout net that is used within
|
#: The net is a special top-level inout net that is used within
|
||||||
#: this module or its submodules. It is an inout port of this module.
|
#: this module or its submodules. It is an inout port of this module.
|
||||||
INOUT = "inout"
|
Inout = "inout"
|
||||||
|
|
||||||
|
|
||||||
class Module:
|
class Module:
|
||||||
|
@ -1039,7 +1040,7 @@ class Instance(Cell):
|
||||||
items.append(f"(attr {name!r} {val!r})")
|
items.append(f"(attr {name!r} {val!r})")
|
||||||
for name, val in self.ports_i.items():
|
for name, val in self.ports_i.items():
|
||||||
items.append(f"(input {name!r} {val})")
|
items.append(f"(input {name!r} {val})")
|
||||||
for name, (start, width) in self.ports_i.items():
|
for name, (start, width) in self.ports_o.items():
|
||||||
items.append(f"(output {name!r} {start}:{start+width})")
|
items.append(f"(output {name!r} {start}:{start+width})")
|
||||||
for name, val in self.ports_io.items():
|
for name, val in self.ports_io.items():
|
||||||
items.append(f"(inout {name!r} {val})")
|
items.append(f"(inout {name!r} {val})")
|
||||||
|
|
|
@ -209,10 +209,6 @@ class FragmentTransformer:
|
||||||
for subfragment, name, src_loc in fragment.subfragments:
|
for subfragment, name, src_loc in fragment.subfragments:
|
||||||
new_fragment.add_subfragment(self(subfragment), name, src_loc=src_loc)
|
new_fragment.add_subfragment(self(subfragment), name, src_loc=src_loc)
|
||||||
|
|
||||||
def map_ports(self, fragment, new_fragment):
|
|
||||||
for port, dir in fragment.ports.items():
|
|
||||||
new_fragment.add_ports(port, dir=dir)
|
|
||||||
|
|
||||||
def map_named_ports(self, fragment, new_fragment):
|
def map_named_ports(self, fragment, new_fragment):
|
||||||
if hasattr(self, "on_value"):
|
if hasattr(self, "on_value"):
|
||||||
for name, (value, dir) in fragment.named_ports.items():
|
for name, (value, dir) in fragment.named_ports.items():
|
||||||
|
@ -285,7 +281,6 @@ class FragmentTransformer:
|
||||||
new_fragment = Fragment(src_loc=fragment.src_loc)
|
new_fragment = Fragment(src_loc=fragment.src_loc)
|
||||||
new_fragment.flatten = fragment.flatten
|
new_fragment.flatten = fragment.flatten
|
||||||
new_fragment.attrs = OrderedDict(fragment.attrs)
|
new_fragment.attrs = OrderedDict(fragment.attrs)
|
||||||
self.map_ports(fragment, new_fragment)
|
|
||||||
self.map_subfragments(fragment, new_fragment)
|
self.map_subfragments(fragment, new_fragment)
|
||||||
self.map_domains(fragment, new_fragment)
|
self.map_domains(fragment, new_fragment)
|
||||||
self.map_statements(fragment, new_fragment)
|
self.map_statements(fragment, new_fragment)
|
||||||
|
|
|
@ -70,8 +70,8 @@ class Simulator:
|
||||||
"a simulation engine name"
|
"a simulation engine name"
|
||||||
.format(engine))
|
.format(engine))
|
||||||
|
|
||||||
self._fragment = Fragment.get(fragment, platform=None).prepare()
|
self._design = Fragment.get(fragment, platform=None).prepare()
|
||||||
self._engine = engine(self._fragment)
|
self._engine = engine(self._design)
|
||||||
self._clocked = set()
|
self._clocked = set()
|
||||||
|
|
||||||
def _check_process(self, process):
|
def _check_process(self, process):
|
||||||
|
@ -165,15 +165,15 @@ class Simulator:
|
||||||
in this case.
|
in this case.
|
||||||
"""
|
"""
|
||||||
if isinstance(domain, ClockDomain):
|
if isinstance(domain, ClockDomain):
|
||||||
if (domain.name in self._fragment.domains and
|
if (domain.name in self._design.fragment.domains and
|
||||||
domain is not self._fragment.domains[domain.name]):
|
domain is not self._design.fragment.domains[domain.name]):
|
||||||
warnings.warn("Adding a clock process that drives a clock domain object "
|
warnings.warn("Adding a clock process that drives a clock domain object "
|
||||||
"named {!r}, which is distinct from an identically named domain "
|
"named {!r}, which is distinct from an identically named domain "
|
||||||
"in the simulated design"
|
"in the simulated design"
|
||||||
.format(domain.name),
|
.format(domain.name),
|
||||||
UserWarning, stacklevel=2)
|
UserWarning, stacklevel=2)
|
||||||
elif domain in self._fragment.domains:
|
elif domain in self._design.fragment.domains:
|
||||||
domain = self._fragment.domains[domain]
|
domain = self._design.fragment.domains[domain]
|
||||||
elif if_exists:
|
elif if_exists:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -36,7 +36,7 @@ class _VCDWriter:
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __init__(self, fragment, *, vcd_file, gtkw_file=None, traces=()):
|
def __init__(self, design, *, vcd_file, gtkw_file=None, traces=()):
|
||||||
# Although pyvcd is a mandatory dependency, be resilient and import it as needed, so that
|
# Although pyvcd is a mandatory dependency, be resilient and import it as needed, so that
|
||||||
# the simulator is still usable if it's not installed for some reason.
|
# the simulator is still usable if it's not installed for some reason.
|
||||||
import vcd, vcd.gtkw
|
import vcd, vcd.gtkw
|
||||||
|
@ -65,14 +65,14 @@ class _VCDWriter:
|
||||||
|
|
||||||
signal_names = SignalDict()
|
signal_names = SignalDict()
|
||||||
memories = {}
|
memories = {}
|
||||||
for subfragment, subfragment_name in \
|
for fragment, fragment_name in design.fragment_names.items():
|
||||||
fragment._assign_names_to_fragments(hierarchy=("bench", "top",)).items():
|
fragment_name = ("bench", *fragment_name)
|
||||||
for signal, signal_name in subfragment._assign_names_to_signals().items():
|
for signal, signal_name in design.signal_names[fragment].items():
|
||||||
if signal not in signal_names:
|
if signal not in signal_names:
|
||||||
signal_names[signal] = set()
|
signal_names[signal] = set()
|
||||||
signal_names[signal].add((*subfragment_name, signal_name))
|
signal_names[signal].add((*fragment_name, signal_name))
|
||||||
if isinstance(subfragment, MemoryInstance):
|
if isinstance(fragment, MemoryInstance):
|
||||||
memories[subfragment._identity] = (subfragment, subfragment_name)
|
memories[fragment._identity] = (fragment, fragment_name)
|
||||||
|
|
||||||
trace_names = SignalDict()
|
trace_names = SignalDict()
|
||||||
assigned_names = set()
|
assigned_names = set()
|
||||||
|
@ -403,16 +403,16 @@ class _PySimulation(BaseSimulation):
|
||||||
|
|
||||||
|
|
||||||
class PySimEngine(BaseEngine):
|
class PySimEngine(BaseEngine):
|
||||||
def __init__(self, fragment):
|
def __init__(self, design):
|
||||||
self._state = _PySimulation()
|
self._state = _PySimulation()
|
||||||
self._timeline = self._state.timeline
|
self._timeline = self._state.timeline
|
||||||
|
|
||||||
self._fragment = fragment
|
self._design = design
|
||||||
self._processes = _FragmentCompiler(self._state)(self._fragment)
|
self._processes = _FragmentCompiler(self._state)(self._design.fragment)
|
||||||
self._vcd_writers = []
|
self._vcd_writers = []
|
||||||
|
|
||||||
def add_coroutine_process(self, process, *, default_cmd):
|
def add_coroutine_process(self, process, *, default_cmd):
|
||||||
self._processes.add(PyCoroProcess(self._state, self._fragment.domains, process,
|
self._processes.add(PyCoroProcess(self._state, self._design.fragment.domains, process,
|
||||||
default_cmd=default_cmd))
|
default_cmd=default_cmd))
|
||||||
|
|
||||||
def add_clock_process(self, clock, *, phase, period):
|
def add_clock_process(self, clock, *, phase, period):
|
||||||
|
@ -462,7 +462,7 @@ class PySimEngine(BaseEngine):
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def write_vcd(self, *, vcd_file, gtkw_file, traces):
|
def write_vcd(self, *, vcd_file, gtkw_file, traces):
|
||||||
vcd_writer = _VCDWriter(self._fragment,
|
vcd_writer = _VCDWriter(self._design,
|
||||||
vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)
|
vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)
|
||||||
try:
|
try:
|
||||||
self._vcd_writers.append(vcd_writer)
|
self._vcd_writers.append(vcd_writer)
|
||||||
|
|
|
@ -88,275 +88,295 @@ class FragmentPortsTestCase(FHDLTestCase):
|
||||||
|
|
||||||
def test_empty(self):
|
def test_empty(self):
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
self.assertEqual(list(f.iter_ports()), [])
|
nl = build_netlist(f, ports=[])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top'))
|
||||||
|
(cell 0 0 (top ))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
f._propagate_ports(ports=(), all_undef_as_ports=True)
|
def test_loopback(self):
|
||||||
self.assertEqual(f.ports, SignalDict([]))
|
|
||||||
|
|
||||||
def test_iter_signals(self):
|
|
||||||
f = Fragment()
|
|
||||||
f.add_ports(self.s1, self.s2, dir="io")
|
|
||||||
self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals())
|
|
||||||
|
|
||||||
def test_self_contained(self):
|
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_statements(
|
f.add_statements(
|
||||||
"comb",
|
"comb",
|
||||||
self.c1.eq(self.s1),
|
self.c1.eq(self.s1),
|
||||||
self.s1.eq(self.c1)
|
|
||||||
)
|
)
|
||||||
|
nl = build_netlist(f, ports=[self.c1, self.s1])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top') (input 's1' 0.2) (output 'c1' 0.2))
|
||||||
|
(cell 0 0 (top (output 'c1' 0.2) (input 's1' 2:3)))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
f._propagate_ports(ports=(), all_undef_as_ports=True)
|
def test_subfragment_simple(self):
|
||||||
self.assertEqual(f.ports, SignalDict([]))
|
f1 = Fragment()
|
||||||
|
f2 = Fragment()
|
||||||
|
f2.add_statements(
|
||||||
|
"comb",
|
||||||
|
self.c1.eq(~self.s1),
|
||||||
|
)
|
||||||
|
f1.add_subfragment(f2, "f2")
|
||||||
|
nl = build_netlist(f1, ports=[self.c1, self.s1])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top') (input 's1' 0.2) (output 'c1' 1.0))
|
||||||
|
(module 1 0 ('top' 'f2') (input 's1' 0.2) (output 'c1' 1.0))
|
||||||
|
(cell 0 0 (top (output 'c1' 1.0) (input 's1' 2:3)))
|
||||||
|
(cell 1 1 (~ 0.2))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
def test_infer_input(self):
|
def test_tree(self):
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_statements(
|
f1 = Fragment()
|
||||||
|
f.add_subfragment(f1, "f1")
|
||||||
|
f11 = Fragment()
|
||||||
|
f1.add_subfragment(f11, "f11")
|
||||||
|
f111 = Fragment()
|
||||||
|
f11.add_subfragment(f111, "f111")
|
||||||
|
f1111 = Fragment()
|
||||||
|
f111.add_subfragment(f1111, "f1111")
|
||||||
|
f12 = Fragment()
|
||||||
|
f1.add_subfragment(f12, "f12")
|
||||||
|
f13 = Fragment()
|
||||||
|
f1.add_subfragment(f13, "f13")
|
||||||
|
f131 = Fragment()
|
||||||
|
f13.add_subfragment(f131, "f131")
|
||||||
|
f2 = Fragment()
|
||||||
|
f.add_subfragment(f2, "f2")
|
||||||
|
f2.add_statements(
|
||||||
"comb",
|
"comb",
|
||||||
self.c1.eq(self.s1)
|
self.s2.eq(~self.s1),
|
||||||
)
|
)
|
||||||
|
f131.add_statements(
|
||||||
|
"comb",
|
||||||
|
self.s3.eq(~self.s2),
|
||||||
|
Assert(~self.s1),
|
||||||
|
)
|
||||||
|
f12.add_statements(
|
||||||
|
"comb",
|
||||||
|
self.c1.eq(~self.s3),
|
||||||
|
)
|
||||||
|
f1111.add_statements(
|
||||||
|
"comb",
|
||||||
|
self.c2.eq(~self.s3),
|
||||||
|
Assert(self.s1),
|
||||||
|
)
|
||||||
|
f111.add_statements(
|
||||||
|
"comb",
|
||||||
|
self.c3.eq(~self.c2),
|
||||||
|
)
|
||||||
|
nl = build_netlist(f, ports=[self.c1, self.c2, self.c3, self.s1])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top')
|
||||||
|
(input 's1' 0.2)
|
||||||
|
(output 'c1' 5.0)
|
||||||
|
(output 'c2' 2.0)
|
||||||
|
(output 'c3' 1.0))
|
||||||
|
(module 1 0 ('top' 'f1')
|
||||||
|
(input 'port$0$2' 0.2)
|
||||||
|
(output 'port$1$0' 1.0)
|
||||||
|
(output 'port$2$0' 2.0)
|
||||||
|
(output 'port$5$0' 5.0)
|
||||||
|
(input 'port$10$0' 10.0))
|
||||||
|
(module 2 1 ('top' 'f1' 'f11')
|
||||||
|
(input 'port$0$2' 0.2)
|
||||||
|
(output 'port$1$0' 1.0)
|
||||||
|
(output 'port$2$0' 2.0)
|
||||||
|
(input 'port$6$0' 6.0))
|
||||||
|
(module 3 2 ('top' 'f1' 'f11' 'f111')
|
||||||
|
(input 'port$0$2' 0.2)
|
||||||
|
(output 'c3' 1.0)
|
||||||
|
(output 'c2' 2.0)
|
||||||
|
(input 'port$6$0' 6.0))
|
||||||
|
(module 4 3 ('top' 'f1' 'f11' 'f111' 'f1111')
|
||||||
|
(input 's1' 0.2)
|
||||||
|
(output 'c2' 2.0)
|
||||||
|
(input 's3' 6.0))
|
||||||
|
(module 5 1 ('top' 'f1' 'f12')
|
||||||
|
(output 'c1' 5.0)
|
||||||
|
(input 's3' 6.0))
|
||||||
|
(module 6 1 ('top' 'f1' 'f13')
|
||||||
|
(input 'port$0$2' 0.2)
|
||||||
|
(output 'port$6$0' 6.0)
|
||||||
|
(input 'port$10$0' 10.0))
|
||||||
|
(module 7 6 ('top' 'f1' 'f13' 'f131')
|
||||||
|
(input 's1' 0.2)
|
||||||
|
(output 's3' 6.0)
|
||||||
|
(input 's2' 10.0))
|
||||||
|
(module 8 0 ('top' 'f2')
|
||||||
|
(input 's1' 0.2)
|
||||||
|
(output 's2' 10.0))
|
||||||
|
(cell 0 0 (top (output 'c1' 5.0) (output 'c2' 2.0) (output 'c3' 1.0) (input 's1' 2:3)))
|
||||||
|
(cell 1 3 (~ 2.0))
|
||||||
|
(cell 2 4 (~ 6.0))
|
||||||
|
(cell 3 4 (assignment_list 1'd0 (1 0:1 1'd1)))
|
||||||
|
(cell 4 4 (assert None 0.2 3.0))
|
||||||
|
(cell 5 5 (~ 6.0))
|
||||||
|
(cell 6 7 (~ 10.0))
|
||||||
|
(cell 7 7 (~ 0.2))
|
||||||
|
(cell 8 7 (assignment_list 1'd0 (1 0:1 1'd1)))
|
||||||
|
(cell 9 7 (assert None 7.0 8.0))
|
||||||
|
(cell 10 8 (~ 0.2))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
f._propagate_ports(ports=(), all_undef_as_ports=True)
|
def test_port_dict(self):
|
||||||
self.assertEqual(f.ports, SignalDict([
|
|
||||||
(self.s1, "i")
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_request_output(self):
|
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_statements(
|
nl = build_netlist(f, ports={
|
||||||
"comb",
|
"a": (self.s1, PortDirection.Output),
|
||||||
self.c1.eq(self.s1)
|
"b": (self.s2, PortDirection.Input),
|
||||||
|
"c": (self.s3, PortDirection.Inout),
|
||||||
|
})
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top') (input 'b' 0.2) (inout 'c' 0.3) (output 'a' 1'd0))
|
||||||
|
(cell 0 0 (top (output 'a' 1'd0) (input 'b' 2:3) (inout 'c' 3:4)))
|
||||||
)
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
f._propagate_ports(ports=(self.c1,), all_undef_as_ports=True)
|
def test_port_domain(self):
|
||||||
self.assertEqual(f.ports, SignalDict([
|
|
||||||
(self.s1, "i"),
|
|
||||||
(self.c1, "o")
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_input_in_subfragment(self):
|
|
||||||
f1 = Fragment()
|
|
||||||
f1.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c1.eq(self.s1)
|
|
||||||
)
|
|
||||||
f2 = Fragment()
|
|
||||||
f2.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.s1.eq(0)
|
|
||||||
)
|
|
||||||
f1.add_subfragment(f2)
|
|
||||||
f1._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f1.ports, SignalDict())
|
|
||||||
self.assertEqual(f2.ports, SignalDict([
|
|
||||||
(self.s1, "o"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_input_only_in_subfragment(self):
|
|
||||||
f1 = Fragment()
|
|
||||||
f2 = Fragment()
|
|
||||||
f2.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c1.eq(self.s1)
|
|
||||||
)
|
|
||||||
f1.add_subfragment(f2)
|
|
||||||
f1._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f1.ports, SignalDict([
|
|
||||||
(self.s1, "i"),
|
|
||||||
]))
|
|
||||||
self.assertEqual(f2.ports, SignalDict([
|
|
||||||
(self.s1, "i"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_output_from_subfragment(self):
|
|
||||||
f1 = Fragment()
|
|
||||||
f1.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c1.eq(0)
|
|
||||||
)
|
|
||||||
f2 = Fragment()
|
|
||||||
f2.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c2.eq(1)
|
|
||||||
)
|
|
||||||
f1.add_subfragment(f2)
|
|
||||||
|
|
||||||
f1._propagate_ports(ports=(self.c2,), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f1.ports, SignalDict([
|
|
||||||
(self.c2, "o"),
|
|
||||||
]))
|
|
||||||
self.assertEqual(f2.ports, SignalDict([
|
|
||||||
(self.c2, "o"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_output_from_subfragment_2(self):
|
|
||||||
f1 = Fragment()
|
|
||||||
f1.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c1.eq(self.s1)
|
|
||||||
)
|
|
||||||
f2 = Fragment()
|
|
||||||
f2.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c2.eq(self.s1)
|
|
||||||
)
|
|
||||||
f1.add_subfragment(f2)
|
|
||||||
f3 = Fragment()
|
|
||||||
f3.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.s1.eq(0)
|
|
||||||
)
|
|
||||||
f2.add_subfragment(f3)
|
|
||||||
|
|
||||||
f1._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f2.ports, SignalDict([
|
|
||||||
(self.s1, "o"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_input_output_sibling(self):
|
|
||||||
f1 = Fragment()
|
|
||||||
f2 = Fragment()
|
|
||||||
f2.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c1.eq(self.c2)
|
|
||||||
)
|
|
||||||
f1.add_subfragment(f2)
|
|
||||||
f3 = Fragment()
|
|
||||||
f3.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c2.eq(0)
|
|
||||||
)
|
|
||||||
f3.add_driver(self.c2)
|
|
||||||
f1.add_subfragment(f3)
|
|
||||||
|
|
||||||
f1._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f1.ports, SignalDict())
|
|
||||||
|
|
||||||
def test_output_input_sibling(self):
|
|
||||||
f1 = Fragment()
|
|
||||||
f2 = Fragment()
|
|
||||||
f2.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c2.eq(0)
|
|
||||||
)
|
|
||||||
f2.add_driver(self.c2)
|
|
||||||
f1.add_subfragment(f2)
|
|
||||||
f3 = Fragment()
|
|
||||||
f3.add_statements(
|
|
||||||
"comb",
|
|
||||||
self.c1.eq(self.c2)
|
|
||||||
)
|
|
||||||
f1.add_subfragment(f3)
|
|
||||||
|
|
||||||
f1._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f1.ports, SignalDict())
|
|
||||||
|
|
||||||
def test_input_cd(self):
|
|
||||||
sync = ClockDomain()
|
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_statements(
|
cd_sync = ClockDomain()
|
||||||
"sync",
|
ctr = Signal(4)
|
||||||
self.c1.eq(self.s1)
|
f.add_domains(cd_sync)
|
||||||
|
f.add_driver(ctr, "sync")
|
||||||
|
f.add_statements("sync", ctr.eq(ctr + 1))
|
||||||
|
nl = build_netlist(f, ports=[
|
||||||
|
ClockSignal("sync"),
|
||||||
|
ResetSignal("sync"),
|
||||||
|
ctr,
|
||||||
|
])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top') (input 'clk' 0.2) (input 'rst' 0.3) (output 'ctr' 5.0:4))
|
||||||
|
(cell 0 0 (top (output 'ctr' 5.0:4) (input 'clk' 2:3) (input 'rst' 3:4)))
|
||||||
|
(cell 1 0 (+ (cat 5.0:4 1'd0) 5'd1))
|
||||||
|
(cell 2 0 (matches 0.3 1))
|
||||||
|
(cell 3 0 (priority_match 1 2.0))
|
||||||
|
(cell 4 0 (assignment_list 5.0:4 (1 0:4 1.0:4) (3.0 0:4 4'd0)))
|
||||||
|
(cell 5 0 (flipflop 4.0:4 0 pos 0.2 0))
|
||||||
)
|
)
|
||||||
f.add_domains(sync)
|
""")
|
||||||
f.add_driver(self.c1, "sync")
|
|
||||||
|
|
||||||
f._propagate_ports(ports=(), all_undef_as_ports=True)
|
def test_port_autodomain(self):
|
||||||
self.assertEqual(f.ports, SignalDict([
|
|
||||||
(self.s1, "i"),
|
|
||||||
(sync.clk, "i"),
|
|
||||||
(sync.rst, "i"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_input_cd_reset_less(self):
|
|
||||||
sync = ClockDomain(reset_less=True)
|
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_statements(
|
ctr = Signal(4)
|
||||||
"sync",
|
f.add_driver(ctr, "sync")
|
||||||
self.c1.eq(self.s1)
|
f.add_statements("sync", ctr.eq(ctr + 1))
|
||||||
|
nl = build_netlist(f, ports=[ctr])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top') (input 'clk' 0.2) (input 'rst' 0.3) (output 'ctr' 5.0:4))
|
||||||
|
(cell 0 0 (top (output 'ctr' 5.0:4) (input 'clk' 2:3) (input 'rst' 3:4)))
|
||||||
|
(cell 1 0 (+ (cat 5.0:4 1'd0) 5'd1))
|
||||||
|
(cell 2 0 (matches 0.3 1))
|
||||||
|
(cell 3 0 (priority_match 1 2.0))
|
||||||
|
(cell 4 0 (assignment_list 5.0:4 (1 0:4 1.0:4) (3.0 0:4 4'd0)))
|
||||||
|
(cell 5 0 (flipflop 4.0:4 0 pos 0.2 0))
|
||||||
)
|
)
|
||||||
f.add_domains(sync)
|
""")
|
||||||
f.add_driver(self.c1, "sync")
|
|
||||||
|
|
||||||
f._propagate_ports(ports=(), all_undef_as_ports=True)
|
def test_port_partial(self):
|
||||||
self.assertEqual(f.ports, SignalDict([
|
f = Fragment()
|
||||||
(self.s1, "i"),
|
|
||||||
(sync.clk, "i"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_inout(self):
|
|
||||||
s = Signal()
|
|
||||||
f1 = Fragment()
|
f1 = Fragment()
|
||||||
f2 = Instance("foo", io_x=s)
|
f.add_subfragment(f1, "f1")
|
||||||
f1.add_subfragment(f2)
|
a = Signal(4)
|
||||||
|
b = Signal(4)
|
||||||
|
c = Signal(3)
|
||||||
|
f1.add_driver(c)
|
||||||
|
f1.add_statements("comb", c.eq((a * b).shift_right(4)))
|
||||||
|
nl = build_netlist(f, ports=[a, b, c])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top')
|
||||||
|
(input 'a' 0.2:6)
|
||||||
|
(input 'b' 0.6:10)
|
||||||
|
(output 'c' 1.4:7))
|
||||||
|
(module 1 0 ('top' 'f1')
|
||||||
|
(input 'a' 0.2:6)
|
||||||
|
(input 'b' 0.6:10)
|
||||||
|
(output 'c' 1.4:7))
|
||||||
|
(cell 0 0 (top
|
||||||
|
(output 'c' 1.4:7)
|
||||||
|
(input 'a' 2:6)
|
||||||
|
(input 'b' 6:10)))
|
||||||
|
(cell 1 1 (* (cat 0.2:6 4'd0) (cat 0.6:10 4'd0)))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
f1._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f1.ports, SignalDict([
|
|
||||||
(s, "io")
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_in_out_same_signal(self):
|
def test_port_instance(self):
|
||||||
s = Signal()
|
|
||||||
|
|
||||||
f1 = Instance("foo", i_x=s, o_y=s)
|
|
||||||
f2 = Fragment()
|
|
||||||
f2.add_subfragment(f1)
|
|
||||||
|
|
||||||
f2._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f1.ports, SignalDict([
|
|
||||||
(s, "o")
|
|
||||||
]))
|
|
||||||
|
|
||||||
f3 = Instance("foo", o_y=s, i_x=s)
|
|
||||||
f4 = Fragment()
|
|
||||||
f4.add_subfragment(f3)
|
|
||||||
|
|
||||||
f4._propagate_ports(ports=(), all_undef_as_ports=True)
|
|
||||||
self.assertEqual(f3.ports, SignalDict([
|
|
||||||
(s, "o")
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_clk_rst(self):
|
|
||||||
sync = ClockDomain()
|
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_domains(sync)
|
f1 = Fragment()
|
||||||
|
f.add_subfragment(f1, "f1")
|
||||||
f = f.prepare(ports=(ClockSignal("sync"), ResetSignal("sync")))
|
a = Signal(4)
|
||||||
self.assertEqual(f.ports, SignalDict([
|
b = Signal(4)
|
||||||
(sync.clk, "i"),
|
c = Signal(4)
|
||||||
(sync.rst, "i"),
|
d = Signal(4)
|
||||||
]))
|
f1.add_subfragment(Instance("t",
|
||||||
|
p_p = "meow",
|
||||||
|
a_a = True,
|
||||||
|
i_aa=a,
|
||||||
|
io_bb=b,
|
||||||
|
o_cc=c,
|
||||||
|
o_dd=d,
|
||||||
|
), "i")
|
||||||
|
nl = build_netlist(f, ports=[a, b, c, d])
|
||||||
|
self.assertRepr(nl, """
|
||||||
|
(
|
||||||
|
(module 0 None ('top')
|
||||||
|
(input 'a' 0.2:6)
|
||||||
|
(inout 'b' 0.6:10)
|
||||||
|
(output 'c' 1.0:4)
|
||||||
|
(output 'd' 1.4:8))
|
||||||
|
(module 1 0 ('top' 'f1')
|
||||||
|
(input 'port$0$2' 0.2:6)
|
||||||
|
(inout 'port$0$6' 0.6:10)
|
||||||
|
(output 'port$1$0' 1.0:4)
|
||||||
|
(output 'port$1$4' 1.4:8))
|
||||||
|
(cell 0 0 (top
|
||||||
|
(output 'c' 1.0:4)
|
||||||
|
(output 'd' 1.4:8)
|
||||||
|
(input 'a' 2:6)
|
||||||
|
(inout 'b' 6:10)))
|
||||||
|
(cell 1 1 (instance 't' 'i'
|
||||||
|
(param 'p' 'meow')
|
||||||
|
(attr 'a' True)
|
||||||
|
(input 'aa' 0.2:6)
|
||||||
|
(output 'cc' 0:4)
|
||||||
|
(output 'dd' 4:8)
|
||||||
|
(inout 'bb' 0.6:10)))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
def test_port_wrong(self):
|
def test_port_wrong(self):
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
|
a = Signal()
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^Only signals may be added as ports, not \(const 1'd1\)$"):
|
r"^Only signals may be added as ports, not \(const 1'd1\)$"):
|
||||||
f.prepare(ports=(Const(1),))
|
build_netlist(f, ports=(Const(1),))
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Port name must be a string, not 1$"):
|
||||||
|
build_netlist(f, ports={1: (a, PortDirection.Input)})
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Port direction must be a `PortDirection` instance or None, not 'i'$"):
|
||||||
|
build_netlist(f, ports={"a": (a, "i")})
|
||||||
|
|
||||||
def test_port_not_iterable(self):
|
def test_port_not_iterable(self):
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^`ports` must be either a list or a tuple, not 1$"):
|
r"^`ports` must be a dict, a list or a tuple, not 1$"):
|
||||||
f.prepare(ports=1)
|
build_netlist(f, ports=1)
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
(r"^`ports` must be either a list or a tuple, not \(const 1'd1\)"
|
(r"^`ports` must be a dict, a list or a tuple, not \(const 1'd1\)"
|
||||||
r" \(did you mean `ports=\(<signal>,\)`, rather than `ports=<signal>`\?\)$")):
|
r" \(did you mean `ports=\(<signal>,\)`, rather than `ports=<signal>`\?\)$")):
|
||||||
f.prepare(ports=Const(1))
|
build_netlist(f, ports=Const(1))
|
||||||
|
|
||||||
class FragmentDomainsTestCase(FHDLTestCase):
|
class FragmentDomainsTestCase(FHDLTestCase):
|
||||||
def test_iter_signals(self):
|
|
||||||
cd1 = ClockDomain()
|
|
||||||
cd2 = ClockDomain(reset_less=True)
|
|
||||||
s1 = Signal()
|
|
||||||
s2 = Signal()
|
|
||||||
|
|
||||||
f = Fragment()
|
|
||||||
f.add_domains(cd1, cd2)
|
|
||||||
f.add_driver(s1, "cd1")
|
|
||||||
self.assertEqual(SignalSet((cd1.clk, cd1.rst, s1)), f.iter_signals())
|
|
||||||
f.add_driver(s2, "cd2")
|
|
||||||
self.assertEqual(SignalSet((cd1.clk, cd1.rst, cd2.clk, s1, s2)), f.iter_signals())
|
|
||||||
|
|
||||||
def test_propagate_up(self):
|
def test_propagate_up(self):
|
||||||
cd = ClockDomain()
|
cd = ClockDomain()
|
||||||
|
|
||||||
|
@ -810,49 +830,17 @@ class InstanceTestCase(FHDLTestCase):
|
||||||
self.assertEqual(f.type, "cpu")
|
self.assertEqual(f.type, "cpu")
|
||||||
self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)]))
|
self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)]))
|
||||||
self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"])
|
self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"])
|
||||||
self.assertEqual(f.ports, SignalDict([]))
|
|
||||||
|
|
||||||
def test_prepare(self):
|
|
||||||
self.setUp_cpu()
|
|
||||||
f = self.wrap.prepare()
|
|
||||||
sync_clk = f.domains["sync"].clk
|
|
||||||
self.assertEqual(f.ports, SignalDict([
|
|
||||||
(sync_clk, "i"),
|
|
||||||
(self.rst, "i"),
|
|
||||||
(self.pins, "io"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_prepare_explicit_ports(self):
|
|
||||||
self.setUp_cpu()
|
|
||||||
f = self.wrap.prepare(ports=[self.rst, self.stb])
|
|
||||||
sync_clk = f.domains["sync"].clk
|
|
||||||
sync_rst = f.domains["sync"].rst
|
|
||||||
self.assertEqual(f.ports, SignalDict([
|
|
||||||
(sync_clk, "i"),
|
|
||||||
(sync_rst, "i"),
|
|
||||||
(self.rst, "i"),
|
|
||||||
(self.stb, "o"),
|
|
||||||
(self.pins, "io"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_prepare_slice_in_port(self):
|
|
||||||
s = Signal(2)
|
|
||||||
f = Fragment()
|
|
||||||
f.add_subfragment(Instance("foo", o_O=s[0]))
|
|
||||||
f.add_subfragment(Instance("foo", o_O=s[1]))
|
|
||||||
fp = f.prepare(ports=[s], missing_domain=lambda name: None)
|
|
||||||
self.assertEqual(fp.ports, SignalDict([
|
|
||||||
(s, "o"),
|
|
||||||
]))
|
|
||||||
|
|
||||||
def test_prepare_attrs(self):
|
def test_prepare_attrs(self):
|
||||||
self.setUp_cpu()
|
self.setUp_cpu()
|
||||||
self.inst.attrs["ATTR"] = 1
|
self.inst.attrs["ATTR"] = 1
|
||||||
f = self.inst.prepare()
|
design = self.inst.prepare()
|
||||||
self.assertEqual(f.attrs, OrderedDict([
|
self.assertEqual(design.fragment.attrs, OrderedDict([
|
||||||
("ATTR", 1),
|
("ATTR", 1),
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
|
||||||
|
class NamesTestCase(FHDLTestCase):
|
||||||
def test_assign_names_to_signals(self):
|
def test_assign_names_to_signals(self):
|
||||||
i = Signal()
|
i = Signal()
|
||||||
rst = Signal()
|
rst = Signal()
|
||||||
|
@ -864,8 +852,6 @@ class InstanceTestCase(FHDLTestCase):
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_domains(cd_sync := ClockDomain())
|
f.add_domains(cd_sync := ClockDomain())
|
||||||
f.add_domains(cd_sync_norst := ClockDomain(reset_less=True))
|
f.add_domains(cd_sync_norst := ClockDomain(reset_less=True))
|
||||||
f.add_ports((i, rst), dir="i")
|
|
||||||
f.add_ports((o1, o2, o3), dir="o")
|
|
||||||
f.add_statements("comb", [o1.eq(0)])
|
f.add_statements("comb", [o1.eq(0)])
|
||||||
f.add_driver(o1, domain="comb")
|
f.add_driver(o1, domain="comb")
|
||||||
f.add_statements("sync", [o2.eq(i1)])
|
f.add_statements("sync", [o2.eq(i1)])
|
||||||
|
@ -873,8 +859,15 @@ class InstanceTestCase(FHDLTestCase):
|
||||||
f.add_statements("sync_norst", [o3.eq(i1)])
|
f.add_statements("sync_norst", [o3.eq(i1)])
|
||||||
f.add_driver(o3, domain="sync_norst")
|
f.add_driver(o3, domain="sync_norst")
|
||||||
|
|
||||||
names = f._assign_names_to_signals()
|
ports = {
|
||||||
self.assertEqual(names, SignalDict([
|
"i": (i, PortDirection.Input),
|
||||||
|
"rst": (rst, PortDirection.Input),
|
||||||
|
"o1": (o1, PortDirection.Output),
|
||||||
|
"o2": (o2, PortDirection.Output),
|
||||||
|
"o3": (o3, PortDirection.Output),
|
||||||
|
}
|
||||||
|
design = f.prepare(ports)
|
||||||
|
self.assertEqual(design.signal_names[design.fragment], SignalDict([
|
||||||
(i, "i"),
|
(i, "i"),
|
||||||
(rst, "rst"),
|
(rst, "rst"),
|
||||||
(o1, "o1"),
|
(o1, "o1"),
|
||||||
|
@ -891,8 +884,8 @@ class InstanceTestCase(FHDLTestCase):
|
||||||
f.add_subfragment(a := Fragment())
|
f.add_subfragment(a := Fragment())
|
||||||
f.add_subfragment(b := Fragment(), name="b")
|
f.add_subfragment(b := Fragment(), name="b")
|
||||||
|
|
||||||
names = f._assign_names_to_fragments()
|
design = Design(f, ports=(), hierarchy=("top",))
|
||||||
self.assertEqual(names, {
|
self.assertEqual(design.fragment_names, {
|
||||||
f: ("top",),
|
f: ("top",),
|
||||||
a: ("top", "U$0"),
|
a: ("top", "U$0"),
|
||||||
b: ("top", "b")
|
b: ("top", "b")
|
||||||
|
@ -903,8 +896,8 @@ class InstanceTestCase(FHDLTestCase):
|
||||||
f.add_subfragment(a := Fragment())
|
f.add_subfragment(a := Fragment())
|
||||||
f.add_subfragment(b := Fragment(), name="b")
|
f.add_subfragment(b := Fragment(), name="b")
|
||||||
|
|
||||||
names = f._assign_names_to_fragments(hierarchy=("bench", "cpu"))
|
design = Design(f, ports=[], hierarchy=("bench", "cpu"))
|
||||||
self.assertEqual(names, {
|
self.assertEqual(design.fragment_names, {
|
||||||
f: ("bench", "cpu",),
|
f: ("bench", "cpu",),
|
||||||
a: ("bench", "cpu", "U$0"),
|
a: ("bench", "cpu", "U$0"),
|
||||||
b: ("bench", "cpu", "b")
|
b: ("bench", "cpu", "b")
|
||||||
|
@ -913,10 +906,10 @@ class InstanceTestCase(FHDLTestCase):
|
||||||
def test_assign_names_to_fragments_collide_with_signal(self):
|
def test_assign_names_to_fragments_collide_with_signal(self):
|
||||||
f = Fragment()
|
f = Fragment()
|
||||||
f.add_subfragment(a_f := Fragment(), name="a")
|
f.add_subfragment(a_f := Fragment(), name="a")
|
||||||
f.add_ports((a_s := Signal(name="a"),), dir="o")
|
a_s = Signal(name="a")
|
||||||
|
|
||||||
names = f._assign_names_to_fragments()
|
design = Design(f, ports=[("a", a_s, None)], hierarchy=("top",))
|
||||||
self.assertEqual(names, {
|
self.assertEqual(design.fragment_names, {
|
||||||
f: ("top",),
|
f: ("top",),
|
||||||
a_f: ("top", "a$U$0")
|
a_f: ("top", "a$U$0")
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue