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: 010ddb96b5c0
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: 54e3195dcb5e
Choose a head ref
  • 2 commits
  • 3 files changed
  • 1 contributor

Commits on Dec 26, 2018

  1. Copy the full SHA
    b4fbef6 View commit details
  2. hdl.dsl: implement FSM.

    whitequark committed Dec 26, 2018
    Copy the full SHA
    54e3195 View commit details
Showing with 149 additions and 4 deletions.
  1. +5 −3 nmigen/back/rtlil.py
  2. +64 −1 nmigen/hdl/dsl.py
  3. +80 −0 nmigen/test/test_hdl_dsl.py
8 changes: 5 additions & 3 deletions nmigen/back/rtlil.py
Original file line number Diff line number Diff line change
@@ -710,11 +710,11 @@ def convert_fragment(builder, fragment, name, top):
stmt_compiler._has_rhs = False
stmt_compiler(lhs_group_filter(fragment.statements))

# Verilog `always @*` blocks will not run if `*` does not match anythng, i.e.
# Verilog `always @*` blocks will not run if `*` does not match anything, i.e.
# if the implicit sensitivity list is empty. We check this while translating,
# by looking at any signals on RHS. If this is not true, we add some logic
# by looking for any signals on RHS. If there aren't any, we add some logic
# whose only purpose is to trigger Verilog simulators when it converts
# through RTLIL and to Verilog.
# through RTLIL and to Verilog, by populating the sensitivity list.
if not stmt_compiler._has_rhs:
if verilog_trigger is None:
verilog_trigger = \
@@ -730,6 +730,8 @@ def convert_fragment(builder, fragment, name, top):
wire_curr, wire_next = compiler_state.resolve(signal)
sync.update(wire_curr, rhs_compiler(ast.Const(signal.reset, signal.nbits)))

# The Verilog simulator trigger needs to change at time 0, so if we haven't
# yet done that in some process, do it.
if verilog_trigger and not verilog_trigger_sync_emitted:
sync.update(verilog_trigger, "1'0")
verilog_trigger_sync_emitted = True
65 changes: 64 additions & 1 deletion nmigen/hdl/dsl.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
from collections.abc import Iterable
from contextlib import contextmanager

from ..tools import flatten
from ..tools import flatten, bits_for
from .ast import *
from .ir import *
from .xfrm import *
@@ -211,6 +211,62 @@ def Case(self, value=None):
self._ctrl_context = "Switch"
self._statements = _outer_case

@contextmanager
def FSM(self, reset=None, domain="sync", name="fsm"):
self._check_context("FSM", context=None)
fsm_data = self._set_ctrl("FSM", {
"signal": Signal(name="{}_state".format(name)),
"domain": domain,
"encoding": OrderedDict(),
"states": OrderedDict(),
})
if reset is not None:
fsm_data["encoding"][reset] = 0
try:
self._ctrl_context = "FSM"
self.domain._depth += 1
yield
finally:
self.domain._depth -= 1
self._ctrl_context = None
self._pop_ctrl()

@contextmanager
def State(self, name):
self._check_context("FSM State", context="FSM")
fsm_data = self._get_ctrl("FSM")
if name in fsm_data["states"]:
raise SyntaxError("FSM state '{}' is already defined".format(name))
if name not in fsm_data["encoding"]:
fsm_data["encoding"][name] = len(fsm_data["encoding"])
try:
_outer_case, self._statements = self._statements, []
self._ctrl_context = None
yield
self._flush_ctrl()
fsm_data["states"][name] = self._statements
finally:
self._ctrl_context = "FSM"
self._statements = _outer_case

@property
def next(self):
raise SyntaxError("Only assignment to `m.next` is permitted")

@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")

def _pop_ctrl(self):
name, data = self._ctrl_stack.pop()

@@ -238,6 +294,13 @@ def _pop_ctrl(self):

self._statements.append(Switch(switch_test, switch_cases))

if name == "FSM":
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.
self._statements.append(Switch(fsm_signal,
OrderedDict((fsm_encoding[name], stmts) for name, stmts in fsm_states.items())))

def _add_statement(self, assigns, domain, depth, compat_mode=False):
def domain_name(domain):
if domain is None:
80 changes: 80 additions & 0 deletions nmigen/test/test_hdl_dsl.py
Original file line number Diff line number Diff line change
@@ -301,6 +301,86 @@ def test_If_inside_Switch_wrong(self):
with m.If(self.s2):
pass

def test_FSM_basic(self):
a = Signal()
b = Signal()
c = Signal()
m = Module()
with m.FSM():
with m.State("FIRST"):
m.d.comb += a.eq(1)
m.next = "SECOND"
with m.State("SECOND"):
m.d.sync += b.eq(~b)
with m.If(c):
m.next = "FIRST"
m._flush()
self.assertRepr(m._statements, """
(
(switch (sig fsm_state)
(case 0
(eq (sig a) (const 1'd1))
(eq (sig fsm_state) (const 1'd1))
)
(case 1
(eq (sig b) (~ (sig b)))
(switch (cat (sig c))
(case 1
(eq (sig fsm_state) (const 1'd0)))
)
)
)
)
""")
self.assertEqual({repr(k): v for k, v in m._driving.items()}, {
"(sig a)": None,
"(sig fsm_state)": "sync",
"(sig b)": "sync",
})

def test_FSM_reset(self):
a = Signal()
m = Module()
with m.FSM(reset="SECOND"):
with m.State("FIRST"):
m.d.comb += a.eq(0)
m.next = "SECOND"
with m.State("SECOND"):
m.next = "FIRST"
m._flush()
self.assertRepr(m._statements, """
(
(switch (sig fsm_state)
(case 1
(eq (sig a) (const 1'd0))
(eq (sig fsm_state) (const 1'd0))
)
(case 0
(eq (sig fsm_state) (const 1'd1))
)
)
)
""")

def test_FSM_wrong_redefined(self):
m = Module()
with m.FSM():
with m.State("FOO"):
pass
with self.assertRaises(SyntaxError,
msg="FSM state 'FOO' is already defined"):
with m.State("FOO"):
pass

def test_FSM_wrong_next(self):
m = Module()
with self.assertRaises(SyntaxError,
msg="Only assignment to `m.next` is permitted"):
m.next
with self.assertRaises(SyntaxError,
msg="`m.next = <...>` is only permitted inside an FSM"):
m.next = "FOO"

def test_auto_pop_ctrl(self):
m = Module()
with m.If(self.w1):