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 .dsl import Pins, DiffPairs, Attrs, Subsignal, Resource, Connector
from .res import ConstraintError from .res import ResourceError
from .plat import Platform, TemplatedPlatform from .plat import Platform, TemplatedPlatform

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@ class PinsTestCase(FHDLTestCase):
"pmod_0:1": "A1", "pmod_0:1": "A1",
"pmod_0:2": "A2", "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): def test_map_names_recur(self):
p = Pins("0", conn=("pmod", 0)) p = Pins("0", conn=("pmod", 0))
@ -31,7 +31,7 @@ class PinsTestCase(FHDLTestCase):
"pmod_0:0": "ext_0:1", "pmod_0:0": "ext_0:1",
"ext_0:1": "A1", "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): def test_wrong_names(self):
with self.assertRaises(TypeError, with self.assertRaises(TypeError,
@ -51,7 +51,7 @@ class PinsTestCase(FHDLTestCase):
with self.assertRaises(NameError, with self.assertRaises(NameError,
msg="Resource (pins io pmod_0:0 pmod_0:1 pmod_0:2) refers to nonexistent " msg="Resource (pins io pmod_0:0 pmod_0:1 pmod_0:2) refers to nonexistent "
"connector pin pmod_0:1"): "connector pin pmod_0:1"):
list(p.map_names(mapping, p)) p.map_names(mapping, p)
class DiffPairsTestCase(FHDLTestCase): class DiffPairsTestCase(FHDLTestCase):
@ -84,81 +84,90 @@ class DiffPairsTestCase(FHDLTestCase):
dp = DiffPairs("A0", "B0 B1") 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): class SubsignalTestCase(FHDLTestCase):
def test_basic_pins(self): def test_basic_pins(self):
s = Subsignal("a", Pins("A0"), extras={"IOSTANDARD": "LVCMOS33"}) s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33"))
self.assertEqual(repr(s), "(subsignal a (pins io A0) IOSTANDARD=LVCMOS33)") self.assertEqual(repr(s),
"(subsignal a (pins io A0) (attrs IOSTANDARD=LVCMOS33))")
def test_basic_diffpairs(self): def test_basic_diffpairs(self):
s = Subsignal("a", DiffPairs("A0", "B0")) 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): def test_basic_subsignals(self):
s = Subsignal("a", s = Subsignal("a",
Subsignal("b", Pins("A0")), Subsignal("b", Pins("A0")),
Subsignal("c", Pins("A1"))) Subsignal("c", Pins("A1")))
self.assertEqual(repr(s), 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", s = Subsignal("a",
Subsignal("b", Pins("A0")), Subsignal("b", Pins("A0")),
Subsignal("c", Pins("A0"), extras={"SLEW": "FAST"}), Subsignal("c", Pins("A0"), Attrs(SLEW="FAST")),
extras={"IOSTANDARD": "LVCMOS33"}) Attrs(IOSTANDARD="LVCMOS33"))
self.assertEqual(s.extras, {"IOSTANDARD": "LVCMOS33"}) self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"})
self.assertEqual(s.io[0].extras, {"IOSTANDARD": "LVCMOS33"}) self.assertEqual(s.ios[0].attrs, {})
self.assertEqual(s.io[1].extras, {"SLEW": "FAST", "IOSTANDARD": "LVCMOS33"}) self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"})
def test_empty_io(self): def test_attrs_many(self):
with self.assertRaises(TypeError, msg="Missing I/O constraints"): 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") s = Subsignal("a")
def test_wrong_io(self): def test_wrong_io(self):
with self.assertRaises(TypeError, 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") s = Subsignal("a", "wrong")
def test_wrong_pins(self): def test_wrong_pins(self):
with self.assertRaises(TypeError, with self.assertRaises(TypeError,
msg="Pins and DiffPairs cannot be followed by more I/O constraints, but " msg="Pins and DiffPairs are incompatible with other location or subsignal "
"(pins io A0) is followed by (pins io A1)"): "constraints, but (pins io A1) appears after (pins io A0)"):
s = Subsignal("a", Pins("A0"), Pins("A1")) s = Subsignal("a", Pins("A0"), Pins("A1"))
def test_wrong_diffpairs(self): def test_wrong_diffpairs(self):
with self.assertRaises(TypeError, with self.assertRaises(TypeError,
msg="Pins and DiffPairs cannot be followed by more I/O constraints, but " msg="Pins and DiffPairs are incompatible with other location or subsignal "
"(diffpairs io (p A0) (n B0)) is followed by " "constraints, but (pins io A1) appears after (diffpairs io (p A0) (n B0))"):
"(pins io A1)"):
s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1")) s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1"))
def test_wrong_subsignals(self): def test_wrong_subsignals(self):
with self.assertRaises(TypeError, with self.assertRaises(TypeError,
msg="A Subsignal can only be followed by more Subsignals, but " msg="Pins and DiffPairs are incompatible with other location or subsignal "
"(subsignal b (pins io A0) ) is followed by (pins io B0)"): "constraints, but (pins io B0) appears after (subsignal b (pins io A0) "
"(attrs ))"):
s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0")) 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): class ResourceTestCase(FHDLTestCase):
def test_basic(self): def test_basic(self):
r = Resource("serial", 0, r = Resource("serial", 0,
Subsignal("tx", Pins("A0", dir="o")), Subsignal("tx", Pins("A0", dir="o")),
Subsignal("rx", Pins("A1", dir="i")), Subsignal("rx", Pins("A1", dir="i")),
extras={"IOSTANDARD": "LVCMOS33"}) Attrs(IOSTANDARD="LVCMOS33"))
self.assertEqual(repr(r), "(resource serial 0" self.assertEqual(repr(r), "(resource serial 0"
" (subsignal tx (pins o A0) IOSTANDARD=LVCMOS33)" " (subsignal tx (pins o A0) (attrs ))"
" (subsignal rx (pins i A1) IOSTANDARD=LVCMOS33)" " (subsignal rx (pins i A1) (attrs ))"
" IOSTANDARD=LVCMOS33)") " (attrs IOSTANDARD=LVCMOS33))")
class ConnectorTestCase(FHDLTestCase): class ConnectorTestCase(FHDLTestCase):

View file

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

View file

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