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: 32310aecad7c
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: e8f79c5539a5
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Sep 14, 2019

  1. hdl.dsl: improve error messages for Case().

    whitequark committed Sep 14, 2019
    Copy the full SHA
    f292a19 View commit details
  2. hdl.ast: add Value.matches(), accepting same language as Case().

    Fixes #202.
    whitequark committed Sep 14, 2019
    Copy the full SHA
    e8f79c5 View commit details
Showing with 129 additions and 21 deletions.
  1. +54 −4 nmigen/hdl/ast.py
  2. +21 −14 nmigen/hdl/dsl.py
  3. +35 −0 nmigen/test/test_hdl_ast.py
  4. +19 −3 nmigen/test/test_hdl_dsl.py
58 changes: 54 additions & 4 deletions nmigen/hdl/ast.py
Original file line number Diff line number Diff line change
@@ -191,9 +191,9 @@ def bit_select(self, offset, width):
Parameters
----------
offset : Value, in
index of first selected bit
Index of first selected bit.
width : int
number of selected bits
Number of selected bits.
Returns
-------
@@ -211,9 +211,9 @@ def word_select(self, offset, width):
Parameters
----------
offset : Value, in
index of first selected word
Index of first selected word.
width : int
number of selected bits
Number of selected bits.
Returns
-------
@@ -222,6 +222,56 @@ def word_select(self, offset, width):
"""
return Part(self, offset, width, stride=width, src_loc_at=1)

def matches(self, *patterns):
"""Pattern matching.
Matches against a set of patterns, which may be integers or bit strings, recognizing
the same grammar as ``Case()``.
Parameters
----------
patterns : int or str
Patterns to match against.
Returns
-------
Value, out
``1`` if any pattern matches the value, ``0`` otherwise.
"""
matches = []
for pattern in patterns:
if isinstance(pattern, str) and any(bit not in "01-" for bit in pattern):
raise SyntaxError("Match pattern '{}' must consist of 0, 1, and - (don't care) "
"bits"
.format(pattern))
if isinstance(pattern, str) and len(pattern) != len(self):
raise SyntaxError("Match pattern '{}' must have the same width as match value "
"(which is {})"
.format(pattern, len(self)))
if not isinstance(pattern, (int, str)):
raise SyntaxError("Match pattern must be an integer or a string, not {}"
.format(pattern))
if isinstance(pattern, int) and bits_for(pattern) > len(self):
warnings.warn("Match pattern '{:b}' is wider than match value "
"(which has width {}); comparison will never be true"
.format(pattern, len(self)),
SyntaxWarning, stacklevel=3)
continue
if isinstance(pattern, int):
matches.append(self == pattern)
elif isinstance(pattern, str):
mask = int(pattern.replace("0", "1").replace("-", "0"), 2)
pattern = int(pattern.replace("-", "0"), 2)
matches.append((self & mask) == pattern)
else:
assert False
if not matches:
return Const(0)
elif len(matches) == 1:
return matches[0]
else:
return Cat(*matches).any()

def eq(self, value):
"""Assignment.
35 changes: 21 additions & 14 deletions nmigen/hdl/dsl.py
Original file line number Diff line number Diff line change
@@ -258,22 +258,29 @@ def Switch(self, test):
self._pop_ctrl()

@contextmanager
def Case(self, *values):
def Case(self, *patterns):
self._check_context("Case", context="Switch")
src_loc = tracer.get_src_loc(src_loc_at=1)
switch_data = self._get_ctrl("Switch")
new_values = ()
for value in values:
if isinstance(value, str) and len(value) != len(switch_data["test"]):
raise SyntaxError("Case value '{}' must have the same width as test (which is {})"
.format(value, len(switch_data["test"])))
if isinstance(value, int) and bits_for(value) > len(switch_data["test"]):
warnings.warn("Case value '{:b}' is wider than test (which has width {}); "
"comparison will never be true"
.format(value, len(switch_data["test"])),
new_patterns = ()
for pattern in patterns:
if isinstance(pattern, str) and any(bit not in "01-" for bit in pattern):
raise SyntaxError("Case pattern '{}' must consist of 0, 1, and - (don't care) bits"
.format(pattern))
if isinstance(pattern, str) and len(pattern) != len(switch_data["test"]):
raise SyntaxError("Case pattern '{}' must have the same width as switch value "
"(which is {})"
.format(pattern, len(switch_data["test"])))
if not isinstance(pattern, (int, str)):
raise SyntaxError("Case pattern must be an integer or a string, not {}"
.format(pattern))
if isinstance(pattern, int) and bits_for(pattern) > len(switch_data["test"]):
warnings.warn("Case pattern '{:b}' is wider than switch value "
"(which has width {}); comparison will never be true"
.format(pattern, len(switch_data["test"])),
SyntaxWarning, stacklevel=3)
continue
new_values = (*new_values, value)
new_patterns = (*new_patterns, pattern)
try:
_outer_case, self._statements = self._statements, []
self._ctrl_context = None
@@ -282,9 +289,9 @@ def Case(self, *values):
# If none of the provided cases can possibly be true, omit this branch completely.
# This needs to be differentiated from no cases being provided in the first place,
# which means the branch will always match.
if not (values and not new_values):
switch_data["cases"][new_values] = self._statements
switch_data["case_src_locs"][new_values] = src_loc
if not (patterns and not new_patterns):
switch_data["cases"][new_patterns] = self._statements
switch_data["case_src_locs"][new_patterns] = src_loc
finally:
self._ctrl_context = "Switch"
self._statements = _outer_case
35 changes: 35 additions & 0 deletions nmigen/test/test_hdl_ast.py
Original file line number Diff line number Diff line change
@@ -263,6 +263,41 @@ def test_xor(self):
v = Const(0b101).xor()
self.assertEqual(repr(v), "(r^ (const 3'd5))")

def test_matches(self):
s = Signal(4)
self.assertRepr(s.matches(), "(const 1'd0)")
self.assertRepr(s.matches(1), """
(== (sig s) (const 1'd1))
""")
self.assertRepr(s.matches(0, 1), """
(r| (cat (== (sig s) (const 1'd0)) (== (sig s) (const 1'd1))))
""")
self.assertRepr(s.matches("10--"), """
(== (& (sig s) (const 4'd12)) (const 4'd8))
""")

def test_matches_width_wrong(self):
s = Signal(4)
with self.assertRaises(SyntaxError,
msg="Match pattern '--' must have the same width as match value (which is 4)"):
s.matches("--")
with self.assertWarns(SyntaxWarning,
msg="Match pattern '10110' is wider than match value (which has width 4); "
"comparison will never be true"):
s.matches(0b10110)

def test_matches_bits_wrong(self):
s = Signal(4)
with self.assertRaises(SyntaxError,
msg="Match pattern 'abc' must consist of 0, 1, and - (don't care) bits"):
s.matches("abc")

def test_matches_pattern_wrong(self):
s = Signal(4)
with self.assertRaises(SyntaxError,
msg="Match pattern must be an integer or a string, not 1.0"):
s.matches(1.0)

def test_hash(self):
with self.assertRaises(TypeError):
hash(Const(0) + Const(0))
22 changes: 19 additions & 3 deletions nmigen/test/test_hdl_dsl.py
Original file line number Diff line number Diff line change
@@ -359,12 +359,12 @@ def test_Case_width_wrong(self):
m = Module()
with m.Switch(self.w1):
with self.assertRaises(SyntaxError,
msg="Case value '--' must have the same width as test (which is 4)"):
msg="Case pattern '--' must have the same width as switch value (which is 4)"):
with m.Case("--"):
pass
with self.assertWarns(SyntaxWarning,
msg="Case value '10110' is wider than test (which has width 4); comparison "
"will never be true"):
msg="Case pattern '10110' is wider than switch value (which has width 4); "
"comparison will never be true"):
with m.Case(0b10110):
pass
self.assertRepr(m._statements, """
@@ -373,6 +373,22 @@ def test_Case_width_wrong(self):
)
""")

def test_Case_bits_wrong(self):
m = Module()
with m.Switch(self.w1):
with self.assertRaises(SyntaxError,
msg="Case pattern 'abc' must consist of 0, 1, and - (don't care) bits"):
with m.Case("abc"):
pass

def test_Case_pattern_wrong(self):
m = Module()
with m.Switch(self.w1):
with self.assertRaises(SyntaxError,
msg="Case pattern must be an integer or a string, not 1.0"):
with m.Case(1.0):
pass

def test_Case_outside_Switch_wrong(self):
m = Module()
with self.assertRaises(SyntaxError,