build.dsl: replace extras= with Attrs().

This change proved more tricky than expected due to downstream
dependencies, so it also includes some secondary refactoring.
This commit is contained in:
whitequark 2019-06-05 07:02:08 +00:00
parent c52cd72d3e
commit ab3f103e5a
7 changed files with 262 additions and 253 deletions

View file

@ -1,3 +1,3 @@
from .dsl import Pins, DiffPairs, Subsignal, Resource, Connector
from .res import ConstraintError
from .dsl import Pins, DiffPairs, Attrs, Subsignal, Resource, Connector
from .res import ResourceError
from .plat import Platform, TemplatedPlatform

View file

@ -1,7 +1,7 @@
from collections import OrderedDict
__all__ = ["Pins", "DiffPairs", "Subsignal", "Resource", "Connector"]
__all__ = ["Pins", "DiffPairs", "Attrs", "Subsignal", "Resource", "Connector"]
class Pins:
@ -32,13 +32,15 @@ class Pins:
return iter(self.names)
def map_names(self, mapping, resource):
mapped_names = []
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
mapped_names.append(name)
return mapped_names
def __repr__(self):
return "(pins {} {})".format(self.dir, " ".join(self.names))
@ -67,65 +69,66 @@ class DiffPairs:
self.dir, " ".join(self.p.names), " ".join(self.n.names))
class Attrs(OrderedDict):
def __init__(self, **attrs):
for attr_key, attr_value in attrs.items():
if not isinstance(attr_value, str):
raise TypeError("Attribute value must be a string, not {!r}"
.format(attr_value))
super().__init__(**attrs)
def __repr__(self):
return "(attrs {})".format(" ".join("{}={}".format(k, v)
for k, v in self.items()))
class Subsignal:
def __init__(self, name, *io, extras=None):
self.name = name
def __init__(self, name, *args):
self.name = name
self.ios = []
self.attrs = Attrs()
if not io:
raise TypeError("Missing I/O constraints")
for c in io:
if not isinstance(c, (Pins, DiffPairs, Subsignal)):
raise TypeError("I/O constraint must be one of Pins, DiffPairs or Subsignal, "
"not {!r}"
.format(c))
if isinstance(io[0], (Pins, DiffPairs)) and len(io) > 1:
raise TypeError("Pins and DiffPairs cannot be followed by more I/O constraints, but "
"{!r} is followed by {!r}"
.format(io[0], io[1]))
if isinstance(io[0], Subsignal):
for c in io[1:]:
if not isinstance(c, Subsignal):
raise TypeError("A Subsignal can only be followed by more Subsignals, but "
"{!r} is followed by {!r}"
.format(io[0], c))
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.update(self.extras)
if not args:
raise ValueError("Missing I/O constraints")
for arg in args:
if isinstance(arg, (Pins, DiffPairs)):
if not self.ios:
self.ios.append(arg)
else:
raise TypeError("Pins and DiffPairs are incompatible with other location or "
"subsignal constraints, but {!r} appears after {!r}"
.format(arg, self.ios[-1]))
elif isinstance(arg, Subsignal):
if not self.ios or isinstance(self.ios[-1], Subsignal):
self.ios.append(arg)
else:
raise TypeError("Subsignal is incompatible with location constraints, but "
"{!r} appears after {!r}"
.format(arg, self.ios[-1]))
elif isinstance(arg, Attrs):
self.attrs.update(arg)
else:
raise TypeError("I/O constraint must be one of Pins, DiffPairs, Subsignal, "
"or Attrs, not {!r}"
.format(arg))
def __repr__(self):
return "(subsignal {} {} {})".format(self.name,
" ".join(map(repr, self.io)),
" ".join("{}={}".format(k, v)
for k, v in self.extras.items()))
" ".join(map(repr, self.ios)),
repr(self.attrs))
class Resource(Subsignal):
def __init__(self, name, number, *io, extras=None):
super().__init__(name, *io, extras=extras)
def __init__(self, name, number, *args):
super().__init__(name, *args)
self.number = number
def __repr__(self):
return "(resource {} {} {} {})".format(self.name, self.number,
" ".join(map(repr, self.io)),
" ".join("{}={}".format(k, v)
for k, v in self.extras.items()))
" ".join(map(repr, self.ios)),
repr(self.attrs))
class Connector:

View file

@ -17,7 +17,7 @@ from .run import *
__all__ = ["Platform", "TemplatedPlatform"]
class Platform(ConstraintManager, metaclass=ABCMeta):
class Platform(ResourceManager, metaclass=ABCMeta):
resources = abstractproperty()
connectors = abstractproperty()
clocks = abstractproperty()
@ -67,25 +67,25 @@ class Platform(ConstraintManager, metaclass=ABCMeta):
pin_fragment.flatten = True
fragment.add_subfragment(pin_fragment, name="pin_{}".format(pin.name))
for pin, port, extras in self.iter_single_ended_pins():
for pin, port, attrs in self.iter_single_ended_pins():
if pin.dir == "i":
add_pin_fragment(pin, self.get_input(pin, port, extras))
add_pin_fragment(pin, self.get_input(pin, port, attrs))
if pin.dir == "o":
add_pin_fragment(pin, self.get_output(pin, port, extras))
add_pin_fragment(pin, self.get_output(pin, port, attrs))
if pin.dir == "oe":
add_pin_fragment(pin, self.get_tristate(pin, port, extras))
add_pin_fragment(pin, self.get_tristate(pin, port, attrs))
if pin.dir == "io":
add_pin_fragment(pin, self.get_input_output(pin, port, extras))
add_pin_fragment(pin, self.get_input_output(pin, port, attrs))
for pin, p_port, n_port, extras in self.iter_differential_pins():
for pin, p_port, n_port, attrs in self.iter_differential_pins():
if pin.dir == "i":
add_pin_fragment(pin, self.get_diff_input(pin, p_port, n_port, extras))
add_pin_fragment(pin, self.get_diff_input(pin, p_port, n_port, attrs))
if pin.dir == "o":
add_pin_fragment(pin, self.get_diff_output(pin, p_port, n_port, extras))
add_pin_fragment(pin, self.get_diff_output(pin, p_port, n_port, attrs))
if pin.dir == "oe":
add_pin_fragment(pin, self.get_diff_tristate(pin, p_port, n_port, extras))
add_pin_fragment(pin, self.get_diff_tristate(pin, p_port, n_port, attrs))
if pin.dir == "io":
add_pin_fragment(pin, self.get_diff_input_output(pin, p_port, n_port, extras))
add_pin_fragment(pin, self.get_diff_input_output(pin, p_port, n_port, attrs))
return self.toolchain_prepare(fragment, name, **kwargs)
@ -104,7 +104,7 @@ class Platform(ConstraintManager, metaclass=ABCMeta):
raise NotImplementedError("Platform {} does not support programming"
.format(self.__class__.__name__))
def _check_feature(self, feature, pin, extras, valid_xdrs, valid_extras):
def _check_feature(self, feature, pin, attrs, valid_xdrs, valid_attrs):
if not valid_xdrs:
raise NotImplementedError("Platform {} does not support {}"
.format(self.__class__.__name__, feature))
@ -112,29 +112,29 @@ class Platform(ConstraintManager, metaclass=ABCMeta):
raise NotImplementedError("Platform {} does not support {} for XDR {}"
.format(self.__class__.__name__, feature, pin.xdr))
if not valid_extras and extras:
raise NotImplementedError("Platform {} does not support extras for {}"
if not valid_attrs and attrs:
raise NotImplementedError("Platform {} does not support attributes for {}"
.format(self.__class__.__name__, feature))
def get_input(self, pin, port, extras):
self._check_feature("single-ended input", pin, extras,
valid_xdrs=(0,), valid_extras=None)
def get_input(self, pin, port, attrs):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0,), valid_attrs=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=(0,), valid_extras=None)
def get_output(self, pin, port, attrs):
self._check_feature("single-ended output", pin, attrs,
valid_xdrs=(0,), valid_attrs=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=(0,), valid_extras=None)
def get_tristate(self, pin, port, attrs):
self._check_feature("single-ended tristate", pin, attrs,
valid_xdrs=(0,), valid_attrs=None)
m = Module()
m.submodules += Instance("$tribuf",
@ -145,9 +145,9 @@ class Platform(ConstraintManager, metaclass=ABCMeta):
)
return m
def get_input_output(self, pin, port, extras):
self._check_feature("single-ended input/output", pin, extras,
valid_xdrs=(0,), valid_extras=None)
def get_input_output(self, pin, port, attrs):
self._check_feature("single-ended input/output", pin, attrs,
valid_xdrs=(0,), valid_attrs=None)
m = Module()
m.submodules += Instance("$tribuf",
@ -159,21 +159,21 @@ class Platform(ConstraintManager, metaclass=ABCMeta):
m.d.comb += pin.i.eq(port)
return m
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_input(self, pin, p_port, n_port, attrs):
self._check_feature("differential input", pin, attrs,
valid_xdrs=(), valid_attrs=None)
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_output(self, pin, p_port, n_port, attrs):
self._check_feature("differential output", pin, attrs,
valid_xdrs=(), valid_attrs=None)
def get_diff_tristate(self, pin, p_port, n_port, extras):
self._check_feature("differential tristate", pin, extras,
valid_xdrs=(), valid_extras=None)
def get_diff_tristate(self, pin, p_port, n_port, attrs):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(), valid_attrs=None)
def get_diff_input_output(self, pin, p_port, n_port, extras):
self._check_feature("differential input/output", pin, extras,
valid_xdrs=(), valid_extras=None)
def get_diff_input_output(self, pin, p_port, n_port, attrs):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(), valid_attrs=None)
class TemplatedPlatform(Platform):

View file

@ -7,21 +7,24 @@ from ..lib.io import *
from .dsl import *
__all__ = ["ConstraintError", "ConstraintManager"]
__all__ = ["ResourceError", "ResourceManager"]
class ConstraintError(Exception):
class ResourceError(Exception):
pass
class ConstraintManager:
class ResourceManager:
def __init__(self, resources, connectors, clocks):
self.resources = OrderedDict()
self._requested = OrderedDict()
self.connectors = OrderedDict()
self._conn_pins = OrderedDict()
self.clocks = OrderedDict()
self._mapping = OrderedDict()
self._requested = OrderedDict()
# Constraint lists
self._ports = []
self.add_resources(resources)
@ -50,36 +53,36 @@ class ConstraintManager:
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
assert conn_pin not in self._conn_pins
self._conn_pins[conn_pin] = plat_pin
def add_clock(self, name, number, frequency):
resource = self.lookup(name, number)
if isinstance(resource.io[0], Subsignal):
if isinstance(resource.ios[0], Subsignal):
raise TypeError("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))
raise ResourceError("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=0):
if (name, number) not in self.resources:
raise ConstraintError("Resource {}#{} does not exist"
raise ResourceError("Resource {}#{} does not exist"
.format(name, number))
return self.resources[name, number]
def request(self, name, number=0, *, 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))
raise ResourceError("Resource {}#{} has already been requested"
.format(name, number))
def merge_options(subsignal, dir, xdr):
if isinstance(subsignal.io[0], Subsignal):
if isinstance(subsignal.ios[0], Subsignal):
if dir is None:
dir = dict()
if xdr is None:
@ -92,52 +95,54 @@ class ConstraintManager:
raise TypeError("Data rate must be a dict, not {!r}, because {!r} "
"has subsignals"
.format(xdr, subsignal))
for sub in subsignal.io:
for sub in subsignal.ios:
sub_dir = dir.get(sub.name, None)
sub_xdr = xdr.get(sub.name, None)
dir[sub.name], xdr[sub.name] = merge_options(sub, sub_dir, sub_xdr)
else:
if dir is None:
dir = subsignal.io[0].dir
dir = subsignal.ios[0].dir
if xdr is None:
xdr = 0
if dir not in ("i", "o", "oe", "io", "-"):
raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", \"io\", "
"or \"-\", not {!r}"
.format(dir))
if dir != subsignal.io[0].dir and not (subsignal.io[0].dir == "io" or dir == "-"):
if dir != subsignal.ios[0].dir and \
not (subsignal.ios[0].dir == "io" or dir == "-"):
raise ValueError("Direction of {!r} cannot be changed from \"{}\" to \"{}\"; "
"direction can be changed from \"io\" to \"i\", \"o\", or "
"\"oe\", or from anything to \"-\""
.format(subsignal.io[0], subsignal.io[0].dir, dir))
.format(subsignal.ios[0], subsignal.ios[0].dir, dir))
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))
.format(subsignal.ios[0], xdr))
return dir, xdr
def resolve(subsignal, dir, xdr, name):
if isinstance(subsignal.io[0], Subsignal):
def resolve(resource, dir, xdr, name, attrs):
if isinstance(resource.ios[0], Subsignal):
fields = OrderedDict()
for sub in subsignal.io:
for sub in resource.ios:
fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
name="{}__{}".format(name, sub.name))
name="{}__{}".format(name, sub.name),
attrs={**attrs, **sub.attrs})
return Record([
(f_name, f.layout) for (f_name, f) in fields.items()
], fields=fields, name=name)
elif isinstance(subsignal.io[0], (Pins, DiffPairs)):
phys = subsignal.io[0]
elif isinstance(resource.ios[0], (Pins, DiffPairs)):
phys = resource.ios[0]
if isinstance(phys, Pins):
port = Record([("io", len(phys))], name=name)
if isinstance(phys, DiffPairs):
port = Record([("p", len(phys)),
("n", len(phys))], name=name)
if dir == "-":
self._ports.append((subsignal, None, port))
self._ports.append((resource, None, port, attrs))
return port
else:
pin = Pin(len(phys), dir, xdr, name=name)
self._ports.append((subsignal, pin, port))
self._ports.append((resource, pin, port, attrs))
return pin
else:
@ -145,51 +150,61 @@ class ConstraintManager:
value = resolve(resource,
*merge_options(resource, dir, xdr),
name="{}_{}".format(resource.name, resource.number))
name="{}_{}".format(resource.name, resource.number),
attrs=resource.attrs)
self._requested[resource.name, resource.number] = value
return value
def iter_single_ended_pins(self):
for res, pin, port in self._ports:
for res, pin, port, attrs in self._ports:
if pin is None:
continue
if isinstance(res.io[0], Pins):
yield pin, port.io, res.extras
if isinstance(res.ios[0], Pins):
yield pin, port.io, attrs
def iter_differential_pins(self):
for res, pin, port in self._ports:
for res, pin, port, attrs in self._ports:
if pin is None:
continue
if isinstance(res.io[0], DiffPairs):
yield pin, port.p, port.n, res.extras
if isinstance(res.ios[0], DiffPairs):
yield pin, port.p, port.n, attrs
def should_skip_port_component(self, port, attrs, component):
return False
def iter_ports(self):
for res, pin, port in self._ports:
if isinstance(res.io[0], Pins):
yield port.io
elif isinstance(res.io[0], DiffPairs):
yield port.p
yield port.n
for res, pin, port, attrs in self._ports:
if isinstance(res.ios[0], Pins):
if not self.should_skip_port_component(port, attrs, "io"):
yield port.io
elif isinstance(res.ios[0], DiffPairs):
if not self.should_skip_port_component(port, attrs, "p"):
yield port.p
if not self.should_skip_port_component(port, attrs, "n"):
yield port.n
else:
assert False
def iter_port_constraints(self):
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
for res, pin, port, attrs in self._ports:
if isinstance(res.ios[0], Pins):
if not self.should_skip_port_component(port, attrs, "io"):
yield port.io.name, res.ios[0].map_names(self._conn_pins, res), attrs
elif isinstance(res.ios[0], DiffPairs):
if not self.should_skip_port_component(port, attrs, "p"):
yield port.p.name, res.ios[0].p.map_names(self._conn_pins, res), attrs
if not self.should_skip_port_component(port, attrs, "n"):
yield port.n.name, res.ios[0].n.map_names(self._conn_pins, res), attrs
else:
assert False
def iter_port_constraints_bits(self):
for port_name, pin_names, extras in self.iter_port_constraints():
for port_name, pin_names, attrs in self.iter_port_constraints():
if len(pin_names) == 1:
yield port_name, pin_names[0], extras
yield port_name, pin_names[0], attrs
else:
for bit, pin_name in enumerate(pin_names):
yield "{}[{}]".format(port_name, bit), pin_name, extras
yield "{}[{}]".format(port_name, bit), pin_name, attrs
def iter_clock_constraints(self):
for name, number in self.clocks.keys() & self._requested.keys():
@ -197,12 +212,12 @@ class ConstraintManager:
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"
.format(name, number))
if isinstance(resource.io[0], Pins):
raise ResourceError("Cannot constrain frequency of resource {}#{} because "
"it has been requested as a tristate buffer"
.format(name, number))
if isinstance(resource.ios[0], Pins):
port_name = "{}__io".format(pin.name)
elif isinstance(resource.io[0], DiffPairs):
elif isinstance(resource.ios[0], DiffPairs):
port_name = "{}__p".format(pin.name)
else:
assert False

View file

@ -23,7 +23,7 @@ class PinsTestCase(FHDLTestCase):
"pmod_0:1": "A1",
"pmod_0:2": "A2",
}
self.assertEqual(list(p.map_names(mapping, p)), ["A0", "A1", "A2"])
self.assertEqual(p.map_names(mapping, p), ["A0", "A1", "A2"])
def test_map_names_recur(self):
p = Pins("0", conn=("pmod", 0))
@ -31,7 +31,7 @@ class PinsTestCase(FHDLTestCase):
"pmod_0:0": "ext_0:1",
"ext_0:1": "A1",
}
self.assertEqual(list(p.map_names(mapping, p)), ["A1"])
self.assertEqual(p.map_names(mapping, p), ["A1"])
def test_wrong_names(self):
with self.assertRaises(TypeError,
@ -51,7 +51,7 @@ class PinsTestCase(FHDLTestCase):
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))
p.map_names(mapping, p)
class DiffPairsTestCase(FHDLTestCase):
@ -84,81 +84,90 @@ class DiffPairsTestCase(FHDLTestCase):
dp = DiffPairs("A0", "B0 B1")
class AttrsTestCase(FHDLTestCase):
def test_basic(self):
a = Attrs(IO_STANDARD="LVCMOS33", PULLUP="1")
self.assertEqual(a["IO_STANDARD"], "LVCMOS33")
self.assertEqual(repr(a), "(attrs IO_STANDARD=LVCMOS33 PULLUP=1)")
def test_wrong_value(self):
with self.assertRaises(TypeError,
msg="Attribute value must be a string, not 1"):
a = Attrs(FOO=1)
class SubsignalTestCase(FHDLTestCase):
def test_basic_pins(self):
s = Subsignal("a", Pins("A0"), extras={"IOSTANDARD": "LVCMOS33"})
self.assertEqual(repr(s), "(subsignal a (pins io A0) IOSTANDARD=LVCMOS33)")
s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33"))
self.assertEqual(repr(s),
"(subsignal a (pins io A0) (attrs IOSTANDARD=LVCMOS33))")
def test_basic_diffpairs(self):
s = Subsignal("a", DiffPairs("A0", "B0"))
self.assertEqual(repr(s), "(subsignal a (diffpairs io (p A0) (n B0)) )")
self.assertEqual(repr(s),
"(subsignal a (diffpairs io (p A0) (n B0)) (attrs ))")
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 io A0) ) (subsignal c (pins io A1) ) )")
"(subsignal a (subsignal b (pins io A0) (attrs )) "
"(subsignal c (pins io A1) (attrs )) (attrs ))")
def test_extras(self):
def test_attrs(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"), Attrs(SLEW="FAST")),
Attrs(IOSTANDARD="LVCMOS33"))
self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"})
self.assertEqual(s.ios[0].attrs, {})
self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"})
def test_empty_io(self):
with self.assertRaises(TypeError, msg="Missing I/O constraints"):
def test_attrs_many(self):
s = Subsignal("a", Pins("A0"), Attrs(SLEW="FAST"), Attrs(PULLUP="1"))
self.assertEqual(s.attrs, {"SLEW": "FAST", "PULLUP": "1"})
def test_wrong_empty_io(self):
with self.assertRaises(ValueError, msg="Missing I/O constraints"):
s = Subsignal("a")
def test_wrong_io(self):
with self.assertRaises(TypeError,
msg="I/O constraint must be one of Pins, DiffPairs or Subsignal, not 'wrong'"):
msg="I/O constraint must be one of Pins, DiffPairs, Subsignal, or Attrs, "
"not 'wrong'"):
s = Subsignal("a", "wrong")
def test_wrong_pins(self):
with self.assertRaises(TypeError,
msg="Pins and DiffPairs cannot be followed by more I/O constraints, but "
"(pins io A0) is followed by (pins io A1)"):
msg="Pins and DiffPairs are incompatible with other location or subsignal "
"constraints, but (pins io A1) appears after (pins io A0)"):
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 io (p A0) (n B0)) is followed by "
"(pins io A1)"):
msg="Pins and DiffPairs are incompatible with other location or subsignal "
"constraints, but (pins io A1) appears after (diffpairs io (p A0) (n B0))"):
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 io A0) ) is followed by (pins io B0)"):
msg="Pins and DiffPairs are incompatible with other location or subsignal "
"constraints, but (pins io B0) appears after (subsignal b (pins io A0) "
"(attrs ))"):
s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0"))
def test_wrong_extras(self):
with self.assertRaises(TypeError,
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"})
Attrs(IOSTANDARD="LVCMOS33"))
self.assertEqual(repr(r), "(resource serial 0"
" (subsignal tx (pins o A0) IOSTANDARD=LVCMOS33)"
" (subsignal rx (pins i A1) IOSTANDARD=LVCMOS33)"
" IOSTANDARD=LVCMOS33)")
" (subsignal tx (pins o A0) (attrs ))"
" (subsignal rx (pins i A1) (attrs ))"
" (attrs IOSTANDARD=LVCMOS33))")
class ConnectorTestCase(FHDLTestCase):

View file

@ -6,7 +6,7 @@ from ..build.res import *
from .tools import *
class ConstraintManagerTestCase(FHDLTestCase):
class ResourceManagerTestCase(FHDLTestCase):
def setUp(self):
self.resources = [
Resource("clk100", 0, DiffPairs("H1", "H2", dir="i")),
@ -20,14 +20,14 @@ class ConstraintManagerTestCase(FHDLTestCase):
self.connectors = [
Connector("pmod", 0, "B0 B1 B2 B3 - -"),
]
self.cm = ConstraintManager(self.resources, self.connectors, [])
self.cm = ResourceManager(self.resources, self.connectors, [])
def test_basic(self):
self.clocks = [
("clk100", 100),
(("clk50", 0), 50),
]
self.cm = ConstraintManager(self.resources, self.connectors, self.clocks)
self.cm = ResourceManager(self.resources, self.connectors, self.clocks)
self.assertEqual(self.cm.resources, {
("clk100", 0): self.resources[0],
("clk50", 0): self.resources[1],
@ -177,8 +177,8 @@ class ConstraintManagerTestCase(FHDLTestCase):
def test_wrong_resources_duplicate(self):
with self.assertRaises(NameError,
msg="Trying to add (resource user_led 0 (pins o A1) ), but "
"(resource user_led 0 (pins o A0) ) has the same name and number"):
msg="Trying to add (resource user_led 0 (pins o A1) (attrs )), but "
"(resource user_led 0 (pins o A0) (attrs )) has the same name and number"):
self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))])
def test_wrong_connectors(self):
@ -192,7 +192,7 @@ class ConstraintManagerTestCase(FHDLTestCase):
self.cm.add_connectors([Connector("pmod", 0, "1 2")])
def test_wrong_lookup(self):
with self.assertRaises(ConstraintError,
with self.assertRaises(ResourceError,
msg="Resource user_led#1 does not exist"):
r = self.cm.lookup("user_led", 1)
@ -203,7 +203,7 @@ class ConstraintManagerTestCase(FHDLTestCase):
self.cm.add_clock("i2c", 0, 10e6)
def test_wrong_frequency_tristate(self):
with self.assertRaises(ConstraintError,
with self.assertRaises(ResourceError,
msg="Cannot constrain frequency of resource clk50#0 because "
"it has been requested as a tristate buffer"):
self.cm.add_clock("clk50", 0, 20e6)
@ -211,13 +211,13 @@ class ConstraintManagerTestCase(FHDLTestCase):
list(self.cm.iter_clock_constraints())
def test_wrong_frequency_duplicate(self):
with self.assertRaises(ConstraintError,
with self.assertRaises(ResourceError,
msg="Resource clk100#0 is already constrained to a frequency of 10.000000 MHz"):
self.cm.add_clock("clk100", 0, 10e6)
self.cm.add_clock("clk100", 0, 5e6)
def test_wrong_request_duplicate(self):
with self.assertRaises(ConstraintError,
with self.assertRaises(ResourceError,
msg="Resource user_led#0 has already been requested"):
self.cm.request("user_led", 0)
self.cm.request("user_led", 0)
@ -238,7 +238,8 @@ class ConstraintManagerTestCase(FHDLTestCase):
def test_wrong_request_with_dir_dict(self):
with self.assertRaises(TypeError,
msg="Directions must be a dict, not 'i', because (resource i2c 0 (subsignal scl "
"(pins o N10) ) (subsignal sda (pins io N11) ) ) has subsignals"):
"(pins o N10) (attrs )) (subsignal sda (pins io N11) (attrs )) (attrs )) "
"has subsignals"):
i2c = self.cm.request("i2c", 0, dir="i")
def test_wrong_request_with_wrong_xdr(self):
@ -249,5 +250,6 @@ class ConstraintManagerTestCase(FHDLTestCase):
def test_wrong_request_with_xdr_dict(self):
with self.assertRaises(TypeError,
msg="Data rate must be a dict, not 2, because (resource i2c 0 (subsignal scl "
"(pins o N10) ) (subsignal sda (pins io N11) ) ) has subsignals"):
"(pins o N10) (attrs )) (subsignal sda (pins io N11) (attrs )) (attrs )) "
"has subsignals"):
i2c = self.cm.request("i2c", 0, xdr=2)

View file

@ -70,7 +70,7 @@ class LatticeICE40Platform(TemplatedPlatform):
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
""",
@ -110,33 +110,17 @@ class LatticeICE40Platform(TemplatedPlatform):
"""
]
def iter_ports(self):
for res, pin, port in self._ports:
if isinstance(res.io[0], Pins):
yield port.io
elif isinstance(res.io[0], DiffPairs):
if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
yield port.p
else:
yield port.p
yield port.n
else:
assert False
def should_skip_port_component(self, port, attrs, component):
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
# the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
# between LP/HX and UP series:
# * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting)
# * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting)
if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n":
return True
return False
def iter_port_constraints(self):
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):
if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras
else:
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 _get_io_buffer(self, m, pin, port, extras, o_invert=None):
def _get_io_buffer(self, m, pin, port, attrs, o_invert=None):
def _get_dff(clk, d, q):
m.submodules += Instance("$dff",
p_CLK_POLARITY=0,
@ -160,9 +144,9 @@ class LatticeICE40Platform(TemplatedPlatform):
o_O=y[bit])
return y
if "GLOBAL" in extras:
is_global_input = bool(extras["GLOBAL"])
del extras["GLOBAL"]
if "GLOBAL" in attrs:
is_global_input = bool(attrs["GLOBAL"])
del attrs["GLOBAL"]
else:
is_global_input = False
@ -185,7 +169,7 @@ class LatticeICE40Platform(TemplatedPlatform):
for bit in range(len(port)):
io_args = [
("io", "PACKAGE_PIN", port[bit]),
*(("p", key, value) for key, value in extras.items()),
*(("p", key, value) for key, value in attrs.items()),
]
if "i" not in pin.dir:
@ -242,56 +226,52 @@ class LatticeICE40Platform(TemplatedPlatform):
else:
m.submodules += Instance("SB_IO", *io_args)
def get_input(self, pin, port, extras):
self._check_feature("single-ended input", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
def get_input(self, pin, port, attrs):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port, extras)
self._get_io_buffer(m, pin, port, attrs)
return m
def get_output(self, pin, port, extras):
self._check_feature("single-ended output", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
def get_output(self, pin, port, attrs):
self._check_feature("single-ended output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port, extras)
self._get_io_buffer(m, pin, port, attrs)
return m
def get_tristate(self, pin, port, extras):
self._check_feature("single-ended tristate", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
def get_tristate(self, pin, port, attrs):
self._check_feature("single-ended tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port, extras)
self._get_io_buffer(m, pin, port, attrs)
return m
def get_input_output(self, pin, port, extras):
self._check_feature("single-ended input/output", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
def get_input_output(self, pin, port, attrs):
self._check_feature("single-ended input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port, extras)
self._get_io_buffer(m, pin, port, attrs)
return m
def get_diff_input(self, pin, p_port, n_port, extras):
self._check_feature("differential input", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
# the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
# between LP/HX and UP series:
# * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting)
# * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting)
def get_diff_input(self, pin, p_port, n_port, attrs):
self._check_feature("differential input", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, p_port, extras)
# See comment in should_skip_port_component above.
self._get_io_buffer(m, pin, p_port, attrs)
return m
def get_diff_output(self, pin, p_port, n_port, extras):
self._check_feature("differential output", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
def get_diff_output(self, pin, p_port, n_port, attrs):
self._check_feature("differential output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
# Note that the non-inverting output pin is not driven the same way as a regular
# output pin. The inverter introduces a delay, so for a non-inverting output pin,
# an identical delay is introduced by instantiating a LUT. This makes the waveform
# perfectly symmetric in the xdr=0 case.
self._get_io_buffer(m, pin, p_port, extras, o_invert=False)
self._get_io_buffer(m, pin, n_port, extras, o_invert=True)
self._get_io_buffer(m, pin, p_port, attrs, o_invert=False)
self._get_io_buffer(m, pin, n_port, attrs, o_invert=True)
return m
# Tristate and bidirectional buffers are not supported on iCE40 because it requires external