fhdl.ir: implement clock domain propagation.
This commit is contained in:
parent
fde2471963
commit
72257b6935
12 changed files with 324 additions and 46 deletions
|
|
@ -2,9 +2,14 @@ from collections import defaultdict, OrderedDict
|
|||
|
||||
from ..tools import *
|
||||
from .ast import *
|
||||
from .cd import *
|
||||
|
||||
|
||||
__all__ = ["Fragment"]
|
||||
__all__ = ["Fragment", "DomainError"]
|
||||
|
||||
|
||||
class DomainError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Fragment:
|
||||
|
|
@ -12,6 +17,7 @@ class Fragment:
|
|||
self.ports = ValueSet()
|
||||
self.drivers = OrderedDict()
|
||||
self.statements = []
|
||||
self.domains = OrderedDict()
|
||||
self.subfragments = []
|
||||
|
||||
def add_ports(self, *ports):
|
||||
|
|
@ -40,6 +46,15 @@ class Fragment:
|
|||
for signal in signals:
|
||||
yield domain, signal
|
||||
|
||||
def add_domains(self, *domains):
|
||||
for domain in domains:
|
||||
assert isinstance(domain, ClockDomain)
|
||||
assert domain.name not in self.domains
|
||||
self.domains[domain.name] = domain
|
||||
|
||||
def iter_domains(self):
|
||||
yield from self.domains
|
||||
|
||||
def add_statements(self, *stmts):
|
||||
self.statements += Statement.wrap(stmts)
|
||||
|
||||
|
|
@ -47,13 +62,79 @@ class Fragment:
|
|||
assert isinstance(subfragment, Fragment)
|
||||
self.subfragments.append((subfragment, name))
|
||||
|
||||
def _propagate_ports(self, ports, clock_domains={}):
|
||||
def _propagate_domains_up(self, hierarchy=("top",)):
|
||||
from .xfrm import DomainRenamer
|
||||
|
||||
domain_subfrags = defaultdict(lambda: set())
|
||||
|
||||
# For each domain defined by a subfragment, determine which subfragments define it.
|
||||
for i, (subfrag, name) in enumerate(self.subfragments):
|
||||
# First, recurse into subfragments and let them propagate domains up as well.
|
||||
hier_name = name
|
||||
if hier_name is None:
|
||||
hier_name = "<unnamed #{}>".format(i)
|
||||
subfrag._propagate_domains_up(hierarchy + (hier_name,))
|
||||
|
||||
# Second, classify subfragments by domains they define.
|
||||
for domain in subfrag.iter_domains():
|
||||
domain_subfrags[domain].add((subfrag, name, i))
|
||||
|
||||
# For each domain defined by more than one subfragment, rename the domain in each
|
||||
# of the subfragments such that they no longer conflict.
|
||||
for domain, subfrags in domain_subfrags.items():
|
||||
if len(subfrags) == 1:
|
||||
continue
|
||||
|
||||
names = [n for f, n, i in subfrags]
|
||||
if not all(names):
|
||||
names = sorted("<unnamed #{}>".format(i) if n is None else "'{}'".format(n)
|
||||
for f, n, i in subfrags)
|
||||
raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}'; "
|
||||
"it is necessary to either rename subfragment domains "
|
||||
"explicitly, or give names to subfragments"
|
||||
.format(domain, ", ".join(names), ".".join(hierarchy)))
|
||||
|
||||
if len(names) != len(set(names)):
|
||||
names = sorted("#{}".format(i) for f, n, i in subfrags)
|
||||
raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}', "
|
||||
"some of which have identical names; it is necessary to either "
|
||||
"rename subfragment domains explicitly, or give distinct names "
|
||||
"to subfragments"
|
||||
.format(domain, ", ".join(names), ".".join(hierarchy)))
|
||||
|
||||
for subfrag, name, i in subfrags:
|
||||
self.subfragments[i] = \
|
||||
(DomainRenamer({domain: "{}_{}".format(name, domain)})(subfrag), name)
|
||||
|
||||
# Finally, collect the (now unique) subfragment domains, and merge them into our domains.
|
||||
for subfrag, name in self.subfragments:
|
||||
for domain in subfrag.iter_domains():
|
||||
self.add_domains(subfrag.domains[domain])
|
||||
|
||||
def _propagate_domains_down(self):
|
||||
# For each domain defined in this fragment, ensure it also exists in all subfragments.
|
||||
for subfrag, name in self.subfragments:
|
||||
for domain in self.iter_domains():
|
||||
if domain in subfrag.domains:
|
||||
assert self.domains[domain] is subfrag.domains[domain]
|
||||
else:
|
||||
subfrag.add_domains(self.domains[domain])
|
||||
|
||||
subfrag._propagate_domains_down()
|
||||
|
||||
def _propagate_domains(self, ensure_sync_exists=False):
|
||||
self._propagate_domains_up()
|
||||
if ensure_sync_exists and not self.domains:
|
||||
self.add_domains(ClockDomain("sync"))
|
||||
self._propagate_domains_down()
|
||||
|
||||
def _propagate_ports(self, ports):
|
||||
# Collect all signals we're driving (on LHS of statements), and signals we're using
|
||||
# (on RHS of statements, or in clock domains).
|
||||
self_driven = union(s._lhs_signals() for s in self.statements)
|
||||
self_used = union(s._rhs_signals() for s in self.statements)
|
||||
for domain, _ in self.iter_sync():
|
||||
cd = clock_domains[domain]
|
||||
cd = self.domains[domain]
|
||||
self_used.add(cd.clk)
|
||||
if cd.rst is not None:
|
||||
self_used.add(cd.rst)
|
||||
|
|
@ -69,8 +150,7 @@ class Fragment:
|
|||
for subfrag, name in self.subfragments:
|
||||
# Always ask subfragments to provide all signals we're using and signals we're asked
|
||||
# to provide. If the subfragment is not driving it, it will silently ignore it.
|
||||
sub_ins, sub_outs = subfrag._propagate_ports(ports=self_used | ports,
|
||||
clock_domains=clock_domains)
|
||||
sub_ins, sub_outs = subfrag._propagate_ports(ports=self_used | ports)
|
||||
# Refine the input port approximation: if a subfragment is driving a signal,
|
||||
# it is definitely not our input.
|
||||
ins -= sub_outs
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class ValueTransformer:
|
|||
elif isinstance(value, Repl):
|
||||
return self.on_Repl(value)
|
||||
else:
|
||||
raise TypeError("Cannot transform value {!r}".format(value))
|
||||
raise TypeError("Cannot transform value {!r}".format(value)) # :nocov:
|
||||
|
||||
def __call__(self, value):
|
||||
return self.on_value(value)
|
||||
|
|
@ -84,7 +84,7 @@ class StatementTransformer:
|
|||
elif isinstance(stmt, (list, tuple)):
|
||||
return self.on_statements(stmt)
|
||||
else:
|
||||
raise TypeError("Cannot transform statement {!r}".format(stmt))
|
||||
raise TypeError("Cannot transform statement {!r}".format(stmt)) # :nocov:
|
||||
|
||||
def __call__(self, value):
|
||||
return self.on_statement(value)
|
||||
|
|
@ -95,6 +95,10 @@ class FragmentTransformer:
|
|||
for subfragment, name in fragment.subfragments:
|
||||
new_fragment.add_subfragment(self(subfragment), name)
|
||||
|
||||
def map_domains(self, fragment, new_fragment):
|
||||
for domain in fragment.iter_domains():
|
||||
new_fragment.add_domains(fragment.domains[domain])
|
||||
|
||||
def map_statements(self, fragment, new_fragment):
|
||||
if hasattr(self, "on_statement"):
|
||||
new_fragment.add_statements(map(self.on_statement, fragment.statements))
|
||||
|
|
@ -108,6 +112,7 @@ class FragmentTransformer:
|
|||
def on_fragment(self, fragment):
|
||||
new_fragment = Fragment()
|
||||
self.map_subfragments(fragment, new_fragment)
|
||||
self.map_domains(fragment, new_fragment)
|
||||
self.map_statements(fragment, new_fragment)
|
||||
self.map_drivers(fragment, new_fragment)
|
||||
return new_fragment
|
||||
|
|
@ -117,25 +122,36 @@ class FragmentTransformer:
|
|||
|
||||
|
||||
class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer):
|
||||
def __init__(self, domains):
|
||||
if isinstance(domains, str):
|
||||
domains = {"sync": domains}
|
||||
self.domains = OrderedDict(domains)
|
||||
def __init__(self, domain_map):
|
||||
if isinstance(domain_map, str):
|
||||
domain_map = {"sync": domain_map}
|
||||
self.domain_map = OrderedDict(domain_map)
|
||||
|
||||
def on_ClockSignal(self, value):
|
||||
if value.domain in self.domains:
|
||||
return ClockSignal(self.domains[value.domain])
|
||||
if value.domain in self.domain_map:
|
||||
return ClockSignal(self.domain_map[value.domain])
|
||||
return value
|
||||
|
||||
def on_ResetSignal(self, value):
|
||||
if value.domain in self.domains:
|
||||
return ResetSignal(self.domains[value.domain])
|
||||
if value.domain in self.domain_map:
|
||||
return ResetSignal(self.domain_map[value.domain])
|
||||
return value
|
||||
|
||||
def map_domains(self, fragment, new_fragment):
|
||||
for domain in fragment.iter_domains():
|
||||
cd = fragment.domains[domain]
|
||||
if domain in self.domain_map:
|
||||
if cd.name == domain:
|
||||
# Rename the actual ClockDomain object.
|
||||
cd.name = self.domain_map[domain]
|
||||
else:
|
||||
assert cd.name == self.domain_map[domain]
|
||||
new_fragment.add_domains(cd)
|
||||
|
||||
def map_drivers(self, fragment, new_fragment):
|
||||
for domain, signals in fragment.drivers.items():
|
||||
if domain in self.domains:
|
||||
domain = self.domains[domain]
|
||||
if domain in self.domain_map:
|
||||
domain = self.domain_map[domain]
|
||||
for signal in signals:
|
||||
new_fragment.drive(signal, domain)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue