build.{dsl,res,plat}: apply clock constraints to signals, not resources.
This adds the Clock() build DSL element, and adds a resource manager function add_clock_constraint() that takes a Pin or a Signal. Note that not all platforms, in particular not any nextpnr platforms at the moment, can add constraints on arbitrary signals. Fixes #86.
This commit is contained in:
parent
ab3f103e5a
commit
c9879c795b
7 changed files with 155 additions and 109 deletions
|
|
@ -1,3 +1,3 @@
|
|||
from .dsl import Pins, DiffPairs, Attrs, Subsignal, Resource, Connector
|
||||
from .dsl import *
|
||||
from .res import ResourceError
|
||||
from .plat import Platform, TemplatedPlatform
|
||||
from .plat import *
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
|
||||
__all__ = ["Pins", "DiffPairs", "Attrs", "Subsignal", "Resource", "Connector"]
|
||||
__all__ = ["Pins", "DiffPairs", "Attrs", "Clock", "Subsignal", "Resource", "Connector"]
|
||||
|
||||
|
||||
class Pins:
|
||||
|
|
@ -83,11 +83,27 @@ class Attrs(OrderedDict):
|
|||
for k, v in self.items()))
|
||||
|
||||
|
||||
class Clock:
|
||||
def __init__(self, frequency):
|
||||
if not isinstance(frequency, (float, int)):
|
||||
raise TypeError("Clock frequency must be a number")
|
||||
|
||||
self.frequency = float(frequency)
|
||||
|
||||
@property
|
||||
def period(self):
|
||||
return 1 / self.frequency
|
||||
|
||||
def __repr__(self):
|
||||
return "(clock {})".format(self.frequency)
|
||||
|
||||
|
||||
class Subsignal:
|
||||
def __init__(self, name, *args):
|
||||
self.name = name
|
||||
self.ios = []
|
||||
self.attrs = Attrs()
|
||||
self.clock = None
|
||||
|
||||
if not args:
|
||||
raise ValueError("Missing I/O constraints")
|
||||
|
|
@ -108,15 +124,33 @@ class Subsignal:
|
|||
.format(arg, self.ios[-1]))
|
||||
elif isinstance(arg, Attrs):
|
||||
self.attrs.update(arg)
|
||||
elif isinstance(arg, Clock):
|
||||
if self.ios and isinstance(self.ios[-1], (Pins, DiffPairs)):
|
||||
if self.clock is None:
|
||||
self.clock = arg
|
||||
else:
|
||||
raise ValueError("Clock constraint can be applied only once")
|
||||
else:
|
||||
raise TypeError("Clock constraint can only be applied to Pins or DiffPairs, "
|
||||
"not {!r}"
|
||||
.format(self.ios[-1]))
|
||||
else:
|
||||
raise TypeError("I/O constraint must be one of Pins, DiffPairs, Subsignal, "
|
||||
"or Attrs, not {!r}"
|
||||
raise TypeError("Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, "
|
||||
"or Clock, not {!r}"
|
||||
.format(arg))
|
||||
|
||||
def _content_repr(self):
|
||||
parts = []
|
||||
for io in self.ios:
|
||||
parts.append(repr(io))
|
||||
if self.clock is not None:
|
||||
parts.append(repr(self.clock))
|
||||
if self.attrs:
|
||||
parts.append(repr(self.attrs))
|
||||
return " ".join(parts)
|
||||
|
||||
def __repr__(self):
|
||||
return "(subsignal {} {} {})".format(self.name,
|
||||
" ".join(map(repr, self.ios)),
|
||||
repr(self.attrs))
|
||||
return "(subsignal {} {})".format(self.name, self._content_repr())
|
||||
|
||||
|
||||
class Resource(Subsignal):
|
||||
|
|
@ -126,9 +160,7 @@ class Resource(Subsignal):
|
|||
self.number = number
|
||||
|
||||
def __repr__(self):
|
||||
return "(resource {} {} {} {})".format(self.name, self.number,
|
||||
" ".join(map(repr, self.ios)),
|
||||
repr(self.attrs))
|
||||
return "(resource {} {} {})".format(self.name, self.number, self._content_repr())
|
||||
|
||||
|
||||
class Connector:
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@ __all__ = ["Platform", "TemplatedPlatform"]
|
|||
class Platform(ResourceManager, metaclass=ABCMeta):
|
||||
resources = abstractproperty()
|
||||
connectors = abstractproperty()
|
||||
clocks = abstractproperty()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.resources, self.connectors, self.clocks)
|
||||
super().__init__(self.resources, self.connectors)
|
||||
|
||||
self.extra_files = OrderedDict()
|
||||
|
||||
|
|
@ -267,7 +266,7 @@ class TemplatedPlatform(Platform):
|
|||
plan = BuildPlan(script="build_{}".format(name))
|
||||
for filename_tpl, content_tpl in self.file_templates.items():
|
||||
plan.add_file(render(filename_tpl, origin=filename_tpl),
|
||||
render(content_tpl, origin=filename_tpl))
|
||||
render(content_tpl, origin=content_tpl))
|
||||
for filename, content in self.extra_files.items():
|
||||
plan.add_file(filename, content)
|
||||
return plan
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from .. import *
|
||||
from ..hdl.ast import *
|
||||
from ..hdl.rec import *
|
||||
from ..lib.io import *
|
||||
|
||||
|
|
@ -15,24 +15,19 @@ class ResourceError(Exception):
|
|||
|
||||
|
||||
class ResourceManager:
|
||||
def __init__(self, resources, connectors, clocks):
|
||||
def __init__(self, resources, connectors):
|
||||
self.resources = OrderedDict()
|
||||
self._requested = OrderedDict()
|
||||
|
||||
self.connectors = OrderedDict()
|
||||
self._conn_pins = OrderedDict()
|
||||
|
||||
self.clocks = OrderedDict()
|
||||
|
||||
# Constraint lists
|
||||
self._ports = []
|
||||
self._clocks = SignalDict()
|
||||
|
||||
self.add_resources(resources)
|
||||
self.add_connectors(connectors)
|
||||
for name_number, frequency in clocks:
|
||||
if not isinstance(name_number, tuple):
|
||||
name_number = (name_number, 0)
|
||||
self.add_clock(*name_number, frequency)
|
||||
|
||||
def add_resources(self, resources):
|
||||
for res in resources:
|
||||
|
|
@ -56,19 +51,6 @@ class ResourceManager:
|
|||
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.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 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 ResourceError("Resource {}#{} does not exist"
|
||||
|
|
@ -138,12 +120,15 @@ class ResourceManager:
|
|||
port = Record([("p", len(phys)),
|
||||
("n", len(phys))], name=name)
|
||||
if dir == "-":
|
||||
self._ports.append((resource, None, port, attrs))
|
||||
return port
|
||||
pin = None
|
||||
else:
|
||||
pin = Pin(len(phys), dir, xdr, name=name)
|
||||
self._ports.append((resource, pin, port, attrs))
|
||||
return pin
|
||||
pin = Pin(len(phys), dir, xdr, 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, resource.clock.frequency)
|
||||
|
||||
return pin if pin is not None else port
|
||||
|
||||
else:
|
||||
assert False # :nocov:
|
||||
|
|
@ -206,19 +191,33 @@ class ResourceManager:
|
|||
for bit, pin_name in enumerate(pin_names):
|
||||
yield "{}[{}]".format(port_name, bit), pin_name, attrs
|
||||
|
||||
def iter_clock_constraints(self):
|
||||
for name, number in self.clocks.keys() & self._requested.keys():
|
||||
resource = self.resources[name, number]
|
||||
period = self.clocks[name, number]
|
||||
pin = self._requested[name, number]
|
||||
if pin.dir == "io":
|
||||
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.ios[0], DiffPairs):
|
||||
port_name = "{}__p".format(pin.name)
|
||||
def add_clock_constraint(self, clock, frequency):
|
||||
if not isinstance(clock, (Signal, Pin)):
|
||||
raise TypeError("Object {!r} is not a Signal or Pin".format(clock))
|
||||
if not isinstance(frequency, (int, float)):
|
||||
raise TypeError("Frequency must be a number, not {!r}".format(frequency))
|
||||
|
||||
if isinstance(clock, Pin):
|
||||
for res, pin, port, attrs in self._ports:
|
||||
if clock is pin:
|
||||
if isinstance(res.ios[0], Pins):
|
||||
clock = port.io
|
||||
elif isinstance(res.ios[0], DiffPairs):
|
||||
clock = port.p
|
||||
else:
|
||||
assert False
|
||||
break
|
||||
else:
|
||||
assert False
|
||||
yield (port_name, period)
|
||||
raise ValueError("Cannot add clock constraint on a Pin {!r} that is not "
|
||||
"a previously requested resource"
|
||||
.format(clock))
|
||||
|
||||
if clock in self._clocks:
|
||||
raise ValueError("Cannot add clock constraint on {!r}, which is already constrained "
|
||||
"to {} Hz"
|
||||
.format(clock, self._clocks[clock]))
|
||||
|
||||
self._clocks[clock] = float(frequency)
|
||||
|
||||
def iter_clock_constraints(self):
|
||||
return iter(self._clocks.items())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue