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: 2b7dc37ffe9b
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: c7d13b78e5c4
Choose a head ref
  • 3 commits
  • 4 files changed
  • 2 contributors

Commits on May 25, 2019

  1. build.dsl: improve repr of Pins() and DiffPairs().

    whitequark committed May 25, 2019
    Copy the full SHA
    48145ce View commit details
  2. build.dsl: make Pins and DiffPairs iterable.

    Returns pin names.
    whitequark committed May 25, 2019
    Copy the full SHA
    3a9fe31 View commit details

Commits on May 26, 2019

  1. build.res: add ConstraintManager.

    Jean-François Nguyen authored and whitequark committed May 26, 2019
    Copy the full SHA
    c7d13b7 View commit details
Showing with 388 additions and 15 deletions.
  1. +15 −2 nmigen/build/dsl.py
  2. +172 −0 nmigen/build/res.py
  3. +16 −13 nmigen/test/test_build_dsl.py
  4. +185 −0 nmigen/test/test_build_res.py
17 changes: 15 additions & 2 deletions nmigen/build/dsl.py
Original file line number Diff line number Diff line change
@@ -13,8 +13,14 @@ def __init__(self, names, dir="io"):
.format(dir))
self.dir = dir

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

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

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


class DiffPairs:
@@ -29,8 +35,15 @@ def __init__(self, p, n, dir="io"):

self.dir = dir

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

def __iter__(self):
return zip(self.p.names, self.n.names)

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


class Subsignal:
172 changes: 172 additions & 0 deletions nmigen/build/res.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from collections import OrderedDict

from .. import *
from ..hdl.rec import *
from ..lib.io import *

from .dsl import *


__all__ = ["ConstraintError", "ConstraintManager"]


class ConstraintError(Exception):
pass


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

self._ports = []
self._tristates = []
self._diffpairs = []

self.add_resources(resources)

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:
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

def add_clock(self, name, number, frequency):
resource = self.lookup(name, number)
if isinstance(resource.io[0], Subsignal):
raise ConstraintError("Cannot constrain frequency of resource {}#{} because it has "
"subsignals"
.format(resource.name, resource.number, frequency))
if (resource.name, resource.number) in self.clocks:
other = self.clocks[resource.name, resource.number]
raise ConstraintError("Resource {}#{} is already constrained to a frequency of "
"{:f} MHz"
.format(resource.name, resource.number, other / 1e6))
self.clocks[resource.name, resource.number] = frequency

def lookup(self, name, number):
if (name, number) not in self.resources:
raise NameError("Resource {}#{} does not exist"
.format(name, number))
return self.resources[name, number]

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

def resolve_dir_xdr(subsignal, dir, xdr):
if isinstance(subsignal.io[0], Subsignal):
if dir is None:
dir = dict()
if xdr is None:
xdr = dict()
if not isinstance(dir, dict):
raise TypeError("Directions must be a dict, not {!r}, because {!r} "
"has subsignals"
.format(dir, subsignal))
if not isinstance(xdr, dict):
raise TypeError("Data rate must be a dict, not {!r}, because {!r} "
"has subsignals"
.format(xdr, subsignal))
for sub in subsignal.io:
sub_dir = dir.get(sub.name, None)
sub_xdr = xdr.get(sub.name, None)
dir[sub.name], xdr[sub.name] = resolve_dir_xdr(sub, sub_dir, sub_xdr)
else:
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}"
.format(dir))
if subsignal.io[0].dir != "io" and dir != subsignal.io[0].dir:
raise ValueError("Direction of {!r} cannot be changed from \"{}\" to \"{}\"; "
"direction can be changed from \"io\" to \"i\" or from \"io\""
"to \"o\""
.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}"
.format(subsignal.io[0], xdr))
return dir, xdr

dir, xdr = resolve_dir_xdr(resource, dir, xdr)

def get_value(subsignal, dir, xdr, name):
if isinstance(subsignal.io[0], Subsignal):
fields = OrderedDict()
for sub in subsignal.io:
fields[sub.name] = get_value(sub, dir[sub.name], xdr[sub.name],
"{}__{}".format(name, sub.name))
rec = Record([
(f_name, f.layout) for (f_name, f) in fields.items()
], fields=fields, name=name)
return rec
elif isinstance(subsignal.io[0], DiffPairs):
pairs = subsignal.io[0]
return Pin(len(pairs), dir, xdr, name=name)
elif isinstance(subsignal.io[0], Pins):
pins = subsignal.io[0]
return Pin(len(pins), dir, xdr, name=name)
else:
assert False # :nocov:

value_name = "{}_{}".format(resource.name, resource.number)
value = get_value(resource, dir, xdr, value_name)

def match_constraints(value, subsignal):
if isinstance(subsignal.io[0], Subsignal):
for sub in subsignal.io:
yield from match_constraints(value[sub.name], sub)
else:
assert isinstance(value, Pin)
yield (value, subsignal.io[0], subsignal.extras)

for (pin, io, extras) in match_constraints(value, resource):
if isinstance(io, DiffPairs):
p = Signal(pin.width, name="{}_p".format(pin.name))
n = Signal(pin.width, name="{}_n".format(pin.name))
self._diffpairs.append((pin, p, n))
self._ports.append((p, io.p.names, extras))
self._ports.append((n, io.n.names, extras))
elif isinstance(io, Pins):
if pin.dir == "io":
port = Signal(pin.width, name="{}_io".format(pin.name))
self._tristates.append((pin, port))
else:
port = getattr(pin, pin.dir)
self._ports.append((port, io.names, extras))
else:
assert False # :nocov:

self.requested[resource.name, resource.number] = value
return value

def iter_ports(self):
for port, pins, extras in self._ports:
yield port

def iter_port_constraints(self):
for port, pins, extras in self._ports:
yield (port.name, pins, extras)

def iter_clock_constraints(self):
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]
if pin.dir == "io":
raise ConstraintError("Cannot constrain frequency of resource {}#{} because "
"it has been requested as a tristate buffer"
.format(name, number))
if isinstance(resource.io[0], DiffPairs):
port_name = "{}_p".format(pin.name)
else:
port_name = getattr(pin, pin.dir).name
yield (port_name, period)
29 changes: 16 additions & 13 deletions nmigen/test/test_build_dsl.py
Original file line number Diff line number Diff line change
@@ -5,9 +5,10 @@
class PinsTestCase(FHDLTestCase):
def test_basic(self):
p = Pins("A0 A1 A2")
self.assertEqual(repr(p), "(pins A0 A1 A2 io)")
self.assertEqual(repr(p), "(pins io A0 A1 A2)")
self.assertEqual(len(p.names), 3)
self.assertEqual(p.dir, "io")
self.assertEqual(list(p), ["A0", "A1", "A2"])

def test_wrong_names(self):
with self.assertRaises(TypeError,
@@ -23,10 +24,11 @@ def test_wrong_dir(self):
class DiffPairsTestCase(FHDLTestCase):
def test_basic(self):
dp = DiffPairs(p="A0 A1", n="B0 B1")
self.assertEqual(repr(dp), "(diffpairs (pins A0 A1 io) (pins B0 B1 io))")
self.assertEqual(repr(dp), "(diffpairs io (p A0 A1) (n B0 B1))")
self.assertEqual(dp.p.names, ["A0", "A1"])
self.assertEqual(dp.n.names, ["B0", "B1"])
self.assertEqual(dp.dir, "io")
self.assertEqual(list(dp), [("A0", "B0"), ("A1", "B1")])

def test_dir(self):
dp = DiffPairs("A0", "B0", dir="o")
@@ -36,26 +38,26 @@ def test_dir(self):

def test_wrong_width(self):
with self.assertRaises(TypeError,
msg="Positive and negative pins must have the same width, but (pins A0 io) and "
"(pins B0 B1 io) do not"):
msg="Positive and negative pins must have the same width, but (pins io A0) "
"and (pins io B0 B1) do not"):
dp = DiffPairs("A0", "B0 B1")


class SubsignalTestCase(FHDLTestCase):
def test_basic_pins(self):
s = Subsignal("a", Pins("A0"), extras=["IOSTANDARD=LVCMOS33"])
self.assertEqual(repr(s), "(subsignal a (pins A0 io) IOSTANDARD=LVCMOS33)")
self.assertEqual(repr(s), "(subsignal a (pins io A0) IOSTANDARD=LVCMOS33)")

def test_basic_diffpairs(self):
s = Subsignal("a", DiffPairs("A0", "B0"))
self.assertEqual(repr(s), "(subsignal a (diffpairs (pins A0 io) (pins B0 io)) )")
self.assertEqual(repr(s), "(subsignal a (diffpairs io (p A0) (n B0)) )")

def test_basic_subsignals(self):
s = Subsignal("a",
Subsignal("b", Pins("A0")),
Subsignal("c", Pins("A1")))
self.assertEqual(repr(s),
"(subsignal a (subsignal b (pins A0 io) ) (subsignal c (pins A1 io) ) )")
"(subsignal a (subsignal b (pins io A0) ) (subsignal c (pins io A1) ) )")

def test_extras(self):
s = Subsignal("a",
@@ -78,24 +80,25 @@ def test_wrong_io(self):
def test_wrong_pins(self):
with self.assertRaises(TypeError,
msg="Pins and DiffPairs cannot be followed by more I/O constraints, but "
"(pins A0 io) is followed by (pins A1 io)"):
"(pins io A0) is followed by (pins io A1)"):
s = Subsignal("a", Pins("A0"), Pins("A1"))

def test_wrong_diffpairs(self):
with self.assertRaises(TypeError,
msg="Pins and DiffPairs cannot be followed by more I/O constraints, but "
"(diffpairs (pins A0 io) (pins B0 io)) is followed by (pins A1 io)"):
"(diffpairs io (p A0) (n B0)) is followed by "
"(pins io A1)"):
s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1"))

def test_wrong_subsignals(self):
with self.assertRaises(TypeError,
msg="A Subsignal can only be followed by more Subsignals, but "
"(subsignal b (pins A0 io) ) is followed by (pins B0 io)"):
"(subsignal b (pins io A0) ) is followed by (pins io B0)"):
s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0"))

def test_wrong_extras(self):
with self.assertRaises(TypeError,
msg="Extra constraint must be a string, not (pins B0 io)"):
msg="Extra constraint must be a string, not (pins io B0)"):
s = Subsignal("a", Pins("A0"), extras=[Pins("B0")])


@@ -106,6 +109,6 @@ def test_basic(self):
Subsignal("rx", Pins("A1", dir="i")),
extras=["IOSTANDARD=LVCMOS33"])
self.assertEqual(repr(r), "(resource serial 0"
" (subsignal tx (pins A0 o) IOSTANDARD=LVCMOS33)"
" (subsignal rx (pins A1 i) IOSTANDARD=LVCMOS33)"
" (subsignal tx (pins o A0) IOSTANDARD=LVCMOS33)"
" (subsignal rx (pins i A1) IOSTANDARD=LVCMOS33)"
" IOSTANDARD=LVCMOS33)")
Loading