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: f417725b10de
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: 9ba2efd86b52
Choose a head ref
  • 4 commits
  • 6 files changed
  • 1 contributor

Commits on Jun 3, 2019

  1. Copy the full SHA
    3327dea View commit details
  2. Copy the full SHA
    c30617f View commit details
  3. build.res: allow requesting raw ports, with dir="-".

    This provides an escape hatch for the case where the nMigen platform
    code is not flexible enough, and a IO buffer primitive needs to be
    instantiated directly.
    whitequark committed Jun 3, 2019
    Copy the full SHA
    cd6488c View commit details
  4. build.{res,plat}: use xdr=0 as default, not xdr=1.

    The previous behavior was semantically incorrect.
    whitequark committed Jun 3, 2019
    Copy the full SHA
    9ba2efd View commit details
Showing with 116 additions and 42 deletions.
  1. +3 −3 nmigen/build/plat.py
  2. +32 −25 nmigen/build/res.py
  3. +9 −8 nmigen/lib/io.py
  4. +25 −4 nmigen/test/test_build_res.py
  5. +40 −1 nmigen/test/test_lib_io.py
  6. +7 −1 nmigen/vendor/fpga/lattice_ice40.py
6 changes: 3 additions & 3 deletions nmigen/build/plat.py
Original file line number Diff line number Diff line change
@@ -169,23 +169,23 @@ def _check_feature(self, feature, pin, extras, valid_xdrs, valid_extras):

def get_input(self, pin, port, extras):
self._check_feature("single-ended input", pin, extras,
valid_xdrs=(1,), valid_extras=None)
valid_xdrs=(0,), valid_extras=None)

m = Module()
m.d.comb += pin.i.eq(port)
return m

def get_output(self, pin, port, extras):
self._check_feature("single-ended output", pin, extras,
valid_xdrs=(1,), valid_extras=None)
valid_xdrs=(0,), valid_extras=None)

m = Module()
m.d.comb += port.eq(pin.o)
return m

def get_tristate(self, pin, port, extras):
self._check_feature("single-ended tristate", pin, extras,
valid_xdrs=(1,), valid_extras=None)
valid_xdrs=(0,), valid_extras=None)

m = Module()
m.submodules += Instance("$tribuf",
57 changes: 32 additions & 25 deletions nmigen/build/res.py
Original file line number Diff line number Diff line change
@@ -83,17 +83,18 @@ def merge_options(subsignal, dir, xdr):
if dir is None:
dir = subsignal.io[0].dir
if xdr is None:
xdr = 1
if dir not in ("i", "o", "io"):
raise TypeError("Direction must be one of \"i\", \"o\" or \"io\", not {!r}"
xdr = 0
if dir not in ("i", "o", "io", "-"):
raise TypeError("Direction must be one of \"i\", \"o\", \"io\", or \"-\", "
"not {!r}"
.format(dir))
if subsignal.io[0].dir != "io" and dir != subsignal.io[0].dir:
if dir != subsignal.io[0].dir and not (subsignal.io[0].dir == "io" or dir == "-"):
raise ValueError("Direction of {!r} cannot be changed from \"{}\" to \"{}\"; "
"direction can be changed from \"io\" to \"i\" or from \"io\""
"to \"o\""
"direction can be changed from \"io\" to \"i\", from \"io\""
"to \"o\", or from anything to \"-\""
.format(subsignal.io[0], subsignal.io[0].dir, dir))
if not isinstance(xdr, int) or xdr < 1:
raise ValueError("Data rate of {!r} must be a positive integer, not {!r}"
if not isinstance(xdr, int) or xdr < 0:
raise ValueError("Data rate of {!r} must be a non-negative integer, not {!r}"
.format(subsignal.io[0], xdr))
return dir, xdr

@@ -109,14 +110,19 @@ def resolve(subsignal, dir, xdr, name):

elif isinstance(subsignal.io[0], (Pins, DiffPairs)):
phys = subsignal.io[0]
pin = Pin(len(phys), dir, xdr, name=name)
if isinstance(phys, Pins):
port = Signal(pin.width, name="{}__io".format(pin.name))
port = Record([("io", len(phys))], name=name)
if isinstance(phys, DiffPairs):
port = (Signal(pin.width, name="{}__p".format(pin.name)),
Signal(pin.width, name="{}__n".format(pin.name)))
self._ports.append((subsignal, pin, port))
return pin
port = Record([("p", len(phys)),
("n", len(phys))], name=name)
if dir == "-":
self._ports.append((subsignal, None, port))
return port
else:
pin = Pin(len(phys), dir, xdr, name=name)
self._ports.append((subsignal, pin, port))
return pin

else:
assert False # :nocov:

@@ -128,38 +134,39 @@ def resolve(subsignal, dir, xdr, name):

def iter_single_ended_pins(self):
for resource, pin, port in self._ports:
if pin is None:
continue
if isinstance(resource.io[0], Pins):
yield pin, port, resource.extras
yield pin, port.io, resource.extras

def iter_differential_pins(self):
for resource, pin, port in self._ports:
if pin is None:
continue
if isinstance(resource.io[0], DiffPairs):
p_port, n_port = port
yield pin, p_port, n_port, resource.extras
yield pin, port.p, port.n, resource.extras

def iter_ports(self):
for resource, pin, port in self._ports:
if isinstance(resource.io[0], Pins):
yield port
yield port.io
elif isinstance(resource.io[0], DiffPairs):
p_port, n_port = port
yield p_port
yield n_port
yield port.p
yield port.n
else:
assert False

def iter_port_constraints(self, diff_pins="pn"):
for resource, pin, port in self._ports:
if isinstance(resource.io[0], Pins):
yield port.name, resource.io[0].names, resource.extras
yield port.io.name, resource.io[0].names, resource.extras
elif isinstance(resource.io[0], DiffPairs):
p_port, n_port = port
# On some FPGAs like iCE40, only one pin out of two in a differential pair may be
# constrained. The other has to be completely disconnected.
if "p" in diff_pins:
yield p_port.name, resource.io[0].p.names, resource.extras
yield port.p.name, resource.io[0].p.names, resource.extras
if "n" in diff_pins:
yield n_port.name, resource.io[0].n.names, resource.extras
yield port.n.name, resource.io[0].n.names, resource.extras
else:
assert False

17 changes: 9 additions & 8 deletions nmigen/lib/io.py
Original file line number Diff line number Diff line change
@@ -18,19 +18,19 @@ def pin_layout(width, dir, xdr=1):
if dir not in ("i", "o", "io"):
raise TypeError("Direction must be one of \"i\", \"o\" or \"io\", not '{!r}'"""
.format(dir))
if not isinstance(xdr, int) or xdr < 1:
raise TypeError("Gearing ratio must be a positive integer, not '{!r}'"
if not isinstance(xdr, int) or xdr < 0:
raise TypeError("Gearing ratio must be a non-negative integer, not '{!r}'"
.format(xdr))

fields = []
if dir in ("i", "io"):
if xdr == 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", "io"):
if xdr == 1:
if xdr in (0, 1):
fields.append(("o", width))
else:
for n in range(xdr):
@@ -60,7 +60,8 @@ class Pin(Record):
is specified, both the ``i``/``iN`` and ``o``/``oN`` signals are present, and an ``oe``
signal is present.
xdr : int
Gearbox ratio. If equal to 1, the I/O buffer is SDR, and only ``i``/``o`` signals are
Gearbox ratio. If equal to 0, the I/O buffer is combinatorial, and only ``i``/``o``
signals are present. If equal to 1, the I/O buffer is SDR, and only ``i``/``o`` signals are
present. If greater than 1, the I/O buffer includes a gearbox, and ``iN``/``oN`` signals
are present instead, where ``N in range(0, N)``. For example, if ``xdr=2``, the I/O buffer
is DDR; the signal ``i0`` reflects the value at the rising edge, and the signal ``i1``
@@ -72,21 +73,21 @@ class Pin(Record):
----------
i : Signal, out
I/O buffer input, without gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is
equal to 1.
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 : Signal, in
I/O buffer output, without gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is
equal to 1.
equal to 0 or 1.
o0, o1, ... : Signal, in
I/O buffer outputs, with gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is
greater than 1.
oe : Signal, in
I/O buffer output enable. Present if ``dir="io"``. Buffers generally cannot change
direction more than once per cycle, so at most one output enable signal is present.
"""
def __init__(self, width, dir, xdr=1, name=None):
def __init__(self, width, dir, xdr=0, name=None):
self.width = width
self.dir = dir
self.xdr = xdr
29 changes: 25 additions & 4 deletions nmigen/test/test_build_res.py
Original file line number Diff line number Diff line change
@@ -122,6 +122,26 @@ def test_request_diffpairs(self):
("clk100_0__n", ["H2"], {}),
])

def test_request_raw(self):
clk50 = self.cm.request("clk50", 0, dir="-")
self.assertIsInstance(clk50, Record)
self.assertIsInstance(clk50.io, Signal)

ports = list(self.cm.iter_ports())
self.assertEqual(len(ports), 1)
self.assertIs(ports[0], clk50.io)

def test_request_raw_diffpairs(self):
clk100 = self.cm.request("clk100", 0, dir="-")
self.assertIsInstance(clk100, Record)
self.assertIsInstance(clk100.p, Signal)
self.assertIsInstance(clk100.n, Signal)

ports = list(self.cm.iter_ports())
self.assertEqual(len(ports), 2)
self.assertIs(ports[0], clk100.p)
self.assertIs(ports[1], clk100.n)

def test_add_clock(self):
self.cm.add_clock("clk100", 0, 10e6)
self.assertEqual(self.cm.clocks["clk100", 0], 10e6)
@@ -177,13 +197,14 @@ def test_wrong_request_duplicate(self):

def test_wrong_request_with_dir(self):
with self.assertRaises(TypeError,
msg="Direction must be one of \"i\", \"o\" or \"io\", not 'wrong'"):
msg="Direction must be one of \"i\", \"o\", \"io\", or \"-\", not 'wrong'"):
user_led = self.cm.request("user_led", 0, dir="wrong")

def test_wrong_request_with_dir_io(self):
with self.assertRaises(ValueError,
msg="Direction of (pins o A0) cannot be changed from \"o\" to \"i\"; direction "
"can be changed from \"io\" to \"i\" or from \"io\"to \"o\""):
"can be changed from \"io\" to \"i\", from \"io\"to \"o\", or from anything "
"to \"-\""):
user_led = self.cm.request("user_led", 0, dir="i")

def test_wrong_request_with_dir_dict(self):
@@ -194,8 +215,8 @@ def test_wrong_request_with_dir_dict(self):

def test_wrong_request_with_wrong_xdr(self):
with self.assertRaises(ValueError,
msg="Data rate of (pins o A0) must be a positive integer, not 0"):
user_led = self.cm.request("user_led", 0, xdr=0)
msg="Data rate of (pins o A0) must be a non-negative integer, not -1"):
user_led = self.cm.request("user_led", 0, xdr=-1)

def test_wrong_request_with_xdr_dict(self):
with self.assertRaises(TypeError,
41 changes: 40 additions & 1 deletion nmigen/test/test_lib_io.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
from ..lib.io import *


class PinLayoutSDRTestCase(FHDLTestCase):
class PinLayoutCombTestCase(FHDLTestCase):
def test_pin_layout_i(self):
layout_1 = pin_layout(1, dir="i")
self.assertEqual(layout_1.fields, {
@@ -43,6 +43,45 @@ def test_pin_layout_io(self):
})


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

layout_2 = pin_layout(2, dir="i", xdr=1)
self.assertEqual(layout_2.fields, {
"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": ((1, False), DIR_NONE),
})

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

def test_pin_layout_io(self):
layout_1 = pin_layout(1, dir="io", xdr=1)
self.assertEqual(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", xdr=1)
self.assertEqual(layout_2.fields, {
"i": ((2, False), DIR_NONE),
"o": ((2, False), DIR_NONE),
"oe": ((1, False), DIR_NONE),
})


class PinLayoutDDRTestCase(FHDLTestCase):
def test_pin_layout_i(self):
layout_1 = pin_layout(1, dir="i", xdr=2)
8 changes: 7 additions & 1 deletion nmigen/vendor/fpga/lattice_ice40.py
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ class LatticeICE40Platform(TemplatedPlatform):
{% if file.endswith(".v") -%}
read_verilog {{get_override("read_opts")|join(" ")}} {{file}}
{% elif file.endswith(".sv") -%}
read_verilog {{get_override("read_opts")|join(" ")}} {{file}}
read_verilog -sv {{get_override("read_opts")|join(" ")}} {{file}}
{% endif %}
{% endfor %}
read_ilang {{name}}.il
@@ -117,20 +117,26 @@ def _get_io_buffer(self, port, extras, fn):
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]),
])

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]),
])

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),