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: 6fae06aea9b7
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: 185abb492d4d
Choose a head ref
  • 3 commits
  • 5 files changed
  • 1 contributor

Commits on Jun 3, 2019

  1. hdl.rec: unbreak hasattr(rec, ...).

    hasattr() requires that AttributeError be raised. Change __getitem__
    to raise AttributeError, too, since it is fundamentally just sugar
    for getattr().
    whitequark committed Jun 3, 2019
    Copy the full SHA
    a1940c5 View commit details
  2. Copy the full SHA
    b42043f View commit details
  3. Copy the full SHA
    185abb4 View commit details
Showing with 119 additions and 36 deletions.
  1. +2 −2 nmigen/hdl/rec.py
  2. +9 −1 nmigen/lib/io.py
  3. +8 −2 nmigen/test/test_hdl_rec.py
  4. +20 −0 nmigen/test/test_lib_io.py
  5. +80 −31 nmigen/vendor/fpga/lattice_ice40.py
4 changes: 2 additions & 2 deletions nmigen/hdl/rec.py
Original file line number Diff line number Diff line change
@@ -109,8 +109,8 @@ def __getitem__(self, item):
reference = "Unnamed record"
else:
reference = "Record '{}'".format(self.name)
raise NameError("{} does not have a field '{}'. Did you mean one of: {}?"
.format(reference, item, ", ".join(self.fields))) from None
raise AttributeError("{} does not have a field '{}'. Did you mean one of: {}?"
.format(reference, item, ", ".join(self.fields))) from None
else:
return super().__getitem__(item)

10 changes: 9 additions & 1 deletion nmigen/lib/io.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
__all__ = ["pin_layout", "Pin"]


def pin_layout(width, dir, xdr=1):
def pin_layout(width, dir, xdr=0):
"""
Layout of the platform interface of a pin or several pins, which may be used inside
user-defined records.
@@ -24,12 +24,16 @@ def pin_layout(width, dir, xdr=1):

fields = []
if dir in ("i", "io"):
if xdr > 0:
fields.append(("i_clk", 1))
if xdr in (0, 1):
fields.append(("i", width))
else:
for n in range(xdr):
fields.append(("i{}".format(n), width))
if dir in ("o", "oe", "io"):
if xdr > 0:
fields.append(("o_clk", 1))
if xdr in (0, 1):
fields.append(("o", width))
else:
@@ -72,12 +76,16 @@ class Pin(Record):
Attributes
----------
i_clk:
I/O buffer input clock. Synchronizes `i*`. Present if ``xdr`` is nonzero.
i : Signal, out
I/O buffer input, without gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is
equal to 0 or 1.
i0, i1, ... : Signal, out
I/O buffer inputs, with gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is
greater than 1.
o_clk:
I/O buffer output clock. Synchronizes `o*`, including `oe`. Present if ``xdr`` is nonzero.
o : Signal, in
I/O buffer output, without gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is
equal to 0 or 1.
10 changes: 8 additions & 2 deletions nmigen/test/test_hdl_rec.py
Original file line number Diff line number Diff line change
@@ -71,6 +71,9 @@ def test_basic(self):
self.assertEqual(r.stb.name, "r__stb")
self.assertEqual(r["stb"].name, "r__stb")

self.assertTrue(hasattr(r, "stb"))
self.assertFalse(hasattr(r, "xxx"))

def test_unnamed(self):
r = [Record([
("stb", 1)
@@ -93,7 +96,10 @@ def test_wrong_field(self):
("stb", 1),
("ack", 1),
])
with self.assertRaises(NameError,
with self.assertRaises(AttributeError,
msg="Record 'r' does not have a field 'en'. Did you mean one of: stb, ack?"):
r["en"]
with self.assertRaises(AttributeError,
msg="Record 'r' does not have a field 'en'. Did you mean one of: stb, ack?"):
r.en

@@ -102,7 +108,7 @@ def test_wrong_field_unnamed(self):
("stb", 1),
("ack", 1),
])][0]
with self.assertRaises(NameError,
with self.assertRaises(AttributeError,
msg="Unnamed record does not have a field 'en'. Did you mean one of: stb, ack?"):
r.en

20 changes: 20 additions & 0 deletions nmigen/test/test_lib_io.py
Original file line number Diff line number Diff line change
@@ -60,49 +60,59 @@ class PinLayoutSDRTestCase(FHDLTestCase):
def test_pin_layout_i(self):
layout_1 = pin_layout(1, dir="i", xdr=1)
self.assertEqual(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, {
"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, {
"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, {
"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, {
"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, {
"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, {
"i_clk": ((1, False), DIR_NONE),
"i": ((1, False), DIR_NONE),
"o_clk": ((1, False), DIR_NONE),
"o": ((1, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})

layout_2 = pin_layout(2, dir="io", xdr=1)
self.assertEqual(layout_2.fields, {
"i_clk": ((1, False), DIR_NONE),
"i": ((2, False), DIR_NONE),
"o_clk": ((1, False), DIR_NONE),
"o": ((2, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})
@@ -112,39 +122,45 @@ class PinLayoutDDRTestCase(FHDLTestCase):
def test_pin_layout_i(self):
layout_1 = pin_layout(1, dir="i", xdr=2)
self.assertEqual(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, {
"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, {
"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, {
"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, {
"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, {
"o_clk": ((1, False), DIR_NONE),
"o0": ((2, False), DIR_NONE),
"o1": ((2, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
@@ -153,17 +169,21 @@ 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, {
"i_clk": ((1, False), DIR_NONE),
"i0": ((1, False), DIR_NONE),
"i1": ((1, False), DIR_NONE),
"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="io", xdr=2)
self.assertEqual(layout_2.fields, {
"i_clk": ((1, False), DIR_NONE),
"i0": ((2, False), DIR_NONE),
"i1": ((2, False), DIR_NONE),
"o_clk": ((1, False), DIR_NONE),
"o0": ((2, False), DIR_NONE),
"o1": ((2, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
111 changes: 80 additions & 31 deletions nmigen/vendor/fpga/lattice_ice40.py
Original file line number Diff line number Diff line change
@@ -108,53 +108,102 @@ class LatticeICE40Platform(TemplatedPlatform):
"""
]

def _get_io_buffer(self, port, extras, fn):
def _get_dff(self, clk, d, q):
return Instance("$dff",
p_CLK_POLARITY=0,
p_WIDTH=len(d),
i_CLK=clk,
i_D=d,
o_Q=q)

def _get_io_buffer(self, pin, port, extras):
m = Module()

if "i" in pin.dir and pin.xdr == 2:
i0_ff = Signal.like(pin.i0, name="{}_ff".format(pin.i0.name))
i1_ff = Signal.like(pin.i1, name="{}_ff".format(pin.i1.name))
m.submodules += self._get_dff(pin.i_clk, i0_ff, pin.i0)
m.submodules += self._get_dff(pin.i_clk, i1_ff, pin.i1)
if "o" in pin.dir and pin.xdr == 2:
o1_ff = Signal.like(pin.o1, name="{}_ff".format(pin.o1.name))
m.submodules += self._get_dff(pin.o_clk, pin.o1, o1_ff)

for bit in range(len(port)):
m.submodules += Instance("SB_IO",
io_args = [
("io", "PACKAGE_PIN", port[bit]),
*fn(bit),
*(("p", key, value) for key, value in extras.items()))
*(("p", key, value) for key, value in extras.items()),
]

if "i" not in pin.dir:
i_type = 0b00 # PIN_NO_INPUT aka PIN_INPUT_REGISTERED
elif pin.xdr == 0:
i_type = 0b01 # PIN_INPUT
elif pin.xdr > 0:
i_type = 0b00 # PIN_INPUT_REGISTERED
if "o" not in pin.dir:
o_type = 0b0000 # PIN_NO_OUTPUT
elif pin.xdr == 0 and pin.dir == "o":
o_type = 0b0110 # PIN_OUTPUT
elif pin.xdr == 0:
o_type = 0b1010 # PIN_OUTPUT_TRISTATE
elif pin.xdr == 1 and pin.dir == "o":
o_type = 0b0101 # PIN_OUTPUT_REGISTERED
elif pin.xdr == 1:
o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
elif pin.xdr == 2 and pin.dir == "o":
o_type = 0b0100 # PIN_OUTPUT_DDR
elif pin.xdr == 2:
o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
io_args.append(("p", "PIN_TYPE", (o_type << 2) | i_type))

if hasattr(pin, "i_clk"):
io_args.append(("i", "INPUT_CLK", pin.i_clk))
if hasattr(pin, "o_clk"):
io_args.append(("i", "OUTPUT_CLK", pin.o_clk))

if "i" in pin.dir:
if pin.xdr < 2:
io_args.append(("o", "D_IN_0", pin.i[bit]))
if pin.xdr == 2:
# Re-register both inputs before they enter fabric. This increases hold time
# to an entire cycle, and adds one cycle of latency.
io_args.append(("o", "D_IN_0", i0_ff))
io_args.append(("o", "D_IN_1", i1_ff))
if "o" in pin.dir:
if pin.xdr < 2:
io_args.append(("i", "D_OUT_0", pin.o[bit]))
if pin.xdr == 2:
# Re-register negedge output after it leaves fabric. This increases setup time
# to an entire cycle, and doesn't add latency.
io_args.append(("i", "D_OUT_0", pin.o0[bit]))
io_args.append(("i", "D_OUT_1", o1_ff))

if pin.dir in ("oe", "io"):
io_args.append(("i", "OUTPUT_ENABLE", pin.oe))

m.submodules += Instance("SB_IO", *io_args)

return m

def get_input(self, pin, port, extras):
self._check_feature("single-ended input", pin, extras,
valid_xdrs=(0,), valid_extras=True)
return self._get_io_buffer(port, extras, lambda bit: [
# PIN_NO_OUTPUT|PIN_INPUT
("p", "PIN_TYPE", 0b0000_01),
("o", "D_IN_0", pin.i[bit]),
])
valid_xdrs=(0, 1, 2), valid_extras=True)
return self._get_io_buffer(pin, port, extras)

def get_output(self, pin, port, extras):
self._check_feature("single-ended output", pin, extras,
valid_xdrs=(0,), valid_extras=True)
return self._get_io_buffer(port, extras, lambda bit: [
# PIN_OUTPUT|PIN_INPUT_REGISTERED
("p", "PIN_TYPE", 0b0110_00),
("i", "D_OUT_0", pin.o[bit]),
])
valid_xdrs=(0, 1, 2), valid_extras=True)
return self._get_io_buffer(pin, port, extras)

def get_tristate(self, pin, port, extras):
self._check_feature("single-ended tristate", pin, extras,
valid_xdrs=(0,), valid_extras=True)
return self._get_io_buffer(port, extras, lambda bit: [
# PIN_OUTPUT_TRISTATE|PIN_INPUT_REGISTERED
("p", "PIN_TYPE", 0b1010_00),
("i", "D_OUT_0", pin.o[bit]),
("i", "OUTPUT_ENABLE", pin.oe),
])
valid_xdrs=(0, 1, 2), valid_extras=True)
return self._get_io_buffer(pin, port, extras)

def get_input_output(self, pin, port, extras):
self._check_feature("single-ended input/output", pin, extras,
valid_xdrs=(0,), valid_extras=True)
return self._get_io_buffer(port, extras, lambda bit: [
# PIN_OUTPUT_TRISTATE|PIN_INPUT
("p", "PIN_TYPE", 0b1010_01),
("o", "D_IN_0", pin.i[bit]),
("i", "D_OUT_0", pin.o[bit]),
("i", "OUTPUT_ENABLE", pin.oe),
])
valid_xdrs=(0, 1, 2), valid_extras=True)
return self._get_io_buffer(pin, port, extras)


class IceStormProgrammerMixin: