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: 0015713bfb06
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: 2001359b665a
Choose a head ref
  • 2 commits
  • 8 files changed
  • 1 contributor

Commits on Dec 14, 2018

  1. fhdl.ir: Fragment.{drive→add_driver}

    whitequark committed Dec 14, 2018
    Copy the full SHA
    579feab View commit details
  2. fhdl.ir: automatically flatten hierarchy to resolve driver conflicts.

    Fixes #5.
    whitequark committed Dec 14, 2018
    Copy the full SHA
    2001359 View commit details
Showing with 230 additions and 21 deletions.
  1. +2 −0 .coveragerc
  2. +1 −1 nmigen/fhdl/dsl.py
  3. +79 −2 nmigen/fhdl/ir.py
  4. +2 −2 nmigen/fhdl/xfrm.py
  5. +122 −2 nmigen/test/test_fhdl_ir.py
  6. +13 −13 nmigen/test/test_fhdl_xfrm.py
  7. +1 −1 nmigen/test/test_sim.py
  8. +10 −0 nmigen/test/tools.py
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -9,3 +9,5 @@ omit =
[report]
exclude_lines =
:nocov:
partial_branches =
:nobr:
2 changes: 1 addition & 1 deletion nmigen/fhdl/dsl.py
Original file line number Diff line number Diff line change
@@ -274,7 +274,7 @@ def lower(self, platform):
fragment.add_subfragment(submodule.get_fragment(platform), name)
fragment.add_statements(self._statements)
for signal, domain in self._driving.items():
fragment.drive(signal, domain)
fragment.add_driver(signal, domain)
return fragment

get_fragment = lower
81 changes: 79 additions & 2 deletions nmigen/fhdl/ir.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import warnings
from collections import defaultdict, OrderedDict

from ..tools import *
from .ast import *
from .cd import *


__all__ = ["Fragment"]
__all__ = ["Fragment", "DriverConflict"]


class DriverConflict(UserWarning):
pass


class Fragment:
@@ -24,7 +29,7 @@ def add_ports(self, *ports, kind):
def iter_ports(self):
yield from self.ports.keys()

def drive(self, signal, domain=None):
def add_driver(self, signal, domain=None):
if domain not in self.drivers:
self.drivers[domain] = ValueSet()
self.drivers[domain].add(signal)
@@ -73,6 +78,77 @@ def add_subfragment(self, subfragment, name=None):
assert isinstance(subfragment, Fragment)
self.subfragments.append((subfragment, name))

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

driver_subfrags = ValueDict()

# 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))

for i, (subfrag, name) in enumerate(self.subfragments):
# 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)

# Second, classify subfragments by domains they define.
for signal in subfrag_drivers:
if signal not in driver_subfrags:
driver_subfrags[signal] = set()
driver_subfrags[signal].add((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()
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)

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

# 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
# A and C were driving a signal S. In that case, since B is not driving S itself,
# processing B will not result in any flattening, but since B is transitively driving S,
# processing A will flatten B into it. Afterwards, we have a tree like AB --- C, which
# has another conflict.
if any(flatten_subfrags):
# Try flattening again.
return self._resolve_driver_conflicts(hierarchy, mode)

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

def _propagate_domains_up(self, hierarchy=("top",)):
from .xfrm import DomainRenamer

@@ -193,6 +269,7 @@ def prepare(self, ports=(), ensure_sync_exists=True):

fragment = FragmentTransformer()(self)
fragment._propagate_domains(ensure_sync_exists)
fragment._resolve_driver_conflicts()
fragment = fragment._insert_domain_resets()
fragment = fragment._lower_domain_signals()
fragment._propagate_ports(ports)
4 changes: 2 additions & 2 deletions nmigen/fhdl/xfrm.py
Original file line number Diff line number Diff line change
@@ -119,7 +119,7 @@ def map_statements(self, fragment, new_fragment):

def map_drivers(self, fragment, new_fragment):
for domain, signal in fragment.iter_drivers():
new_fragment.drive(signal, domain)
new_fragment.add_driver(signal, domain)

def on_fragment(self, fragment):
new_fragment = Fragment()
@@ -165,7 +165,7 @@ def map_drivers(self, fragment, new_fragment):
if domain in self.domain_map:
domain = self.domain_map[domain]
for signal in signals:
new_fragment.drive(signal, domain)
new_fragment.add_driver(signal, domain)


class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
124 changes: 122 additions & 2 deletions nmigen/test/test_fhdl_ir.py
Original file line number Diff line number Diff line change
@@ -109,7 +109,7 @@ def test_input_cd(self):
self.c1.eq(self.s1)
)
f.add_domains(sync)
f.drive(self.c1, "sync")
f.add_driver(self.c1, "sync")

f._propagate_ports(ports=())
self.assertEqual(f.ports, ValueDict([
@@ -125,7 +125,7 @@ def test_input_cd_reset_less(self):
self.c1.eq(self.s1)
)
f.add_domains(sync)
f.drive(self.c1, "sync")
f.add_driver(self.c1, "sync")

f._propagate_ports(ports=())
self.assertEqual(f.ports, ValueDict([
@@ -245,3 +245,123 @@ def test_propagate_ensure_sync(self):
self.assertEqual(f1.domains.keys(), {"sync"})
self.assertEqual(f2.domains.keys(), {"sync"})
self.assertEqual(f1.domains["sync"], f2.domains["sync"])


class FragmentDriverConflictTestCase(FHDLTestCase):
def setUp_self_sub(self):
self.s1 = Signal()
self.c1 = Signal()
self.c2 = Signal()

self.f1 = Fragment()
self.f1.add_statements(self.c1.eq(0))
self.f1.add_driver(self.s1)
self.f1.add_driver(self.c1, "sync")

self.f1a = Fragment()
self.f1.add_subfragment(self.f1a, "f1a")

self.f2 = Fragment()
self.f2.add_statements(self.c2.eq(1))
self.f2.add_driver(self.s1)
self.f2.add_driver(self.c2, "sync")
self.f1.add_subfragment(self.f2)

self.f1b = Fragment()
self.f1.add_subfragment(self.f1b, "f1b")

self.f2a = Fragment()
self.f2.add_subfragment(self.f2a, "f2a")

def test_conflict_self_sub(self):
self.setUp_self_sub()

self.f1._resolve_driver_conflicts(mode="silent")
self.assertEqual(self.f1.subfragments, [
(self.f1a, "f1a"),
(self.f1b, "f1b"),
(self.f2a, "f2a"),
])
self.assertRepr(self.f1.statements, """
(
(eq (sig c1) (const 1'd0))
(eq (sig c2) (const 1'd1))
)
""")
self.assertEqual(self.f1.drivers, {
None: ValueSet((self.s1,)),
"sync": ValueSet((self.c1, self.c2)),
})

def test_conflict_self_sub_error(self):
self.setUp_self_sub()

with self.assertRaises(DriverConflict,
msg="Signal '(sig s1)' is driven from multiple fragments: top, top.<unnamed #1>"):
self.f1._resolve_driver_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")

def setUp_sub_sub(self):
self.s1 = Signal()
self.c1 = Signal()
self.c2 = Signal()

self.f1 = Fragment()

self.f2 = Fragment()
self.f2.add_driver(self.s1)
self.f2.add_statements(self.c1.eq(0))
self.f1.add_subfragment(self.f2)

self.f3 = Fragment()
self.f3.add_driver(self.s1)
self.f3.add_statements(self.c2.eq(1))
self.f1.add_subfragment(self.f3)

def test_conflict_sub_sub(self):
self.setUp_sub_sub()

self.f1._resolve_driver_conflicts(mode="silent")
self.assertEqual(self.f1.subfragments, [])
self.assertRepr(self.f1.statements, """
(
(eq (sig c1) (const 1'd0))
(eq (sig c2) (const 1'd1))
)
""")

def setUp_self_subsub(self):
self.s1 = Signal()
self.c1 = Signal()
self.c2 = Signal()

self.f1 = Fragment()
self.f1.add_driver(self.s1)

self.f2 = Fragment()
self.f2.add_statements(self.c1.eq(0))
self.f1.add_subfragment(self.f2)

self.f3 = Fragment()
self.f3.add_driver(self.s1)
self.f3.add_statements(self.c2.eq(1))
self.f2.add_subfragment(self.f3)

def test_conflict_self_subsub(self):
self.setUp_self_subsub()

self.f1._resolve_driver_conflicts(mode="silent")
self.assertEqual(self.f1.subfragments, [])
self.assertRepr(self.f1.statements, """
(
(eq (sig c1) (const 1'd0))
(eq (sig c2) (const 1'd1))
)
""")
26 changes: 13 additions & 13 deletions nmigen/test/test_fhdl_xfrm.py
Original file line number Diff line number Diff line change
@@ -23,9 +23,9 @@ def test_rename_signals(self):
self.s4.eq(ClockSignal("other")),
self.s5.eq(ResetSignal("other")),
)
f.drive(self.s1, None)
f.drive(self.s2, None)
f.drive(self.s3, "sync")
f.add_driver(self.s1, None)
f.add_driver(self.s2, None)
f.add_driver(self.s3, "sync")

f = DomainRenamer("pix")(f)
self.assertRepr(f.statements, """
@@ -170,7 +170,7 @@ def test_reset_default(self):
f.add_statements(
self.s1.eq(1)
)
f.drive(self.s1, "sync")
f.add_driver(self.s1, "sync")

f = ResetInserter(self.c1)(f)
self.assertRepr(f.statements, """
@@ -189,8 +189,8 @@ def test_reset_cd(self):
self.s2.eq(0),
)
f.add_domains(ClockDomain("sync"))
f.drive(self.s1, "sync")
f.drive(self.s2, "pix")
f.add_driver(self.s1, "sync")
f.add_driver(self.s2, "pix")

f = ResetInserter({"pix": self.c1})(f)
self.assertRepr(f.statements, """
@@ -208,7 +208,7 @@ def test_reset_value(self):
f.add_statements(
self.s2.eq(0)
)
f.drive(self.s2, "sync")
f.add_driver(self.s2, "sync")

f = ResetInserter(self.c1)(f)
self.assertRepr(f.statements, """
@@ -225,7 +225,7 @@ def test_reset_less(self):
f.add_statements(
self.s3.eq(0)
)
f.drive(self.s3, "sync")
f.add_driver(self.s3, "sync")

f = ResetInserter(self.c1)(f)
self.assertRepr(f.statements, """
@@ -250,7 +250,7 @@ def test_ce_default(self):
f.add_statements(
self.s1.eq(1)
)
f.drive(self.s1, "sync")
f.add_driver(self.s1, "sync")

f = CEInserter(self.c1)(f)
self.assertRepr(f.statements, """
@@ -268,8 +268,8 @@ def test_ce_cd(self):
self.s1.eq(1),
self.s2.eq(0),
)
f.drive(self.s1, "sync")
f.drive(self.s2, "pix")
f.add_driver(self.s1, "sync")
f.add_driver(self.s2, "pix")

f = CEInserter({"pix": self.c1})(f)
self.assertRepr(f.statements, """
@@ -287,13 +287,13 @@ def test_ce_subfragment(self):
f1.add_statements(
self.s1.eq(1)
)
f1.drive(self.s1, "sync")
f1.add_driver(self.s1, "sync")

f2 = Fragment()
f2.add_statements(
self.s2.eq(1)
)
f2.drive(self.s2, "sync")
f2.add_driver(self.s2, "sync")
f1.add_subfragment(f2)

f1 = CEInserter(self.c1)(f1)
2 changes: 1 addition & 1 deletion nmigen/test/test_sim.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ def assertOperator(self, stmt, inputs, output):

frag = Fragment()
frag.add_statements(osig.eq(stmt(*isigs)))
frag.drive(osig)
frag.add_driver(osig)

with Simulator(frag,
vcd_file =open("test.vcd", "w"),
Loading