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

Commits on Jun 3, 2019

  1. build.dsl: add support for connectors.

    whitequark committed Jun 3, 2019
    Copy the full SHA
    a013eb1 View commit details
  2. build.{plat,res}: add support for connectors.

    Fixes #77.
    whitequark committed Jun 3, 2019
    Copy the full SHA
    ed64880 View commit details
Showing with 255 additions and 52 deletions.
  1. +1 −1 nmigen/build/__init__.py
  2. +67 −7 nmigen/build/dsl.py
  3. +4 −3 nmigen/build/plat.py
  4. +44 −27 nmigen/build/res.py
  5. +92 −0 nmigen/test/test_build_dsl.py
  6. +35 −2 nmigen/test/test_build_res.py
  7. +12 −12 nmigen/vendor/fpga/lattice_ice40.py
2 changes: 1 addition & 1 deletion nmigen/build/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .dsl import Pins, DiffPairs, Subsignal, Resource
from .dsl import Pins, DiffPairs, Subsignal, Resource, Connector
from .plat import Platform, TemplatedPlatform
74 changes: 67 additions & 7 deletions nmigen/build/dsl.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
__all__ = ["Pins", "DiffPairs", "Subsignal", "Resource"]
from collections import OrderedDict


__all__ = ["Pins", "DiffPairs", "Subsignal", "Resource", "Connector"]


class Pins:
def __init__(self, names, dir="io"):
def __init__(self, names, *, dir="io", conn=None):
if not isinstance(names, str):
raise TypeError("Names must be a whitespace-separated string, not {!r}"
.format(names))
self.names = names.split()
names = names.split()

if conn is not None:
conn_name, conn_number = conn
if not (isinstance(conn_name, str) and isinstance(conn_number, int)):
raise TypeError("Connector must be None or a pair of string and integer, not {!r}"
.format(conn))
names = ["{}_{}:{}".format(conn_name, conn_number, name) for name in names]

if dir not in ("i", "o", "io"):
raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not {!r}"
.format(dir))
self.dir = dir

self.names = names
self.dir = dir

def __len__(self):
return len(self.names)

def __iter__(self):
return iter(self.names)

def map_names(self, mapping, resource):
for name in self.names:
while ":" in name:
if name not in mapping:
raise NameError("Resource {!r} refers to nonexistent connector pin {}"
.format(resource, name))
name = mapping[name]
yield name

def __repr__(self):
return "(pins {} {})".format(self.dir, " ".join(self.names))


class DiffPairs:
def __init__(self, p, n, dir="io"):
self.p = Pins(p, dir=dir)
self.n = Pins(n, dir=dir)
def __init__(self, p, n, *, dir="io", conn=None):
self.p = Pins(p, dir=dir, conn=conn)
self.n = Pins(n, dir=dir, conn=conn)

if len(self.p.names) != len(self.n.names):
raise TypeError("Positive and negative pins must have the same width, but {!r} "
@@ -105,3 +126,42 @@ def __repr__(self):
" ".join(map(repr, self.io)),
" ".join("{}={}".format(k, v)
for k, v in self.extras.items()))


class Connector:
def __init__(self, name, number, io):
self.name = name
self.number = number
self.mapping = OrderedDict()

if isinstance(io, dict):
for conn_pin, plat_pin in io.items():
if not isinstance(conn_pin, str):
raise TypeError("Connector pin name must be a string, not {!r}"
.format(conn_pin))
if not isinstance(plat_pin, str):
raise TypeError("Platform pin name must be a string, not {!r}"
.format(plat_pin))
self.mapping[conn_pin] = plat_pin

elif isinstance(io, str):
for conn_pin, plat_pin in enumerate(io.split(), start=1):
if plat_pin == "-":
continue
self.mapping[str(conn_pin)] = plat_pin

else:
raise TypeError("Connector I/Os must be a dictionary or a string, not {!r}"
.format(io))

def __repr__(self):
return "(connector {} {} {})".format(self.name, self.number,
" ".join("{}=>{}".format(conn, plat)
for conn, plat in self.mapping.items()))

def __len__(self):
return len(self.mapping)

def __iter__(self):
for conn_pin, plat_pin in self.mapping.items():
yield "{}_{}:{}".format(self.name, self.number, conn_pin), plat_pin
7 changes: 4 additions & 3 deletions nmigen/build/plat.py
Original file line number Diff line number Diff line change
@@ -74,11 +74,12 @@ def get(self, filename, mode="b"):


class Platform(ConstraintManager, metaclass=ABCMeta):
resources = abstractproperty()
clocks = abstractproperty()
resources = abstractproperty()
connectors = abstractproperty()
clocks = abstractproperty()

def __init__(self):
super().__init__(self.resources, self.clocks)
super().__init__(self.resources, self.connectors, self.clocks)

self.extra_files = OrderedDict()

71 changes: 44 additions & 27 deletions nmigen/build/res.py
Original file line number Diff line number Diff line change
@@ -15,26 +15,43 @@ class ConstraintError(Exception):


class ConstraintManager:
def __init__(self, resources, clocks):
def __init__(self, resources, connectors, clocks):
self.resources = OrderedDict()
self.requested = OrderedDict()
self.connectors = OrderedDict()
self.clocks = OrderedDict()

self._mapping = OrderedDict()
self._requested = OrderedDict()
self._ports = []

self.add_resources(resources)
self.add_connectors(connectors)
for name_number, frequency in clocks:
if not isinstance(name_number, tuple):
name_number = (name_number, 0)
self.add_clock(*name_number, frequency)

def add_resources(self, resources):
for r in resources:
if not isinstance(r, Resource):
raise TypeError("Object {!r} is not a Resource".format(r))
if (r.name, r.number) in self.resources:
for res in resources:
if not isinstance(res, Resource):
raise TypeError("Object {!r} is not a Resource".format(res))
if (res.name, res.number) in self.resources:
raise NameError("Trying to add {!r}, but {!r} has the same name and number"
.format(res, self.resources[res.name, res.number]))
self.resources[res.name, res.number] = res

def add_connectors(self, connectors):
for conn in connectors:
if not isinstance(conn, Connector):
raise TypeError("Object {!r} is not a Connector".format(conn))
if (conn.name, conn.number) in self.connectors:
raise NameError("Trying to add {!r}, but {!r} has the same name and number"
.format(r, self.resources[r.name, r.number]))
self.resources[r.name, r.number] = r
.format(conn, self.connectors[conn.name, conn.number]))
self.connectors[conn.name, conn.number] = conn

for conn_pin, plat_pin in conn:
assert conn_pin not in self._mapping
self._mapping[conn_pin] = plat_pin

def add_clock(self, name, number, frequency):
resource = self.lookup(name, number)
@@ -57,7 +74,7 @@ def lookup(self, name, number=0):

def request(self, name, number=0, *, dir=None, xdr=None):
resource = self.lookup(name, number)
if (resource.name, resource.number) in self.requested:
if (resource.name, resource.number) in self._requested:
raise ConstraintError("Resource {}#{} has already been requested"
.format(name, number))

@@ -129,48 +146,48 @@ def resolve(subsignal, dir, xdr, name):
value = resolve(resource,
*merge_options(resource, dir, xdr),
name="{}_{}".format(resource.name, resource.number))
self.requested[resource.name, resource.number] = value
self._requested[resource.name, resource.number] = value
return value

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

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

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

def iter_port_constraints(self):
for resource, pin, port in self._ports:
if isinstance(resource.io[0], Pins):
yield port.io.name, resource.io[0].names, resource.extras
elif isinstance(resource.io[0], DiffPairs):
yield port.p.name, resource.io[0].p.names, resource.extras
yield port.n.name, resource.io[0].n.names, resource.extras
for res, pin, port in self._ports:
if isinstance(res.io[0], Pins):
yield port.io.name, list(res.io[0].map_names(self._mapping, res)), res.extras
elif isinstance(res.io[0], DiffPairs):
yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras
yield port.n.name, list(res.io[0].n.map_names(self._mapping, res)), res.extras
else:
assert False

def iter_clock_constraints(self):
for name, number in self.clocks.keys() & self.requested.keys():
for name, number in self.clocks.keys() & self._requested.keys():
resource = self.resources[name, number]
pin = self.requested[name, number]
period = self.clocks[name, number]
pin = self._requested[name, number]
if pin.dir == "io":
raise ConstraintError("Cannot constrain frequency of resource {}#{} because "
"it has been requested as a tristate buffer"
92 changes: 92 additions & 0 deletions nmigen/test/test_build_dsl.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from collections import OrderedDict

from ..build.dsl import *
from .tools import *

@@ -10,6 +12,27 @@ def test_basic(self):
self.assertEqual(p.dir, "io")
self.assertEqual(list(p), ["A0", "A1", "A2"])

def test_conn(self):
p = Pins("0 1 2", conn=("pmod", 0))
self.assertEqual(list(p), ["pmod_0:0", "pmod_0:1", "pmod_0:2"])

def test_map_names(self):
p = Pins("0 1 2", conn=("pmod", 0))
mapping = {
"pmod_0:0": "A0",
"pmod_0:1": "A1",
"pmod_0:2": "A2",
}
self.assertEqual(list(p.map_names(mapping, p)), ["A0", "A1", "A2"])

def test_map_names_recur(self):
p = Pins("0", conn=("pmod", 0))
mapping = {
"pmod_0:0": "ext_0:1",
"ext_0:1": "A1",
}
self.assertEqual(list(p.map_names(mapping, p)), ["A1"])

def test_wrong_names(self):
with self.assertRaises(TypeError,
msg="Names must be a whitespace-separated string, not ['A0', 'A1', 'A2']"):
@@ -20,6 +43,16 @@ def test_wrong_dir(self):
msg="Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not 'wrong'"):
p = Pins("A0 A1", dir="wrong")

def test_wrong_map_names(self):
p = Pins("0 1 2", conn=("pmod", 0))
mapping = {
"pmod_0:0": "A0",
}
with self.assertRaises(NameError,
msg="Resource (pins io pmod_0:0 pmod_0:1 pmod_0:2) refers to nonexistent "
"connector pin pmod_0:1"):
list(p.map_names(mapping, p))


class DiffPairsTestCase(FHDLTestCase):
def test_basic(self):
@@ -30,6 +63,14 @@ def test_basic(self):
self.assertEqual(dp.dir, "io")
self.assertEqual(list(dp), [("A0", "B0"), ("A1", "B1")])

def test_conn(self):
dp = DiffPairs(p="0 1 2", n="3 4 5", conn=("pmod", 0))
self.assertEqual(list(dp), [
("pmod_0:0", "pmod_0:3"),
("pmod_0:1", "pmod_0:4"),
("pmod_0:2", "pmod_0:5"),
])

def test_dir(self):
dp = DiffPairs("A0", "B0", dir="o")
self.assertEqual(dp.dir, "o")
@@ -118,3 +159,54 @@ def test_basic(self):
" (subsignal tx (pins o A0) IOSTANDARD=LVCMOS33)"
" (subsignal rx (pins i A1) IOSTANDARD=LVCMOS33)"
" IOSTANDARD=LVCMOS33)")


class ConnectorTestCase(FHDLTestCase):
def test_string(self):
c = Connector("pmod", 0, "A0 A1 A2 A3 - - A4 A5 A6 A7 - -")
self.assertEqual(c.name, "pmod")
self.assertEqual(c.number, 0)
self.assertEqual(c.mapping, OrderedDict([
("1", "A0"),
("2", "A1"),
("3", "A2"),
("4", "A3"),
("7", "A4"),
("8", "A5"),
("9", "A6"),
("10", "A7"),
]))
self.assertEqual(list(c), [
("pmod_0:1", "A0"),
("pmod_0:2", "A1"),
("pmod_0:3", "A2"),
("pmod_0:4", "A3"),
("pmod_0:7", "A4"),
("pmod_0:8", "A5"),
("pmod_0:9", "A6"),
("pmod_0:10", "A7"),
])
self.assertEqual(repr(c),
"(connector pmod 0 1=>A0 2=>A1 3=>A2 4=>A3 7=>A4 8=>A5 9=>A6 10=>A7)")

def test_dict(self):
c = Connector("ext", 1, {"DP0": "A0", "DP1": "A1"})
self.assertEqual(c.name, "ext")
self.assertEqual(c.number, 1)
self.assertEqual(c.mapping, OrderedDict([
("DP0", "A0"),
("DP1", "A1"),
]))

def test_wrong_io(self):
with self.assertRaises(TypeError,
msg="Connector I/Os must be a dictionary or a string, not []"):
Connector("pmod", 0, [])

def test_wrong_dict_key_value(self):
with self.assertRaises(TypeError,
msg="Connector pin name must be a string, not 0"):
Connector("pmod", 0, {0: "A"})
with self.assertRaises(TypeError,
msg="Platform pin name must be a string, not 0"):
Connector("pmod", 0, {"A": 0})
Loading