Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/nmigen
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 59c7540aeb08
Choose a base ref
...
head repository: m-labs/nmigen
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 68dae9f50e82
Choose a head ref
  • 2 commits
  • 2 files changed
  • 1 contributor

Commits on Dec 22, 2018

  1. hdl.ir: factor out _merge_subfragment. NFC.

    whitequark committed Dec 22, 2018
    Copy the full SHA
    fd89d2f View commit details
  2. Copy the full SHA
    68dae9f View commit details
Showing with 121 additions and 45 deletions.
  1. +77 −39 nmigen/hdl/ir.py
  2. +44 −6 nmigen/test/test_hdl_ir.py
116 changes: 77 additions & 39 deletions nmigen/hdl/ir.py
Original file line number Diff line number Diff line change
@@ -86,67 +86,104 @@ def add_subfragment(self, subfragment, name=None):
def get_fragment(self, platform):
return self

def _resolve_driver_conflicts(self, hierarchy=("top",), mode="warn"):
def _merge_subfragment(self, subfragment):
# Merge subfragment's everything except clock domains into this fragment.
# 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.
self.ports.update(subfragment.ports)
for domain, signal in subfragment.iter_drivers():
self.add_driver(signal, domain)
self.statements += subfragment.statements
self.subfragments += subfragment.subfragments

# Remove the merged subfragment.
found = False
for i, (check_subfrag, check_name) in enumerate(self.subfragments): # :nobr:
if subfragment == check_subfrag:
del self.subfragments[i]
found = True
break
assert found

def _resolve_hierarchy_conflicts(self, hierarchy=("top",), mode="warn"):
assert mode in ("silent", "warn", "error")

driver_subfrags = SignalDict()
memory_subfrags = OrderedDict()
def add_subfrag(registry, entity, entry):
if entity not in registry:
registry[entity] = set()
registry[entity].add(entry)

# For each signal driven by this fragment and/or its subfragments, determine which
# subfragments also drive it.
for domain, signal in self.iter_drivers():
if signal not in driver_subfrags:
driver_subfrags[signal] = set()
driver_subfrags[signal].add((None, hierarchy))
add_subfrag(driver_subfrags, signal, (None, hierarchy))

for i, (subfrag, name) in enumerate(self.subfragments):
# Never flatten instances.
if isinstance(subfrag, Instance):
# For memories (which are subfragments, but semantically a part of superfragment),
# record that this fragment is driving it.
if subfrag.type in ("$memrd", "$memwr"):
memory = subfrag.parameters["MEMID"]
add_subfrag(memory_subfrags, memory, (None, hierarchy))

# Never flatten instances.
continue

# First, recurse into subfragments and let them detect driver conflicts as well.
if name is None:
name = "<unnamed #{}>".format(i)
subfrag_hierarchy = hierarchy + (name,)
subfrag_drivers = subfrag._resolve_driver_conflicts(subfrag_hierarchy, mode)
subfrag_drivers, subfrag_memories = \
subfrag._resolve_hierarchy_conflicts(subfrag_hierarchy, mode)

# Second, classify subfragments by domains they define.
# Second, classify subfragments by signals they drive and memories they use.
for signal in subfrag_drivers:
if signal not in driver_subfrags:
driver_subfrags[signal] = set()
driver_subfrags[signal].add((subfrag, subfrag_hierarchy))
add_subfrag(driver_subfrags, signal, (subfrag, subfrag_hierarchy))
for memory in subfrag_memories:
add_subfrag(memory_subfrags, memory, (subfrag, subfrag_hierarchy))

# Find out the set of subfragments that needs to be flattened into this fragment
# to resolve driver-driver conflicts.
flatten_subfrags = set()
def flatten_subfrags_if_needed(subfrags):
if len(subfrags) == 1:
return []
flatten_subfrags.update((f, h) for f, h in subfrags if f is not None)
return list(sorted(".".join(h) for f, h in subfrags))

for signal, subfrags in driver_subfrags.items():
if len(subfrags) > 1:
flatten_subfrags.update((f, h) for f, h in subfrags if f is not None)

# While we're at it, show a message.
subfrag_names = ", ".join(sorted(".".join(h) for f, h in subfrags))
message = ("Signal '{}' is driven from multiple fragments: {}"
.format(signal, subfrag_names))
if mode == "error":
raise DriverConflict(message)
elif mode == "warn":
message += "; hierarchy will be flattened"
warnings.warn_explicit(message, DriverConflict, *signal.src_loc)
subfrag_names = flatten_subfrags_if_needed(subfrags)
if not subfrag_names:
continue

# While we're at it, show a message.
message = ("Signal '{}' is driven from multiple fragments: {}"
.format(signal, ", ".join(subfrag_names)))
if mode == "error":
raise DriverConflict(message)
elif mode == "warn":
message += "; hierarchy will be flattened"
warnings.warn_explicit(message, DriverConflict, *signal.src_loc)

for memory, subfrags in memory_subfrags.items():
subfrag_names = flatten_subfrags_if_needed(subfrags)
if not subfrag_names:
continue

# While we're at it, show a message.
message = ("Memory '{}' is accessed from multiple fragments: {}"
.format(memory.name, ", ".join(subfrag_names)))
if mode == "error":
raise DriverConflict(message)
elif mode == "warn":
message += "; hierarchy will be flattened"
warnings.warn_explicit(message, DriverConflict, *memory.src_loc)

# Flatten hierarchy.
for subfrag, subfrag_hierarchy in sorted(flatten_subfrags, key=lambda x: x[1]):
# Merge subfragment's everything except clock domains into this fragment.
# 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.
self.ports.update(subfrag.ports)
for domain, signal in subfrag.iter_drivers():
self.add_driver(signal, domain)
self.statements += subfrag.statements
self.subfragments += subfrag.subfragments

# Remove the merged subfragment.
for i, (check_subfrag, check_name) in enumerate(self.subfragments): # :nobr:
if subfrag == check_subfrag:
del self.subfragments[i]
break
self._merge_subfragment(subfrag)

# If we flattened anything, we might be in a situation where we have a driver conflict
# again, e.g. if we had a tree of fragments like A --- B --- C where only fragments
@@ -156,10 +193,11 @@ def _resolve_driver_conflicts(self, hierarchy=("top",), mode="warn"):
# has another conflict.
if any(flatten_subfrags):
# Try flattening again.
return self._resolve_driver_conflicts(hierarchy, mode)
return self._resolve_hierarchy_conflicts(hierarchy, mode)

# Nothing was flattened, we're done!
return SignalSet(driver_subfrags.keys())
return (SignalSet(driver_subfrags.keys()),
set(memory_subfrags.keys()))

def _propagate_domains_up(self, hierarchy=("top",)):
from .xfrm import DomainRenamer
@@ -302,7 +340,7 @@ def prepare(self, ports=(), ensure_sync_exists=True):

fragment = FragmentTransformer()(self)
fragment._propagate_domains(ensure_sync_exists)
fragment._resolve_driver_conflicts()
fragment._resolve_hierarchy_conflicts()
fragment = fragment._insert_domain_resets()
fragment = fragment._lower_domain_signals()
fragment._propagate_ports(ports)
50 changes: 44 additions & 6 deletions nmigen/test/test_hdl_ir.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
from ..hdl.ast import *
from ..hdl.cd import *
from ..hdl.ir import *
from ..hdl.mem import *
from .tools import *


@@ -321,7 +322,7 @@ def test_propagate_ensure_sync(self):
self.assertEqual(f1.domains["sync"], f2.domains["sync"])


class FragmentDriverConflictTestCase(FHDLTestCase):
class FragmentHierarchyConflictTestCase(FHDLTestCase):
def setUp_self_sub(self):
self.s1 = Signal()
self.c1 = Signal()
@@ -350,7 +351,7 @@ def setUp_self_sub(self):
def test_conflict_self_sub(self):
self.setUp_self_sub()

self.f1._resolve_driver_conflicts(mode="silent")
self.f1._resolve_hierarchy_conflicts(mode="silent")
self.assertEqual(self.f1.subfragments, [
(self.f1a, "f1a"),
(self.f1b, "f1b"),
@@ -372,15 +373,15 @@ def test_conflict_self_sub_error(self):

with self.assertRaises(DriverConflict,
msg="Signal '(sig s1)' is driven from multiple fragments: top, top.<unnamed #1>"):
self.f1._resolve_driver_conflicts(mode="error")
self.f1._resolve_hierarchy_conflicts(mode="error")

def test_conflict_self_sub_warning(self):
self.setUp_self_sub()

with self.assertWarns(DriverConflict,
msg="Signal '(sig s1)' is driven from multiple fragments: top, top.<unnamed #1>; "
"hierarchy will be flattened"):
self.f1._resolve_driver_conflicts(mode="warn")
self.f1._resolve_hierarchy_conflicts(mode="warn")

def setUp_sub_sub(self):
self.s1 = Signal()
@@ -402,7 +403,7 @@ def setUp_sub_sub(self):
def test_conflict_sub_sub(self):
self.setUp_sub_sub()

self.f1._resolve_driver_conflicts(mode="silent")
self.f1._resolve_hierarchy_conflicts(mode="silent")
self.assertEqual(self.f1.subfragments, [])
self.assertRepr(self.f1.statements, """
(
@@ -431,7 +432,7 @@ def setUp_self_subsub(self):
def test_conflict_self_subsub(self):
self.setUp_self_subsub()

self.f1._resolve_driver_conflicts(mode="silent")
self.f1._resolve_hierarchy_conflicts(mode="silent")
self.assertEqual(self.f1.subfragments, [])
self.assertRepr(self.f1.statements, """
(
@@ -440,6 +441,43 @@ def test_conflict_self_subsub(self):
)
""")

def setUp_memory(self):
self.m = Memory(width=8, depth=4)
self.fr = self.m.read_port().get_fragment(platform=None)
self.fw = self.m.write_port().get_fragment(platform=None)
self.f1 = Fragment()
self.f2 = Fragment()
self.f2.add_subfragment(self.fr)
self.f1.add_subfragment(self.f2)
self.f3 = Fragment()
self.f3.add_subfragment(self.fw)
self.f1.add_subfragment(self.f3)

def test_conflict_memory(self):
self.setUp_memory()

self.f1._resolve_hierarchy_conflicts(mode="silent")
self.assertEqual(self.f1.subfragments, [
(self.fr, None),
(self.fw, None),
])

def test_conflict_memory_error(self):
self.setUp_memory()

with self.assertRaises(DriverConflict,
msg="Memory 'm' is accessed from multiple fragments: top.<unnamed #0>, "
"top.<unnamed #1>"):
self.f1._resolve_hierarchy_conflicts(mode="error")

def test_conflict_memory_warning(self):
self.setUp_memory()

with self.assertWarns(DriverConflict,
msg="Memory 'm' is accessed from multiple fragments: top.<unnamed #0>, "
"top.<unnamed #1>; hierarchy will be flattened"):
self.f1._resolve_hierarchy_conflicts(mode="warn")


class InstanceTestCase(FHDLTestCase):
def setUp_cpu(self):