Skip to content

Commit

Permalink
hdl.rec: preserve shapes when constructing a layout.
Browse files Browse the repository at this point in the history
Preserve the original user-provided shape, while still checking 
its validity. This allows Enum decoders to work when specifying
record fields with Enums.

Fixes #393.
  • Loading branch information
shawnanastasio authored Jun 5, 2020
1 parent 9c80c32 commit 2f7c3bf
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 40 deletions.
5 changes: 3 additions & 2 deletions nmigen/hdl/rec.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def __init__(self, fields, *, src_loc_at=0):
.format(field))
if not isinstance(shape, Layout):
try:
shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
# Check provided shape by calling Shape.cast and checking for exception
Shape.cast(shape, src_loc_at=1 + src_loc_at)
except Exception as error:
raise TypeError("Field {!r} has invalid shape: should be castable to Shape "
"or a list of fields of a nested record"
Expand Down Expand Up @@ -134,7 +135,7 @@ def concat(a, b):
if isinstance(field_shape, Layout):
assert isinstance(field, Record) and field_shape == field.layout
else:
assert isinstance(field, Signal) and field_shape == field.shape()
assert isinstance(field, Signal) and Shape.cast(field_shape) == field.shape()
self.fields[field_name] = field
else:
if isinstance(field_shape, Layout):
Expand Down
31 changes: 20 additions & 11 deletions nmigen/test/test_hdl_rec.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ class UnsignedEnum(Enum):


class LayoutTestCase(FHDLTestCase):
def assertFieldEqual(self, field, expected):
(shape, dir) = field
shape = Shape.cast(shape)
self.assertEqual((shape, dir), expected)

def test_fields(self):
layout = Layout.cast([
("cyc", 1),
Expand All @@ -24,28 +29,28 @@ def test_fields(self):
])
])

self.assertEqual(layout["cyc"], ((1, False), DIR_NONE))
self.assertEqual(layout["data"], ((32, True), DIR_NONE))
self.assertEqual(layout["stb"], ((1, False), DIR_FANOUT))
self.assertEqual(layout["ack"], ((1, False), DIR_FANIN))
self.assertFieldEqual(layout["cyc"], ((1, False), DIR_NONE))
self.assertFieldEqual(layout["data"], ((32, True), DIR_NONE))
self.assertFieldEqual(layout["stb"], ((1, False), DIR_FANOUT))
self.assertFieldEqual(layout["ack"], ((1, False), DIR_FANIN))
sublayout = layout["info"][0]
self.assertEqual(layout["info"][1], DIR_NONE)
self.assertEqual(sublayout["a"], ((1, False), DIR_NONE))
self.assertEqual(sublayout["b"], ((1, False), DIR_NONE))
self.assertFieldEqual(sublayout["a"], ((1, False), DIR_NONE))
self.assertFieldEqual(sublayout["b"], ((1, False), DIR_NONE))

def test_enum_field(self):
layout = Layout.cast([
("enum", UnsignedEnum),
("enum_dir", UnsignedEnum, DIR_FANOUT),
])
self.assertEqual(layout["enum"], ((2, False), DIR_NONE))
self.assertEqual(layout["enum_dir"], ((2, False), DIR_FANOUT))
self.assertFieldEqual(layout["enum"], ((2, False), DIR_NONE))
self.assertFieldEqual(layout["enum_dir"], ((2, False), DIR_FANOUT))

def test_range_field(self):
layout = Layout.cast([
("range", range(0, 7)),
])
self.assertEqual(layout["range"], ((3, False), DIR_NONE))
self.assertFieldEqual(layout["range"], ((3, False), DIR_NONE))

def test_slice_tuple(self):
layout = Layout.cast([
Expand All @@ -60,9 +65,9 @@ def test_slice_tuple(self):
self.assertEqual(layout["a", "c"], expect)

def test_repr(self):
self.assertEqual(repr(Layout([("a", 1), ("b", signed(2))])),
self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", signed(2))])),
"Layout([('a', unsigned(1)), ('b', signed(2))])")
self.assertEqual(repr(Layout([("a", 1), ("b", [("c", signed(3))])])),
self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", [("c", signed(3))])])),
"Layout([('a', unsigned(1)), "
"('b', Layout([('c', signed(3))]))])")

Expand Down Expand Up @@ -201,6 +206,10 @@ def test_slice_tuple(self):
self.assertIs(r2.a, r1.a)
self.assertIs(r2.c, r1.c)

def test_enum_decoder(self):
r1 = Record([("a", UnsignedEnum)])
self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1")


class ConnectTestCase(FHDLTestCase):
def setUp_flat(self):
Expand Down
63 changes: 36 additions & 27 deletions nmigen/test/test_lib_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,103 +5,112 @@
from ..lib.io import *


class PinLayoutCombTestCase(FHDLTestCase):
class PinLayoutTestCase(FHDLTestCase):
def assertLayoutEqual(self, layout, expected):
casted_layout = {}
for name, (shape, dir) in layout.items():
casted_layout[name] = (Shape.cast(shape), dir)

self.assertEqual(casted_layout, expected)


class PinLayoutCombTestCase(PinLayoutTestCase):
def test_pin_layout_i(self):
layout_1 = pin_layout(1, dir="i")
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"i": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="i")
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"i": ((2, False), DIR_NONE),
})

def test_pin_layout_o(self):
layout_1 = pin_layout(1, dir="o")
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"o": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="o")
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"o": ((2, False), DIR_NONE),
})

def test_pin_layout_oe(self):
layout_1 = pin_layout(1, dir="oe")
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"o": ((1, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="oe")
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"o": ((2, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})

def test_pin_layout_io(self):
layout_1 = pin_layout(1, dir="io")
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"i": ((1, False), DIR_NONE),
"o": ((1, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="io")
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"i": ((2, False), DIR_NONE),
"o": ((2, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})


class PinLayoutSDRTestCase(FHDLTestCase):
class PinLayoutSDRTestCase(PinLayoutTestCase):
def test_pin_layout_i(self):
layout_1 = pin_layout(1, dir="i", xdr=1)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"i_clk": ((1, False), DIR_NONE),
"i": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="i", xdr=1)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"i_clk": ((1, False), DIR_NONE),
"i": ((2, False), DIR_NONE),
})

def test_pin_layout_o(self):
layout_1 = pin_layout(1, dir="o", xdr=1)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"o_clk": ((1, False), DIR_NONE),
"o": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="o", xdr=1)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"o_clk": ((1, False), DIR_NONE),
"o": ((2, False), DIR_NONE),
})

def test_pin_layout_oe(self):
layout_1 = pin_layout(1, dir="oe", xdr=1)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"o_clk": ((1, False), DIR_NONE),
"o": ((1, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="oe", xdr=1)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"o_clk": ((1, False), DIR_NONE),
"o": ((2, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})

def test_pin_layout_io(self):
layout_1 = pin_layout(1, dir="io", xdr=1)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"i_clk": ((1, False), DIR_NONE),
"i": ((1, False), DIR_NONE),
"o_clk": ((1, False), DIR_NONE),
Expand All @@ -110,7 +119,7 @@ def test_pin_layout_io(self):
})

layout_2 = pin_layout(2, dir="io", xdr=1)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"i_clk": ((1, False), DIR_NONE),
"i": ((2, False), DIR_NONE),
"o_clk": ((1, False), DIR_NONE),
Expand All @@ -119,48 +128,48 @@ def test_pin_layout_io(self):
})


class PinLayoutDDRTestCase(FHDLTestCase):
class PinLayoutDDRTestCase(PinLayoutTestCase):
def test_pin_layout_i(self):
layout_1 = pin_layout(1, dir="i", xdr=2)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"i_clk": ((1, False), DIR_NONE),
"i0": ((1, False), DIR_NONE),
"i1": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="i", xdr=2)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"i_clk": ((1, False), DIR_NONE),
"i0": ((2, False), DIR_NONE),
"i1": ((2, False), DIR_NONE),
})

def test_pin_layout_o(self):
layout_1 = pin_layout(1, dir="o", xdr=2)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"o_clk": ((1, False), DIR_NONE),
"o0": ((1, False), DIR_NONE),
"o1": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="o", xdr=2)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"o_clk": ((1, False), DIR_NONE),
"o0": ((2, False), DIR_NONE),
"o1": ((2, False), DIR_NONE),
})

def test_pin_layout_oe(self):
layout_1 = pin_layout(1, dir="oe", xdr=2)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"o_clk": ((1, False), DIR_NONE),
"o0": ((1, False), DIR_NONE),
"o1": ((1, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="oe", xdr=2)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"o_clk": ((1, False), DIR_NONE),
"o0": ((2, False), DIR_NONE),
"o1": ((2, False), DIR_NONE),
Expand All @@ -169,7 +178,7 @@ def test_pin_layout_oe(self):

def test_pin_layout_io(self):
layout_1 = pin_layout(1, dir="io", xdr=2)
self.assertEqual(layout_1.fields, {
self.assertLayoutEqual(layout_1.fields, {
"i_clk": ((1, False), DIR_NONE),
"i0": ((1, False), DIR_NONE),
"i1": ((1, False), DIR_NONE),
Expand All @@ -180,7 +189,7 @@ def test_pin_layout_io(self):
})

layout_2 = pin_layout(2, dir="io", xdr=2)
self.assertEqual(layout_2.fields, {
self.assertLayoutEqual(layout_2.fields, {
"i_clk": ((1, False), DIR_NONE),
"i0": ((2, False), DIR_NONE),
"i1": ((2, False), DIR_NONE),
Expand Down

0 comments on commit 2f7c3bf

Please sign in to comment.