1 from abc
import ABCMeta
2 from collections
import defaultdict
, OrderedDict
3 from functools
import reduce
7 from .._unused
import *
12 __all__
= ["UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance"]
15 class UnusedElaboratable(UnusedMustUse
):
19 class Elaboratable(MustUse
, metaclass
=ABCMeta
):
20 _MustUse__warning
= UnusedElaboratable
23 class DriverConflict(UserWarning):
29 def get(obj
, platform
):
32 if isinstance(obj
, Fragment
):
34 elif isinstance(obj
, Elaboratable
):
35 code
= obj
.elaborate
.__code
__
36 obj
._MustUse
__used
= True
37 obj
= obj
.elaborate(platform
)
38 elif hasattr(obj
, "elaborate"):
40 message
="Class {!r} is an elaboratable that does not explicitly inherit from "
41 "Elaboratable; doing so would improve diagnostics"
43 category
=RuntimeWarning,
45 code
= obj
.elaborate
.__code
__
46 obj
= obj
.elaborate(platform
)
48 raise AttributeError("Object {!r} cannot be elaborated".format(obj
))
49 if obj
is None and code
is not None:
50 warnings
.warn_explicit(
51 message
=".elaborate() returned None; missing return statement?",
53 filename
=code
.co_filename
,
54 lineno
=code
.co_firstlineno
)
57 self
.ports
= SignalDict()
58 self
.drivers
= OrderedDict()
60 self
.domains
= OrderedDict()
61 self
.subfragments
= []
62 self
.attrs
= OrderedDict()
63 self
.generated
= OrderedDict()
66 def add_ports(self
, *ports
, dir):
67 assert dir in ("i", "o", "io")
68 for port
in flatten(ports
):
69 self
.ports
[port
] = dir
71 def iter_ports(self
, dir=None):
75 for port
, port_dir
in self
.ports
.items():
79 def add_driver(self
, signal
, domain
=None):
80 if domain
not in self
.drivers
:
81 self
.drivers
[domain
] = SignalSet()
82 self
.drivers
[domain
].add(signal
)
84 def iter_drivers(self
):
85 for domain
, signals
in self
.drivers
.items():
86 for signal
in signals
:
90 if None in self
.drivers
:
91 yield from self
.drivers
[None]
94 for domain
, signals
in self
.drivers
.items():
97 for signal
in signals
:
100 def iter_signals(self
):
101 signals
= SignalSet()
102 signals |
= self
.ports
.keys()
103 for domain
, domain_signals
in self
.drivers
.items():
104 if domain
is not None:
105 cd
= self
.domains
[domain
]
107 if cd
.rst
is not None:
109 signals |
= domain_signals
112 def add_domains(self
, *domains
):
113 for domain
in flatten(domains
):
114 assert isinstance(domain
, ClockDomain
)
115 assert domain
.name
not in self
.domains
116 self
.domains
[domain
.name
] = domain
118 def iter_domains(self
):
119 yield from self
.domains
121 def add_statements(self
, *stmts
):
122 for stmt
in Statement
.cast(stmts
):
123 stmt
._MustUse
__used
= True
124 self
.statements
.append(stmt
)
126 def add_subfragment(self
, subfragment
, name
=None):
127 assert isinstance(subfragment
, Fragment
)
128 self
.subfragments
.append((subfragment
, name
))
130 def find_subfragment(self
, name_or_index
):
131 if isinstance(name_or_index
, int):
132 if name_or_index
< len(self
.subfragments
):
133 subfragment
, name
= self
.subfragments
[name_or_index
]
135 raise NameError("No subfragment at index #{}".format(name_or_index
))
137 for subfragment
, name
in self
.subfragments
:
138 if name
== name_or_index
:
140 raise NameError("No subfragment with name '{}'".format(name_or_index
))
142 def find_generated(self
, *path
):
144 path_component
, *path
= path
145 return self
.find_subfragment(path_component
).find_generated(*path
)
148 return self
.generated
[item
]
150 def elaborate(self
, platform
):
153 def _merge_subfragment(self
, subfragment
):
154 # Merge subfragment's everything except clock domains into this fragment.
155 # Flattening is done after clock domain propagation, so we can assume the domains
156 # are already the same in every involved fragment in the first place.
157 self
.ports
.update(subfragment
.ports
)
158 for domain
, signal
in subfragment
.iter_drivers():
159 self
.add_driver(signal
, domain
)
160 self
.statements
+= subfragment
.statements
161 self
.subfragments
+= subfragment
.subfragments
163 # Remove the merged subfragment.
165 for i
, (check_subfrag
, check_name
) in enumerate(self
.subfragments
): # :nobr:
166 if subfragment
== check_subfrag
:
167 del self
.subfragments
[i
]
172 def _resolve_hierarchy_conflicts(self
, hierarchy
=("top",), mode
="warn"):
173 assert mode
in ("silent", "warn", "error")
175 driver_subfrags
= SignalDict()
176 memory_subfrags
= OrderedDict()
177 def add_subfrag(registry
, entity
, entry
):
178 # Because of missing domain insertion, at the point when this code runs, we have
179 # a mixture of bound and unbound {Clock,Reset}Signals. Map the bound ones to
180 # the actual signals (because the signal itself can be driven as well); but leave
181 # the unbound ones as it is, because there's no concrete signal for it yet anyway.
182 if isinstance(entity
, ClockSignal
) and entity
.domain
in self
.domains
:
183 entity
= self
.domains
[entity
.domain
].clk
184 elif isinstance(entity
, ResetSignal
) and entity
.domain
in self
.domains
:
185 entity
= self
.domains
[entity
.domain
].rst
187 if entity
not in registry
:
188 registry
[entity
] = set()
189 registry
[entity
].add(entry
)
191 # For each signal driven by this fragment and/or its subfragments, determine which
192 # subfragments also drive it.
193 for domain
, signal
in self
.iter_drivers():
194 add_subfrag(driver_subfrags
, signal
, (None, hierarchy
))
196 flatten_subfrags
= set()
197 for i
, (subfrag
, name
) in enumerate(self
.subfragments
):
199 name
= "<unnamed #{}>".format(i
)
200 subfrag_hierarchy
= hierarchy
+ (name
,)
203 # Always flatten subfragments that explicitly request it.
204 flatten_subfrags
.add((subfrag
, subfrag_hierarchy
))
206 if isinstance(subfrag
, Instance
):
207 # For memories (which are subfragments, but semantically a part of superfragment),
208 # record that this fragment is driving it.
209 if subfrag
.type in ("$memrd", "$memwr"):
210 memory
= subfrag
.parameters
["MEMID"]
211 add_subfrag(memory_subfrags
, memory
, (None, hierarchy
))
213 # Never flatten instances.
216 # First, recurse into subfragments and let them detect driver conflicts as well.
217 subfrag_drivers
, subfrag_memories
= \
218 subfrag
._resolve
_hierarchy
_conflicts
(subfrag_hierarchy
, mode
)
220 # Second, classify subfragments by signals they drive and memories they use.
221 for signal
in subfrag_drivers
:
222 add_subfrag(driver_subfrags
, signal
, (subfrag
, subfrag_hierarchy
))
223 for memory
in subfrag_memories
:
224 add_subfrag(memory_subfrags
, memory
, (subfrag
, subfrag_hierarchy
))
226 # Find out the set of subfragments that needs to be flattened into this fragment
227 # to resolve driver-driver conflicts.
228 def flatten_subfrags_if_needed(subfrags
):
229 if len(subfrags
) == 1:
231 flatten_subfrags
.update((f
, h
) for f
, h
in subfrags
if f
is not None)
232 return list(sorted(".".join(h
) for f
, h
in subfrags
))
234 for signal
, subfrags
in driver_subfrags
.items():
235 subfrag_names
= flatten_subfrags_if_needed(subfrags
)
236 if not subfrag_names
:
239 # While we're at it, show a message.
240 message
= ("Signal '{}' is driven from multiple fragments: {}"
241 .format(signal
, ", ".join(subfrag_names
)))
243 raise DriverConflict(message
)
245 message
+= "; hierarchy will be flattened"
246 warnings
.warn_explicit(message
, DriverConflict
, *signal
.src_loc
)
248 for memory
, subfrags
in memory_subfrags
.items():
249 subfrag_names
= flatten_subfrags_if_needed(subfrags
)
250 if not subfrag_names
:
253 # While we're at it, show a message.
254 message
= ("Memory '{}' is accessed from multiple fragments: {}"
255 .format(memory
.name
, ", ".join(subfrag_names
)))
257 raise DriverConflict(message
)
259 message
+= "; hierarchy will be flattened"
260 warnings
.warn_explicit(message
, DriverConflict
, *memory
.src_loc
)
263 for subfrag
, subfrag_hierarchy
in sorted(flatten_subfrags
, key
=lambda x
: x
[1]):
264 self
._merge
_subfragment
(subfrag
)
266 # If we flattened anything, we might be in a situation where we have a driver conflict
267 # again, e.g. if we had a tree of fragments like A --- B --- C where only fragments
268 # A and C were driving a signal S. In that case, since B is not driving S itself,
269 # processing B will not result in any flattening, but since B is transitively driving S,
270 # processing A will flatten B into it. Afterwards, we have a tree like AB --- C, which
271 # has another conflict.
272 if any(flatten_subfrags
):
273 # Try flattening again.
274 return self
._resolve
_hierarchy
_conflicts
(hierarchy
, mode
)
276 # Nothing was flattened, we're done!
277 return (SignalSet(driver_subfrags
.keys()),
278 set(memory_subfrags
.keys()))
280 def _propagate_domains_up(self
, hierarchy
=("top",)):
281 from .xfrm
import DomainRenamer
283 domain_subfrags
= defaultdict(lambda: set())
285 # For each domain defined by a subfragment, determine which subfragments define it.
286 for i
, (subfrag
, name
) in enumerate(self
.subfragments
):
287 # First, recurse into subfragments and let them propagate domains up as well.
289 if hier_name
is None:
290 hier_name
= "<unnamed #{}>".format(i
)
291 subfrag
._propagate
_domains
_up
(hierarchy
+ (hier_name
,))
293 # Second, classify subfragments by domains they define.
294 for domain_name
, domain
in subfrag
.domains
.items():
297 domain_subfrags
[domain_name
].add((subfrag
, name
, i
))
299 # For each domain defined by more than one subfragment, rename the domain in each
300 # of the subfragments such that they no longer conflict.
301 for domain_name
, subfrags
in domain_subfrags
.items():
302 if len(subfrags
) == 1:
305 names
= [n
for f
, n
, i
in subfrags
]
307 names
= sorted("<unnamed #{}>".format(i
) if n
is None else "'{}'".format(n
)
308 for f
, n
, i
in subfrags
)
309 raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}'; "
310 "it is necessary to either rename subfragment domains "
311 "explicitly, or give names to subfragments"
312 .format(domain_name
, ", ".join(names
), ".".join(hierarchy
)))
314 if len(names
) != len(set(names
)):
315 names
= sorted("#{}".format(i
) for f
, n
, i
in subfrags
)
316 raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}', "
317 "some of which have identical names; it is necessary to either "
318 "rename subfragment domains explicitly, or give distinct names "
320 .format(domain_name
, ", ".join(names
), ".".join(hierarchy
)))
322 for subfrag
, name
, i
in subfrags
:
323 domain_name_map
= {domain_name
: "{}_{}".format(name
, domain_name
)}
324 self
.subfragments
[i
] = (DomainRenamer(domain_name_map
)(subfrag
), name
)
326 # Finally, collect the (now unique) subfragment domains, and merge them into our domains.
327 for subfrag
, name
in self
.subfragments
:
328 for domain_name
, domain
in subfrag
.domains
.items():
331 self
.add_domains(domain
)
333 def _propagate_domains_down(self
):
334 # For each domain defined in this fragment, ensure it also exists in all subfragments.
335 for subfrag
, name
in self
.subfragments
:
336 for domain
in self
.iter_domains():
337 if domain
in subfrag
.domains
:
338 assert self
.domains
[domain
] is subfrag
.domains
[domain
]
340 subfrag
.add_domains(self
.domains
[domain
])
342 subfrag
._propagate
_domains
_down
()
344 def _create_missing_domains(self
, missing_domain
, *, platform
=None):
345 from .xfrm
import DomainCollector
347 collector
= DomainCollector()
351 for domain_name
in collector
.used_domains
- collector
.defined_domains
:
352 if domain_name
is None:
354 value
= missing_domain(domain_name
)
356 raise DomainError("Domain '{}' is used but not defined".format(domain_name
))
357 if type(value
) is ClockDomain
:
358 self
.add_domains(value
)
359 # And expose ports on the newly added clock domain, since it is added directly
360 # and there was no chance to add any logic driving it.
361 new_domains
.append(value
)
363 new_fragment
= Fragment
.get(value
, platform
=platform
)
364 if domain_name
not in new_fragment
.domains
:
365 defined
= new_fragment
.domains
.keys()
367 "Fragment returned by missing domain callback does not define "
368 "requested domain '{}' (defines {})."
369 .format(domain_name
, ", ".join("'{}'".format(n
) for n
in defined
)))
370 self
.add_subfragment(new_fragment
, "cd_{}".format(domain_name
))
371 self
.add_domains(new_fragment
.domains
.values())
374 def _propagate_domains(self
, missing_domain
, *, platform
=None):
375 self
._propagate
_domains
_up
()
376 self
._propagate
_domains
_down
()
377 self
._resolve
_hierarchy
_conflicts
()
378 new_domains
= self
._create
_missing
_domains
(missing_domain
, platform
=platform
)
379 self
._propagate
_domains
_down
()
382 def _prepare_use_def_graph(self
, parent
, level
, uses
, defs
, ios
, top
):
383 def add_uses(*sigs
, self
=self
):
384 for sig
in flatten(sigs
):
390 for sig
in flatten(sigs
):
394 assert defs
[sig
] is self
397 for sig
in flatten(sigs
):
401 assert ios
[sig
] is self
403 # Collect all signals we're driving (on LHS of statements), and signals we're using
404 # (on RHS of statements, or in clock domains).
405 for stmt
in self
.statements
:
406 add_uses(stmt
._rhs
_signals
())
407 add_defs(stmt
._lhs
_signals
())
409 for domain
, _
in self
.iter_sync():
410 cd
= self
.domains
[domain
]
412 if cd
.rst
is not None:
415 # Repeat for subfragments.
416 for subfrag
, name
in self
.subfragments
:
417 if isinstance(subfrag
, Instance
):
418 for port_name
, (value
, dir) in subfrag
.named_ports
.items():
420 # Prioritize defs over uses.
421 rhs_without_outputs
= value
._rhs
_signals
() - subfrag
.iter_ports(dir="o")
422 subfrag
.add_ports(rhs_without_outputs
, dir=dir)
423 add_uses(value
._rhs
_signals
())
425 subfrag
.add_ports(value
._lhs
_signals
(), dir=dir)
426 add_defs(value
._lhs
_signals
())
428 subfrag
.add_ports(value
._lhs
_signals
(), dir=dir)
429 add_io(value
._lhs
_signals
())
431 parent
[subfrag
] = self
432 level
[subfrag
] = level
[self
] + 1
434 subfrag
._prepare
_use
_def
_graph
(parent
, level
, uses
, defs
, ios
, top
)
436 def _propagate_ports(self
, ports
, all_undef_as_ports
):
437 # Take this fragment graph:
439 # __ B (def: q, use: p r)
441 # A (def: p, use: q r)
443 # \_ C (def: r, use: p q)
445 # We need to consider three cases.
446 # 1. Signal p requires an input port in B;
447 # 2. Signal r requires an output port in C;
448 # 3. Signal r requires an output port in C and an input port in B.
450 # Adding these ports can be in general done in three steps for each signal:
451 # 1. Find the least common ancestor of all uses and defs.
452 # 2. Going upwards from the single def, add output ports.
453 # 3. Going upwards from all uses, add input ports.
455 parent
= {self
: None}
460 self
._prepare
_use
_def
_graph
(parent
, level
, uses
, defs
, ios
, self
)
462 ports
= SignalSet(ports
)
463 if all_undef_as_ports
:
474 def lca_of(fragu
, fragv
):
475 # Normalize fragu to be deeper than fragv.
476 if level
[fragu
] < level
[fragv
]:
477 fragu
, fragv
= fragv
, fragu
478 # Find ancestor of fragu on the same level as fragv.
479 for _
in range(level
[fragu
] - level
[fragv
]):
480 fragu
= parent
[fragu
]
481 # If fragv was the ancestor of fragv, we're done.
484 # Otherwise, they are at the same level but in different branches. Step both fragu
485 # and fragv until we find the common ancestor.
486 while parent
[fragu
] != parent
[fragv
]:
487 fragu
= parent
[fragu
]
488 fragv
= parent
[fragv
]
493 lca
= reduce(lca_of
, uses
[sig
], defs
[sig
])
495 lca
= reduce(lca_of
, uses
[sig
])
497 for frag
in uses
[sig
]:
498 if sig
in defs
and frag
is defs
[sig
]:
501 frag
.add_ports(sig
, dir="i")
507 frag
.add_ports(sig
, dir="o")
512 while frag
is not None:
513 frag
.add_ports(sig
, dir="io")
520 self
.add_ports(sig
, dir="o")
522 self
.add_ports(sig
, dir="i")
524 def prepare(self
, ports
=None, missing_domain
=lambda name
: ClockDomain(name
)):
525 from .xfrm
import SampleLowerer
, DomainLowerer
527 fragment
= SampleLowerer()(self
)
528 new_domains
= fragment
._propagate
_domains
(missing_domain
)
529 fragment
= DomainLowerer()(fragment
)
531 fragment
._propagate
_ports
(ports
=(), all_undef_as_ports
=True)
533 if not isinstance(ports
, tuple) and not isinstance(ports
, list):
534 msg
= "`ports` must be either a list or a tuple, not {!r}"\
536 if isinstance(ports
, Value
):
537 msg
+= " (did you mean `ports=(<signal>,)`, rather than `ports=<signal>`?)"
540 # Lower late bound signals like ClockSignal() to ports.
541 port_lowerer
= DomainLowerer(fragment
.domains
)
543 if not isinstance(port
, (Signal
, ClockSignal
, ResetSignal
)):
544 raise TypeError("Only signals may be added as ports, not {!r}"
546 mapped_ports
.append(port_lowerer
.on_value(port
))
547 # Add ports for all newly created missing clock domains, since not doing so defeats
548 # the purpose of domain auto-creation. (It's possible to refer to these ports before
549 # the domain actually exists through late binding, but it's inconvenient.)
550 for cd
in new_domains
:
551 mapped_ports
.append(cd
.clk
)
552 if cd
.rst
is not None:
553 mapped_ports
.append(cd
.rst
)
554 fragment
._propagate
_ports
(ports
=mapped_ports
, all_undef_as_ports
=False)
558 class Instance(Fragment
):
559 def __init__(self
, type, *args
, **kwargs
):
563 self
.parameters
= OrderedDict()
564 self
.named_ports
= OrderedDict()
566 for (kind
, name
, value
) in args
:
568 self
.attrs
[name
] = value
570 self
.parameters
[name
] = value
571 elif kind
in ("i", "o", "io"):
572 self
.named_ports
[name
] = (Value
.cast(value
), kind
)
574 raise NameError("Instance argument {!r} should be a tuple (kind, name, value) "
575 "where kind is one of \"a\", \"p\", \"i\", \"o\", or \"io\""
576 .format((kind
, name
, value
)))
578 for kw
, arg
in kwargs
.items():
579 if kw
.startswith("a_"):
580 self
.attrs
[kw
[2:]] = arg
581 elif kw
.startswith("p_"):
582 self
.parameters
[kw
[2:]] = arg
583 elif kw
.startswith("i_"):
584 self
.named_ports
[kw
[2:]] = (Value
.cast(arg
), "i")
585 elif kw
.startswith("o_"):
586 self
.named_ports
[kw
[2:]] = (Value
.cast(arg
), "o")
587 elif kw
.startswith("io_"):
588 self
.named_ports
[kw
[3:]] = (Value
.cast(arg
), "io")
590 raise NameError("Instance keyword argument {}={!r} does not start with one of "
591 "\"a_\", \"p_\", \"i_\", \"o_\", or \"io_\""