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: b34c1a9ad083
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: 240a40c2c2f8
Choose a head ref
  • 2 commits
  • 7 files changed
  • 1 contributor

Commits on Dec 14, 2018

  1. fhdl.xfrm: implement DomainLowerer.

    whitequark committed Dec 14, 2018
    Copy the full SHA
    7d91dd5 View commit details
  2. Copy the full SHA
    240a40c View commit details
Showing with 124 additions and 9 deletions.
  1. +2 −2 nmigen/back/pysim.py
  2. +4 −1 nmigen/fhdl/ast.py
  3. +5 −1 nmigen/fhdl/cd.py
  4. +8 −3 nmigen/fhdl/ir.py
  5. +28 −2 nmigen/fhdl/xfrm.py
  6. +8 −0 nmigen/test/test_fhdl_cd.py
  7. +69 −0 nmigen/test/test_fhdl_xfrm.py
4 changes: 2 additions & 2 deletions nmigen/back/pysim.py
Original file line number Diff line number Diff line change
@@ -513,8 +513,8 @@ def __exit__(self, *args):
for domain, cd in self._domains.items():
with gtkw_save.group("d.{}".format(domain)):
if cd.rst is not None:
gtkw_save.trace("top.{}".format(cd.rst.name))
gtkw_save.trace("top.{}".format(cd.clk.name))
gtkw_save.trace(self._vcd_names[cd.rst])
gtkw_save.trace(self._vcd_names[cd.clk])

for signal in self._gtkw_signals:
if signal in self._vcd_names:
5 changes: 4 additions & 1 deletion nmigen/fhdl/ast.py
Original file line number Diff line number Diff line change
@@ -626,12 +626,15 @@ class ResetSignal(Value):
----------
domain : str
Clock domain to obtain a reset signal for. Defaults to ``"sync"``.
allow_reset_less : bool
If the clock domain is reset-less, act as a constant ``0`` instead of reporting an error.
"""
def __init__(self, domain="sync"):
def __init__(self, domain="sync", allow_reset_less=False):
super().__init__()
if not isinstance(domain, str):
raise TypeError("Clock domain name must be a string, not {!r}".format(domain))
self.domain = domain
self.allow_reset_less = allow_reset_less

def __repr__(self):
return "(rst {})".format(self.domain)
6 changes: 5 additions & 1 deletion nmigen/fhdl/cd.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,11 @@
from .ast import Signal


__all__ = ["ClockDomain"]
__all__ = ["ClockDomain", "DomainError"]


class DomainError(Exception):
pass


class ClockDomain:
11 changes: 8 additions & 3 deletions nmigen/fhdl/ir.py
Original file line number Diff line number Diff line change
@@ -146,9 +146,13 @@ def _propagate_domains(self, ensure_sync_exists):
def _insert_domain_resets(self):
from .xfrm import ResetInserter

return ResetInserter({
cd.name: cd.rst for cd in self.domains.values() if cd.rst is not None
})(self)
resets = {cd.name: cd.rst for cd in self.domains.values() if cd.rst is not None}
return ResetInserter(resets)(self)

def _lower_domain_signals(self):
from .xfrm import DomainLowerer

return DomainLowerer(self.domains)(self)

def _propagate_ports(self, ports):
# Collect all signals we're driving (on LHS of statements), and signals we're using
@@ -194,5 +198,6 @@ def prepare(self, ports=(), ensure_sync_exists=True):
fragment = FragmentTransformer()(self)
fragment._propagate_domains(ensure_sync_exists)
fragment = fragment._insert_domain_resets()
fragment = fragment._lower_domain_signals()
fragment._propagate_ports(ports)
return fragment
30 changes: 28 additions & 2 deletions nmigen/fhdl/xfrm.py
Original file line number Diff line number Diff line change
@@ -2,11 +2,12 @@

from ..tools import flatten
from .ast import *
from .ast import _StatementList
from .ir import *


__all__ = ["ValueTransformer", "StatementTransformer", "FragmentTransformer",
"DomainRenamer", "ResetInserter", "CEInserter"]
"DomainRenamer", "DomainLowerer", "ResetInserter", "CEInserter"]


class ValueTransformer:
@@ -81,7 +82,7 @@ def on_Switch(self, stmt):
return Switch(self.on_value(stmt.test), cases)

def on_statements(self, stmt):
return list(flatten(self.on_statement(stmt) for stmt in stmt))
return _StatementList(flatten(self.on_statement(stmt) for stmt in stmt))

def on_unknown_statement(self, stmt):
raise TypeError("Cannot transform statement {!r}".format(stmt)) # :nocov:
@@ -166,6 +167,31 @@ def map_drivers(self, fragment, new_fragment):
new_fragment.drive(signal, domain)


class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
def __init__(self, domains):
self.domains = domains

def _resolve(self, domain, context):
if domain not in self.domains:
raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
.format(context, domain))
return self.domains[domain]

def on_ClockSignal(self, value):
cd = self._resolve(value.domain, value)
return cd.clk

def on_ResetSignal(self, value):
cd = self._resolve(value.domain, value)
if cd.rst is None:
if value.allow_reset_less:
return Const(0)
else:
raise DomainError("Signal {!r} refers to reset of reset-less domain '{}'"
.format(value, value.domain))
return cd.rst


class _ControlInserter(FragmentTransformer):
def __init__(self, controls):
if isinstance(controls, Value):
8 changes: 8 additions & 0 deletions nmigen/test/test_fhdl_cd.py
Original file line number Diff line number Diff line change
@@ -47,3 +47,11 @@ def test_rename(self):
self.assertEqual(sync.name, "pix")
self.assertEqual(sync.clk.name, "pix_clk")
self.assertEqual(sync.rst.name, "pix_rst")

def test_rename_reset_less(self):
sync = ClockDomain(reset_less=True)
self.assertEqual(sync.name, "sync")
self.assertEqual(sync.clk.name, "clk")
sync.rename("pix")
self.assertEqual(sync.name, "pix")
self.assertEqual(sync.clk.name, "pix_clk")
69 changes: 69 additions & 0 deletions nmigen/test/test_fhdl_xfrm.py
Original file line number Diff line number Diff line change
@@ -89,6 +89,75 @@ def test_rename_cd_subfragment(self):
})


class DomainLowererTestCase(FHDLTestCase):
def setUp(self):
self.s = Signal()

def test_lower_clk(self):
sync = ClockDomain()
f = Fragment()
f.add_statements(
self.s.eq(ClockSignal("sync"))
)

f = DomainLowerer({"sync": sync})(f)
self.assertRepr(f.statements, """
(
(eq (sig s) (sig clk))
)
""")

def test_lower_rst(self):
sync = ClockDomain()
f = Fragment()
f.add_statements(
self.s.eq(ResetSignal("sync"))
)

f = DomainLowerer({"sync": sync})(f)
self.assertRepr(f.statements, """
(
(eq (sig s) (sig rst))
)
""")

def test_lower_rst_reset_less(self):
sync = ClockDomain(reset_less=True)
f = Fragment()
f.add_statements(
self.s.eq(ResetSignal("sync", allow_reset_less=True))
)

f = DomainLowerer({"sync": sync})(f)
self.assertRepr(f.statements, """
(
(eq (sig s) (const 1'd0))
)
""")

def test_lower_wrong_domain(self):
sync = ClockDomain()
f = Fragment()
f.add_statements(
self.s.eq(ClockSignal("xxx"))
)

with self.assertRaises(DomainError,
msg="Signal (clk xxx) refers to nonexistent domain 'xxx'"):
DomainLowerer({"sync": sync})(f)

def test_lower_wrong_reset_less_domain(self):
sync = ClockDomain(reset_less=True)
f = Fragment()
f.add_statements(
self.s.eq(ResetSignal("sync"))
)

with self.assertRaises(DomainError,
msg="Signal (rst sync) refers to reset of reset-less domain 'sync'"):
DomainLowerer({"sync": sync})(f)


class ResetInserterTestCase(FHDLTestCase):
def setUp(self):
self.s1 = Signal()