2019-04-26 06:37:08 -06:00
|
|
|
from collections import OrderedDict
|
|
|
|
|
2019-06-05 02:48:36 -06:00
|
|
|
from ..hdl.ast import *
|
2019-04-26 06:37:08 -06:00
|
|
|
from ..hdl.rec import *
|
|
|
|
from ..lib.io import *
|
|
|
|
|
|
|
|
from .dsl import *
|
|
|
|
|
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
__all__ = ["ResourceError", "ResourceManager"]
|
2019-04-26 06:37:08 -06:00
|
|
|
|
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
class ResourceError(Exception):
|
2019-04-26 06:37:08 -06:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
class ResourceManager:
|
2019-06-05 02:48:36 -06:00
|
|
|
def __init__(self, resources, connectors):
|
2019-04-26 06:37:08 -06:00
|
|
|
self.resources = OrderedDict()
|
2019-06-05 01:02:08 -06:00
|
|
|
self._requested = OrderedDict()
|
2019-07-03 09:07:44 -06:00
|
|
|
self._phys_reqd = OrderedDict()
|
2019-06-05 01:02:08 -06:00
|
|
|
|
2019-06-03 09:02:15 -06:00
|
|
|
self.connectors = OrderedDict()
|
2019-06-05 01:02:08 -06:00
|
|
|
self._conn_pins = OrderedDict()
|
|
|
|
|
|
|
|
# Constraint lists
|
2019-04-26 06:37:08 -06:00
|
|
|
self._ports = []
|
2019-06-05 02:48:36 -06:00
|
|
|
self._clocks = SignalDict()
|
2019-04-26 06:37:08 -06:00
|
|
|
|
|
|
|
self.add_resources(resources)
|
2019-06-03 09:02:15 -06:00
|
|
|
self.add_connectors(connectors)
|
2019-04-26 06:37:08 -06:00
|
|
|
|
|
|
|
def add_resources(self, resources):
|
2019-06-03 09:02:15 -06:00
|
|
|
for res in resources:
|
|
|
|
if not isinstance(res, Resource):
|
|
|
|
raise TypeError("Object {!r} is not a Resource".format(res))
|
|
|
|
if (res.name, res.number) in self.resources:
|
|
|
|
raise NameError("Trying to add {!r}, but {!r} has the same name and number"
|
|
|
|
.format(res, self.resources[res.name, res.number]))
|
|
|
|
self.resources[res.name, res.number] = res
|
|
|
|
|
|
|
|
def add_connectors(self, connectors):
|
|
|
|
for conn in connectors:
|
|
|
|
if not isinstance(conn, Connector):
|
|
|
|
raise TypeError("Object {!r} is not a Connector".format(conn))
|
|
|
|
if (conn.name, conn.number) in self.connectors:
|
2019-04-26 06:37:08 -06:00
|
|
|
raise NameError("Trying to add {!r}, but {!r} has the same name and number"
|
2019-06-03 09:02:15 -06:00
|
|
|
.format(conn, self.connectors[conn.name, conn.number]))
|
|
|
|
self.connectors[conn.name, conn.number] = conn
|
|
|
|
|
|
|
|
for conn_pin, plat_pin in conn:
|
2019-06-05 01:02:08 -06:00
|
|
|
assert conn_pin not in self._conn_pins
|
|
|
|
self._conn_pins[conn_pin] = plat_pin
|
2019-04-26 06:37:08 -06:00
|
|
|
|
2019-06-02 20:54:17 -06:00
|
|
|
def lookup(self, name, number=0):
|
2019-04-26 06:37:08 -06:00
|
|
|
if (name, number) not in self.resources:
|
2019-06-05 01:02:08 -06:00
|
|
|
raise ResourceError("Resource {}#{} does not exist"
|
2019-06-04 04:23:27 -06:00
|
|
|
.format(name, number))
|
2019-04-26 06:37:08 -06:00
|
|
|
return self.resources[name, number]
|
|
|
|
|
2019-06-02 20:54:17 -06:00
|
|
|
def request(self, name, number=0, *, dir=None, xdr=None):
|
2019-04-26 06:37:08 -06:00
|
|
|
resource = self.lookup(name, number)
|
2019-06-03 09:02:15 -06:00
|
|
|
if (resource.name, resource.number) in self._requested:
|
2019-06-05 01:02:08 -06:00
|
|
|
raise ResourceError("Resource {}#{} has already been requested"
|
|
|
|
.format(name, number))
|
2019-04-26 06:37:08 -06:00
|
|
|
|
2019-06-02 19:28:34 -06:00
|
|
|
def merge_options(subsignal, dir, xdr):
|
2019-06-05 01:02:08 -06:00
|
|
|
if isinstance(subsignal.ios[0], Subsignal):
|
2019-04-26 06:37:08 -06:00
|
|
|
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))
|
2019-06-05 01:02:08 -06:00
|
|
|
for sub in subsignal.ios:
|
2019-04-26 06:37:08 -06:00
|
|
|
sub_dir = dir.get(sub.name, None)
|
|
|
|
sub_xdr = xdr.get(sub.name, None)
|
2019-06-02 19:28:34 -06:00
|
|
|
dir[sub.name], xdr[sub.name] = merge_options(sub, sub_dir, sub_xdr)
|
2019-04-26 06:37:08 -06:00
|
|
|
else:
|
|
|
|
if dir is None:
|
2019-06-05 01:02:08 -06:00
|
|
|
dir = subsignal.ios[0].dir
|
2019-04-26 06:37:08 -06:00
|
|
|
if xdr is None:
|
2019-06-02 21:32:30 -06:00
|
|
|
xdr = 0
|
2019-06-02 22:39:05 -06:00
|
|
|
if dir not in ("i", "o", "oe", "io", "-"):
|
|
|
|
raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", \"io\", "
|
|
|
|
"or \"-\", not {!r}"
|
2019-04-26 06:37:08 -06:00
|
|
|
.format(dir))
|
2019-06-05 01:02:08 -06:00
|
|
|
if dir != subsignal.ios[0].dir and \
|
|
|
|
not (subsignal.ios[0].dir == "io" or dir == "-"):
|
2019-04-26 06:37:08 -06:00
|
|
|
raise ValueError("Direction of {!r} cannot be changed from \"{}\" to \"{}\"; "
|
2019-06-02 22:39:05 -06:00
|
|
|
"direction can be changed from \"io\" to \"i\", \"o\", or "
|
|
|
|
"\"oe\", or from anything to \"-\""
|
2019-06-05 01:02:08 -06:00
|
|
|
.format(subsignal.ios[0], subsignal.ios[0].dir, dir))
|
2019-06-02 21:32:30 -06:00
|
|
|
if not isinstance(xdr, int) or xdr < 0:
|
|
|
|
raise ValueError("Data rate of {!r} must be a non-negative integer, not {!r}"
|
2019-06-05 01:02:08 -06:00
|
|
|
.format(subsignal.ios[0], xdr))
|
2019-04-26 06:37:08 -06:00
|
|
|
return dir, xdr
|
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
def resolve(resource, dir, xdr, name, attrs):
|
2019-07-08 05:15:04 -06:00
|
|
|
for attr_key, attr_value in attrs.items():
|
|
|
|
if hasattr(attr_value, "__call__"):
|
|
|
|
attr_value = attr_value(self)
|
|
|
|
assert attr_value is None or isinstance(attr_value, str)
|
|
|
|
if attr_value is None:
|
|
|
|
del attrs[attr_key]
|
|
|
|
else:
|
|
|
|
attrs[attr_key] = attr_value
|
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
if isinstance(resource.ios[0], Subsignal):
|
2019-04-26 06:37:08 -06:00
|
|
|
fields = OrderedDict()
|
2019-06-05 01:02:08 -06:00
|
|
|
for sub in resource.ios:
|
2019-06-02 19:28:34 -06:00
|
|
|
fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
|
2019-06-05 01:02:08 -06:00
|
|
|
name="{}__{}".format(name, sub.name),
|
2019-07-08 05:15:04 -06:00
|
|
|
attrs={**attrs, **sub.attrs})
|
2019-06-02 19:28:34 -06:00
|
|
|
return Record([
|
2019-04-26 06:37:08 -06:00
|
|
|
(f_name, f.layout) for (f_name, f) in fields.items()
|
|
|
|
], fields=fields, name=name)
|
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
elif isinstance(resource.ios[0], (Pins, DiffPairs)):
|
|
|
|
phys = resource.ios[0]
|
2019-06-02 19:28:34 -06:00
|
|
|
if isinstance(phys, Pins):
|
2019-07-03 09:07:44 -06:00
|
|
|
phys_names = phys.names
|
2019-06-02 21:17:20 -06:00
|
|
|
port = Record([("io", len(phys))], name=name)
|
2019-06-02 19:28:34 -06:00
|
|
|
if isinstance(phys, DiffPairs):
|
2019-07-03 09:07:44 -06:00
|
|
|
phys_names = phys.p.names + phys.n.names
|
2019-06-02 21:17:20 -06:00
|
|
|
port = Record([("p", len(phys)),
|
|
|
|
("n", len(phys))], name=name)
|
|
|
|
if dir == "-":
|
2019-06-05 02:48:36 -06:00
|
|
|
pin = None
|
2019-06-02 21:17:20 -06:00
|
|
|
else:
|
2019-10-16 13:50:04 -06:00
|
|
|
pin = Pin(len(phys), dir, xdr=xdr, name=name)
|
2019-07-03 09:07:44 -06:00
|
|
|
|
|
|
|
for phys_name in phys_names:
|
|
|
|
if phys_name in self._phys_reqd:
|
|
|
|
raise ResourceError("Resource component {} uses physical pin {}, but it "
|
|
|
|
"is already used by resource component {} that was "
|
|
|
|
"requested earlier"
|
|
|
|
.format(name, phys_name, self._phys_reqd[phys_name]))
|
|
|
|
self._phys_reqd[phys_name] = name
|
|
|
|
|
2019-06-05 02:48:36 -06:00
|
|
|
self._ports.append((resource, pin, port, attrs))
|
|
|
|
|
|
|
|
if pin is not None and resource.clock is not None:
|
build.res: simplify clock constraints.
Before this commit, it was possible to set and get clock constraints
placed on Pin objects. This was not a very good implementation, since
it relied on matching the identity of the provided Pin object to
a previously requested one. The only reason it worked like that is
deficiencies in nextpnr.
Since then, nextpnr has been fixed to allow setting constraints on
arbitrary nets. Correspondingly, backends that are using Synplify
were changed to use [get_nets] instead of [get_ports] in SDC files.
However, in some situations, Synplify does not allow specifying
ports in [get_nets]. (In fact, nextpnr had a similar problem, but
it has also been fixed.)
The simplest way to address this is to refer to the interior net
(after the input buffer), which always works. The only downside
of this is that requesting a clock as a raw pin using
platform.request("clk", dir="-")
and directly applying a constraint to it could fail in some cases.
This is not a significant issue.
2019-09-21 08:12:29 -06:00
|
|
|
self.add_clock_constraint(pin.i, resource.clock.frequency)
|
2019-06-05 02:48:36 -06:00
|
|
|
|
|
|
|
return pin if pin is not None else port
|
2019-06-02 21:17:20 -06:00
|
|
|
|
2019-04-26 06:37:08 -06:00
|
|
|
else:
|
|
|
|
assert False # :nocov:
|
|
|
|
|
2019-06-02 19:28:34 -06:00
|
|
|
value = resolve(resource,
|
|
|
|
*merge_options(resource, dir, xdr),
|
2019-06-05 01:02:08 -06:00
|
|
|
name="{}_{}".format(resource.name, resource.number),
|
|
|
|
attrs=resource.attrs)
|
2019-06-03 09:02:15 -06:00
|
|
|
self._requested[resource.name, resource.number] = value
|
2019-04-26 06:37:08 -06:00
|
|
|
return value
|
|
|
|
|
2019-06-02 19:58:43 -06:00
|
|
|
def iter_single_ended_pins(self):
|
2019-06-05 01:02:08 -06:00
|
|
|
for res, pin, port, attrs in self._ports:
|
2019-06-02 21:17:20 -06:00
|
|
|
if pin is None:
|
|
|
|
continue
|
2019-06-05 01:02:08 -06:00
|
|
|
if isinstance(res.ios[0], Pins):
|
2019-06-12 08:42:39 -06:00
|
|
|
yield pin, port.io, attrs, res.ios[0].invert
|
2019-06-02 19:58:43 -06:00
|
|
|
|
|
|
|
def iter_differential_pins(self):
|
2019-06-05 01:02:08 -06:00
|
|
|
for res, pin, port, attrs in self._ports:
|
2019-06-02 21:17:20 -06:00
|
|
|
if pin is None:
|
|
|
|
continue
|
2019-06-05 01:02:08 -06:00
|
|
|
if isinstance(res.ios[0], DiffPairs):
|
2019-06-12 08:42:39 -06:00
|
|
|
yield pin, port.p, port.n, attrs, res.ios[0].invert
|
2019-06-05 01:02:08 -06:00
|
|
|
|
|
|
|
def should_skip_port_component(self, port, attrs, component):
|
|
|
|
return False
|
2019-06-02 19:58:43 -06:00
|
|
|
|
2019-04-26 06:37:08 -06:00
|
|
|
def iter_ports(self):
|
2019-06-05 01:02:08 -06:00
|
|
|
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
|
2019-06-02 19:58:43 -06:00
|
|
|
else:
|
|
|
|
assert False
|
2019-04-26 06:37:08 -06:00
|
|
|
|
2019-06-03 02:38:12 -06:00
|
|
|
def iter_port_constraints(self):
|
2019-06-05 01:02:08 -06:00
|
|
|
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
|
2019-06-02 19:58:43 -06:00
|
|
|
else:
|
|
|
|
assert False
|
2019-04-26 06:37:08 -06:00
|
|
|
|
2019-06-04 02:37:52 -06:00
|
|
|
def iter_port_constraints_bits(self):
|
2019-06-05 01:02:08 -06:00
|
|
|
for port_name, pin_names, attrs in self.iter_port_constraints():
|
2019-06-04 02:37:52 -06:00
|
|
|
if len(pin_names) == 1:
|
2019-06-05 01:02:08 -06:00
|
|
|
yield port_name, pin_names[0], attrs
|
2019-06-04 02:37:52 -06:00
|
|
|
else:
|
|
|
|
for bit, pin_name in enumerate(pin_names):
|
2019-06-05 01:02:08 -06:00
|
|
|
yield "{}[{}]".format(port_name, bit), pin_name, attrs
|
2019-06-04 02:37:52 -06:00
|
|
|
|
2019-06-05 06:51:53 -06:00
|
|
|
def add_clock_constraint(self, clock, frequency):
|
build.res: simplify clock constraints.
Before this commit, it was possible to set and get clock constraints
placed on Pin objects. This was not a very good implementation, since
it relied on matching the identity of the provided Pin object to
a previously requested one. The only reason it worked like that is
deficiencies in nextpnr.
Since then, nextpnr has been fixed to allow setting constraints on
arbitrary nets. Correspondingly, backends that are using Synplify
were changed to use [get_nets] instead of [get_ports] in SDC files.
However, in some situations, Synplify does not allow specifying
ports in [get_nets]. (In fact, nextpnr had a similar problem, but
it has also been fixed.)
The simplest way to address this is to refer to the interior net
(after the input buffer), which always works. The only downside
of this is that requesting a clock as a raw pin using
platform.request("clk", dir="-")
and directly applying a constraint to it could fail in some cases.
This is not a significant issue.
2019-09-21 08:12:29 -06:00
|
|
|
if not isinstance(clock, Signal):
|
|
|
|
raise TypeError("Object {!r} is not a Signal".format(clock))
|
2019-06-05 06:51:53 -06:00
|
|
|
if not isinstance(frequency, (int, float)):
|
|
|
|
raise TypeError("Frequency must be a number, not {!r}".format(frequency))
|
|
|
|
|
2019-06-05 02:48:36 -06:00
|
|
|
if clock in self._clocks:
|
|
|
|
raise ValueError("Cannot add clock constraint on {!r}, which is already constrained "
|
|
|
|
"to {} Hz"
|
|
|
|
.format(clock, self._clocks[clock]))
|
2019-06-05 06:51:53 -06:00
|
|
|
else:
|
|
|
|
self._clocks[clock] = float(frequency)
|
2019-06-05 02:48:36 -06:00
|
|
|
|
|
|
|
def iter_clock_constraints(self):
|
|
|
|
return iter(self._clocks.items())
|