from collections import OrderedDict from ..hdl.ast import * from ..hdl.rec import * from ..lib.io import * from .dsl import * __all__ = ["ResourceError", "ResourceManager"] class ResourceError(Exception): pass class ResourceManager: def __init__(self, resources, connectors): self.resources = OrderedDict() self._requested = OrderedDict() self._phys_reqd = OrderedDict() self.connectors = OrderedDict() self._conn_pins = OrderedDict() # Constraint lists self._ports = [] self._clocks = SignalDict() self.add_resources(resources) self.add_connectors(connectors) def add_resources(self, resources): 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: raise NameError("Trying to add {!r}, but {!r} has the same name and number" .format(conn, self.connectors[conn.name, conn.number])) self.connectors[conn.name, conn.number] = conn for conn_pin, plat_pin in conn: assert conn_pin not in self._conn_pins self._conn_pins[conn_pin] = plat_pin def lookup(self, name, number=0): if (name, number) not in self.resources: 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 ResourceError("Resource {}#{} has already been requested" .format(name, number)) def merge_options(subsignal, dir, xdr): if isinstance(subsignal.ios[0], Subsignal): if dir is None: dir = dict() if xdr is None: xdr = dict() if not isinstance(dir, dict): raise TypeError("Directions must be a dict, not {!r}, because {!r} " "has subsignals" .format(dir, subsignal)) if not isinstance(xdr, dict): raise TypeError("Data rate must be a dict, not {!r}, because {!r} " "has subsignals" .format(xdr, subsignal)) for sub in subsignal.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.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.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.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.ios[0], xdr)) return dir, xdr def resolve(resource, dir, xdr, name, attrs): 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 if isinstance(resource.ios[0], Subsignal): fields = OrderedDict() for sub in resource.ios: fields[sub.name] = resolve(sub, dir[sub.name], xdr[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(resource.ios[0], (Pins, DiffPairs)): phys = resource.ios[0] if isinstance(phys, Pins): phys_names = phys.names port = Record([("io", len(phys))], name=name) if isinstance(phys, DiffPairs): phys_names = phys.p.names + phys.n.names port = Record([("p", len(phys)), ("n", len(phys))], name=name) if dir == "-": pin = None else: pin = Pin(len(phys), dir, xdr=xdr, name=name) 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 self._ports.append((resource, pin, port, attrs)) if pin is not None and resource.clock is not None: self.add_clock_constraint(pin.i, resource.clock.frequency) return pin if pin is not None else port else: assert False # :nocov: value = resolve(resource, *merge_options(resource, dir, xdr), 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, attrs in self._ports: if pin is None: continue if isinstance(res.ios[0], Pins): yield pin, port.io, attrs, res.ios[0].invert def iter_differential_pins(self): for res, pin, port, attrs in self._ports: if pin is None: continue if isinstance(res.ios[0], DiffPairs): yield pin, port.p, port.n, attrs, res.ios[0].invert def should_skip_port_component(self, port, attrs, component): return False def iter_ports(self): 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, 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, attrs in self.iter_port_constraints(): if len(pin_names) == 1: yield port_name, pin_names[0], attrs else: for bit, pin_name in enumerate(pin_names): yield "{}[{}]".format(port_name, bit), pin_name, attrs def add_clock_constraint(self, clock, frequency): if not isinstance(clock, Signal): raise TypeError("Object {!r} is not a Signal".format(clock)) if not isinstance(frequency, (int, float)): raise TypeError("Frequency must be a number, not {!r}".format(frequency)) if clock in self._clocks: raise ValueError("Cannot add clock constraint on {!r}, which is already constrained " "to {} Hz" .format(clock, self._clocks[clock])) else: self._clocks[clock] = float(frequency) def iter_clock_constraints(self): # Back-propagate constraints through the input buffer. For clock constraints on pins # (the majority of cases), toolchains work better if the constraint is defined on the pin # and not on the buffered internal net; and if the toolchain is advanced enough that # it considers clock phase and delay of the input buffer, it is *necessary* to define # the constraint on the pin to match the designer's expectation of phase being referenced # to the pin. # # Constraints on nets with no corresponding input pin (e.g. PLL or SERDES outputs) are not # affected. pin_i_to_port = SignalDict() for res, pin, port, attrs in self._ports: if hasattr(pin, "i"): if isinstance(res.ios[0], Pins): pin_i_to_port[pin.i] = port.io elif isinstance(res.ios[0], DiffPairs): pin_i_to_port[pin.i] = port.p else: assert False for net_signal, frequency in self._clocks.items(): port_signal = pin_i_to_port.get(net_signal) yield net_signal, port_signal, frequency