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: e4ebe03115d5
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: fb0185437260
Choose a head ref
  • 3 commits
  • 8 files changed
  • 1 contributor

Commits on Jun 2, 2019

  1. build.dsl: require a dict for extras instead of a stringly array.

    Fixes #72.
    whitequark committed Jun 2, 2019
    Copy the full SHA
    98497b2 View commit details

Commits on Jun 3, 2019

  1. build.res: simplify. NFC.

    whitequark committed Jun 3, 2019
    Copy the full SHA
    268fe63 View commit details
  2. build.{res,plat}: propagate extras to pin fragment factories.

    This is necessary because on some platforms, like iCE40, extras
    become parameters on an IO primitive, since the constraint file
    format is not expressive enough for all of them.
    whitequark committed Jun 3, 2019
    Copy the full SHA
    fb01854 View commit details
Showing with 169 additions and 130 deletions.
  1. +22 −11 nmigen/build/dsl.py
  2. +40 −30 nmigen/build/plat.py
  3. +53 −47 nmigen/build/res.py
  4. +14 −8 nmigen/test/test_build_dsl.py
  5. +16 −10 nmigen/test/test_build_res.py
  6. +9 −9 nmigen/vendor/ice40_hx1k_blink_evn.py
  7. +9 −9 nmigen/vendor/icestick.py
  8. +6 −6 nmigen/vendor/tinyfpga_bx.py
33 changes: 22 additions & 11 deletions nmigen/build/dsl.py
Original file line number Diff line number Diff line change
@@ -47,7 +47,7 @@ def __repr__(self):


class Subsignal:
def __init__(self, name, *io, extras=()):
def __init__(self, name, *io, extras=None):
self.name = name

if not io:
@@ -67,30 +67,41 @@ def __init__(self, name, *io, extras=()):
raise TypeError("A Subsignal can only be followed by more Subsignals, but "
"{!r} is followed by {!r}"
.format(io[0], c))
self.io = io

for c in extras:
if not isinstance(c, str):
raise TypeError("Extra constraint must be a string, not {!r}".format(c))
self.extras = list(extras)
self.io = io
self.extras = {}

if extras is not None:
if not isinstance(extras, dict):
raise TypeError("Extra constraints must be a dict, not {!r}"
.format(extras))
for extra_key, extra_value in extras.items():
if not isinstance(extra_key, str):
raise TypeError("Extra constraint key must be a string, not {!r}"
.format(extra_key))
if not isinstance(extra_value, str):
raise TypeError("Extra constraint value must be a string, not {!r}"
.format(extra_value))
self.extras[extra_key] = extra_value

if isinstance(self.io[0], Subsignal):
for sub in self.io:
sub.extras += self.extras
sub.extras.update(self.extras)

def __repr__(self):
return "(subsignal {} {} {})".format(self.name,
" ".join(map(repr, self.io)),
" ".join(self.extras))
" ".join("{}={}".format(k, v)
for k, v in self.extras.items()))


class Resource(Subsignal):
def __init__(self, name, number, *io, extras=()):
def __init__(self, name, number, *io, extras=None):
super().__init__(name, *io, extras=extras)

self.number = number

def __repr__(self):
return "(resource {} {} {} {})".format(self.name, self.number,
" ".join(map(repr, self.io)),
" ".join(self.extras))
" ".join("{}={}".format(k, v)
for k, v in self.extras.items()))
70 changes: 40 additions & 30 deletions nmigen/build/plat.py
Original file line number Diff line number Diff line change
@@ -116,27 +116,27 @@ def prepare(self, fragment, name="top", **kwargs):

fragment = Fragment.get(fragment, self)

pin_fragments = []
for pin, port in self._se_pins:
def add_pin_fragment(pin, pin_fragment):
pin_fragment = Fragment.get(pin_fragment, self)
if not isinstance(pin_fragment, Instance):
pin_fragment.flatten = True
fragment.add_subfragment(pin_fragment, name="pin_{}".format(pin.name))

for pin, port, extras in self.iter_single_ended_pins():
if pin.dir == "i":
pin_fragments.append((pin.name, self.get_input(pin, port)))
add_pin_fragment(pin, self.get_input(pin, port, extras))
if pin.dir == "o":
pin_fragments.append((pin.name, self.get_output(pin, port)))
add_pin_fragment(pin, self.get_output(pin, port, extras))
if pin.dir == "io":
pin_fragments.append((pin.name, self.get_tristate(pin, port)))
for pin, p_port, n_port in self._dp_pins:
add_pin_fragment(pin, self.get_input(pin, port, extras))

for pin, p_port, n_port, extras in self.iter_differential_pins():
if pin.dir == "i":
pin_fragments.append((pin.name, self.get_diff_input(pin, p_port, n_port)))
add_pin_fragment(pin, self.get_diff_input(pin, p_port, n_port))
if pin.dir == "o":
pin_fragments.append((pin.name, self.get_diff_output(pin, p_port, n_port)))
add_pin_fragment(pin, self.get_diff_output(pin, p_port, n_port))
if pin.dir == "io":
pin_fragments.append((pin.name, self.get_diff_tristate(pin, p_port, n_port)))

for pin_name, pin_fragment in pin_fragments:
pin_fragment = Fragment.get(pin_fragment, self)
if not isinstance(pin_fragment, Instance):
pin_fragment.flatten = True
fragment.add_subfragment(pin_fragment, name="pin_{}".format(pin_name))
add_pin_fragment(pin, self.get_diff_tristate(pin, p_port, n_port))

return self.toolchain_prepare(fragment, name, **kwargs)

@@ -155,30 +155,37 @@ def toolchain_program(self, products, name, **kwargs):
raise NotImplementedError("Platform {} does not support programming"
.format(self.__class__.__name__))

def _check_feature(self, feature, pin, xdrs):
if not xdrs:
def _check_feature(self, feature, pin, extras, valid_xdrs, valid_extras):
if not valid_xdrs:
raise NotImplementedError("Platform {} does not support {}"
.format(self.__class__.__name__, feature))
elif pin.xdr not in xdrs:
elif pin.xdr not in valid_xdrs:
raise NotImplementedError("Platform {} does not support {} for XDR {}"
.format(self.__class__.__name__, feature, pin.xdr))

def get_input(self, pin, port):
self._check_feature("single-ended input", pin, xdrs=(1,))
if not valid_extras and extras:
raise NotImplementedError("Platform {} does not support extras for {}"
.format(self.__class__.__name__, feature))

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

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

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

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

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

m = Module()
m.submodules += Instance("$tribuf",
@@ -190,14 +197,17 @@ def get_tristate(self, pin, port):
m.d.comb += pin.i.eq(port)
return m

def get_diff_input(self, pin, p_port, n_port):
self._check_feature("differential input", pin, xdrs=())
def get_diff_input(self, pin, p_port, n_port, extras):
self._check_feature("differential input", pin, extras,
valid_xdrs=(), valid_extras=None)

def get_diff_output(self, pin, p_port, n_port):
self._check_feature("differential output", pin, xdrs=())
def get_diff_output(self, pin, p_port, n_port, extras):
self._check_feature("differential output", pin, extras,
valid_xdrs=(), valid_extras=None)

def get_diff_tristate(self, pin, p_port, n_port):
self._check_feature("differential tristate", pin, xdrs=())
def get_diff_tristate(self, pin, p_port, n_port, extras):
self._check_feature("differential tristate", pin, extras,
valid_xdrs=(), valid_extras=None)


class TemplatedPlatform(Platform):
100 changes: 53 additions & 47 deletions nmigen/build/res.py
Original file line number Diff line number Diff line change
@@ -19,10 +19,7 @@ def __init__(self, resources, clocks):
self.resources = OrderedDict()
self.requested = OrderedDict()
self.clocks = OrderedDict()

self._ports = []
self._se_pins = []
self._dp_pins = []

self.add_resources(resources)
for name_number, frequency in clocks:
@@ -64,7 +61,7 @@ def request(self, name, number, dir=None, xdr=None):
raise ConstraintError("Resource {}#{} has already been requested"
.format(name, number))

def resolve_dir_xdr(subsignal, dir, xdr):
def merge_options(subsignal, dir, xdr):
if isinstance(subsignal.io[0], Subsignal):
if dir is None:
dir = dict()
@@ -81,7 +78,7 @@ def resolve_dir_xdr(subsignal, dir, xdr):
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)
dir[sub.name], xdr[sub.name] = merge_options(sub, sub_dir, sub_xdr)
else:
if dir is None:
dir = subsignal.io[0].dir
@@ -100,62 +97,71 @@ def resolve_dir_xdr(subsignal, dir, xdr):
.format(subsignal.io[0], xdr))
return dir, xdr

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

def get_value(subsignal, dir, xdr, name):
def resolve(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([
fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
name="{}__{}".format(name, sub.name))
return 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, Pins):
port = Signal(pin.width, name="{}_io".format(pin.name))
self._se_pins.append((pin, port))
self._ports.append((port, io.names, extras))
elif isinstance(io, DiffPairs):
p_port = Signal(pin.width, name="{}_p".format(pin.name))
n_port = Signal(pin.width, name="{}_n".format(pin.name))
self._dp_pins.append((pin, p_port, n_port))
self._ports.append((p_port, io.p.names, extras))
self._ports.append((n_port, io.n.names, extras))
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))
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
else:
assert False # :nocov:

value = resolve(resource,
*merge_options(resource, dir, xdr),
name="{}_{}".format(resource.name, resource.number))
self.requested[resource.name, resource.number] = value
return value

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

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

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

def iter_port_constraints(self):
for port, pins, extras in self._ports:
yield (port.name, pins, extras)
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
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
if "n" in diff_pins:
yield n_port.name, resource.io[0].n.names, resource.extras
else:
assert False

def iter_clock_constraints(self):
for name, number in self.clocks.keys() & self.requested.keys():
22 changes: 14 additions & 8 deletions nmigen/test/test_build_dsl.py
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ def test_wrong_width(self):

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

def test_basic_diffpairs(self):
@@ -62,11 +62,11 @@ def test_basic_subsignals(self):
def test_extras(self):
s = Subsignal("a",
Subsignal("b", Pins("A0")),
Subsignal("c", Pins("A0"), extras=["SLEW=FAST"]),
extras=["IOSTANDARD=LVCMOS33"])
self.assertEqual(s.extras, ["IOSTANDARD=LVCMOS33"])
self.assertEqual(s.io[0].extras, ["IOSTANDARD=LVCMOS33"])
self.assertEqual(s.io[1].extras, ["SLEW=FAST", "IOSTANDARD=LVCMOS33"])
Subsignal("c", Pins("A0"), extras={"SLEW": "FAST"}),
extras={"IOSTANDARD": "LVCMOS33"})
self.assertEqual(s.extras, {"IOSTANDARD": "LVCMOS33"})
self.assertEqual(s.io[0].extras, {"IOSTANDARD": "LVCMOS33"})
self.assertEqual(s.io[1].extras, {"SLEW": "FAST", "IOSTANDARD": "LVCMOS33"})

def test_empty_io(self):
with self.assertRaises(TypeError, msg="Missing I/O constraints"):
@@ -98,16 +98,22 @@ def test_wrong_subsignals(self):

def test_wrong_extras(self):
with self.assertRaises(TypeError,
msg="Extra constraint must be a string, not (pins io B0)"):
msg="Extra constraints must be a dict, not [(pins io B0)]"):
s = Subsignal("a", Pins("A0"), extras=[Pins("B0")])
with self.assertRaises(TypeError,
msg="Extra constraint key must be a string, not 1"):
s = Subsignal("a", Pins("A0"), extras={1: 2})
with self.assertRaises(TypeError,
msg="Extra constraint value must be a string, not 2"):
s = Subsignal("a", Pins("A0"), extras={"1": 2})


class ResourceTestCase(FHDLTestCase):
def test_basic(self):
r = Resource("serial", 0,
Subsignal("tx", Pins("A0", dir="o")),
Subsignal("rx", Pins("A1", dir="i")),
extras=["IOSTANDARD=LVCMOS33"])
extras={"IOSTANDARD": "LVCMOS33"})
self.assertEqual(repr(r), "(resource serial 0"
" (subsignal tx (pins o A0) IOSTANDARD=LVCMOS33)"
" (subsignal rx (pins i A1) IOSTANDARD=LVCMOS33)"
Loading