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: 597d778cf6cb
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: 35a44f017f66
Choose a head ref
  • 3 commits
  • 4 files changed
  • 1 contributor

Commits on Dec 26, 2018

  1. hdl.ir: add an API for retrieving generated values, like FSM signal.

    This is useful for tests.
    whitequark committed Dec 26, 2018
    Copy the full SHA
    040811c View commit details
  2. hdl.dsl: provide generated values for FSMs.

    whitequark committed Dec 26, 2018
    Copy the full SHA
    934546e View commit details
  3. Copy the full SHA
    35a44f0 View commit details
Showing with 94 additions and 14 deletions.
  1. +23 −13 nmigen/hdl/dsl.py
  2. +21 −0 nmigen/hdl/ir.py
  3. +19 −1 nmigen/test/test_hdl_dsl.py
  4. +31 −0 nmigen/test/test_hdl_ir.py
36 changes: 23 additions & 13 deletions nmigen/hdl/dsl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from collections import OrderedDict
from collections import OrderedDict, namedtuple
from collections.abc import Iterable
from contextlib import contextmanager

@@ -92,6 +92,9 @@ def __setattr__(self, name, domain):
self._builder._add_domain(domain)


_FSM = namedtuple("_FSM", ("state", "encoding", "decoding"))


class Module(_ModuleBuilderRoot):
def __init__(self):
_ModuleBuilderRoot.__init__(self, self, depth=0)
@@ -105,6 +108,7 @@ def __init__(self):
self._driving = SignalDict()
self._submodules = []
self._domains = []
self._generated = {}

def _check_context(self, construct, context):
if self._ctrl_context != context:
@@ -215,6 +219,7 @@ def Case(self, value=None):
def FSM(self, reset=None, domain="sync", name="fsm"):
self._check_context("FSM", context=None)
fsm_data = self._set_ctrl("FSM", {
"name": name,
"signal": Signal(name="{}_state".format(name)),
"domain": domain,
"encoding": OrderedDict(),
@@ -255,17 +260,18 @@ def next(self):

@next.setter
def next(self, name):
for ctrl_name, ctrl_data in reversed(self._ctrl_stack):
if ctrl_name == "FSM":
if name not in ctrl_data["encoding"]:
ctrl_data["encoding"][name] = len(ctrl_data["encoding"])
self._add_statement(
assigns=[ctrl_data["signal"].eq(ctrl_data["encoding"][name])],
domain=ctrl_data["domain"],
depth=len(self._ctrl_stack))
break
else:
raise SyntaxError("`m.next = <...>` is only permitted inside an FSM")
if self._ctrl_context != "FSM":
for level, (ctrl_name, ctrl_data) in enumerate(reversed(self._ctrl_stack)):
if ctrl_name == "FSM":
if name not in ctrl_data["encoding"]:
ctrl_data["encoding"][name] = len(ctrl_data["encoding"])
self._add_statement(
assigns=[ctrl_data["signal"].eq(ctrl_data["encoding"][name])],
domain=ctrl_data["domain"],
depth=len(self._ctrl_stack))
return

raise SyntaxError("`m.next = <...>` is only permitted inside an FSM state")

def _pop_ctrl(self):
name, data = self._ctrl_stack.pop()
@@ -298,11 +304,14 @@ def _pop_ctrl(self):
fsm_signal, fsm_encoding, fsm_states = data["signal"], data["encoding"], data["states"]
fsm_signal.nbits = bits_for(len(fsm_encoding) - 1)
# The FSM is encoded such that the state with encoding 0 is always the reset state.
fsm_decoding = {n: s for s, n in fsm_encoding.items()}
fsm_decoding = OrderedDict({n: s for s, n in fsm_encoding.items()})
fsm_signal.decoder = lambda n: "{}/{}".format(fsm_decoding[n], n)
self._statements.append(Switch(fsm_signal,
OrderedDict((fsm_encoding[name], stmts) for name, stmts in fsm_states.items())))

fsm_name = data["name"]
self._generated[fsm_name] = _FSM(fsm_signal, fsm_encoding, fsm_decoding)

def _add_statement(self, assigns, domain, depth, compat_mode=False):
def domain_name(domain):
if domain is None:
@@ -354,6 +363,7 @@ def lower(self, platform):
for signal, domain in self._driving.items():
fragment.add_driver(signal, domain)
fragment.add_domains(self._domains)
fragment.generated.update(self._generated)
return fragment

get_fragment = lower
21 changes: 21 additions & 0 deletions nmigen/hdl/ir.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ def __init__(self):
self.statements = []
self.domains = OrderedDict()
self.subfragments = []
self.generated = OrderedDict()

def add_ports(self, *ports, dir):
assert dir in ("i", "o", "io")
@@ -83,6 +84,26 @@ def add_subfragment(self, subfragment, name=None):
assert isinstance(subfragment, Fragment)
self.subfragments.append((subfragment, name))

def find_subfragment(self, name_or_index):
if isinstance(name_or_index, int):
if name_or_index < len(self.subfragments):
subfragment, name = self.subfragments[name_or_index]
return subfragment
raise NameError("No subfragment at index #{}".format(name_or_index))
else:
for subfragment, name in self.subfragments:
if name == name_or_index:
return subfragment
raise NameError("No subfragment with name '{}'".format(name_or_index))

def find_generated(self, *path):
if len(path) > 1:
path_component, *path = path
return self.find_subfragment(path_component).find_generated(*path)
else:
item, = path
return self.generated[item]

def get_fragment(self, platform):
return self

20 changes: 19 additions & 1 deletion nmigen/test/test_hdl_dsl.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from collections import OrderedDict

from ..hdl.ast import *
from ..hdl.cd import *
from ..hdl.dsl import *
@@ -338,6 +340,18 @@ def test_FSM_basic(self):
"(sig b)": "sync",
})

frag = m.lower(platform=None)
fsm = frag.find_generated("fsm")
self.assertIsInstance(fsm.state, Signal)
self.assertEqual(fsm.encoding, OrderedDict({
"FIRST": 0,
"SECOND": 1,
}))
self.assertEqual(fsm.decoding, OrderedDict({
0: "FIRST",
1: "SECOND"
}))

def test_FSM_reset(self):
a = Signal()
m = Module()
@@ -378,8 +392,12 @@ def test_FSM_wrong_next(self):
msg="Only assignment to `m.next` is permitted"):
m.next
with self.assertRaises(SyntaxError,
msg="`m.next = <...>` is only permitted inside an FSM"):
msg="`m.next = <...>` is only permitted inside an FSM state"):
m.next = "FOO"
with self.assertRaises(SyntaxError,
msg="`m.next = <...>` is only permitted inside an FSM state"):
with m.FSM():
m.next = "FOO"

def test_auto_pop_ctrl(self):
m = Module()
31 changes: 31 additions & 0 deletions nmigen/test/test_hdl_ir.py
Original file line number Diff line number Diff line change
@@ -7,6 +7,37 @@
from .tools import *


class FragmentGeneratedTestCase(FHDLTestCase):
def test_find_subfragment(self):
f1 = Fragment()
f2 = Fragment()
f1.add_subfragment(f2, "f2")

self.assertEqual(f1.find_subfragment(0), f2)
self.assertEqual(f1.find_subfragment("f2"), f2)

def test_find_subfragment_wrong(self):
f1 = Fragment()
f2 = Fragment()
f1.add_subfragment(f2, "f2")

with self.assertRaises(NameError,
msg="No subfragment at index #1"):
f1.find_subfragment(1)
with self.assertRaises(NameError,
msg="No subfragment with name 'fx'"):
f1.find_subfragment("fx")

def test_find_generated(self):
f1 = Fragment()
f2 = Fragment()
f2.generated["sig"] = sig = Signal()
f1.add_subfragment(f2, "f2")

self.assertEqual(SignalKey(f1.find_generated("f2", "sig")),
SignalKey(sig))


class FragmentDriversTestCase(FHDLTestCase):
def test_empty(self):
f = Fragment()