1 from collections
import OrderedDict
4 __all__
= ["Pins", "PinsN", "DiffPairs", "DiffPairsN",
5 "Attrs", "Clock", "Subsignal", "Resource", "Connector"]
9 def __init__(self
, names
, *, dir="io", invert
=False, conn
=None, assert_width
=None):
10 if not isinstance(names
, str):
11 raise TypeError("Names must be a whitespace-separated string, not {!r}"
16 conn_name
, conn_number
= conn
17 if not (isinstance(conn_name
, str) and isinstance(conn_number
, (int, str))):
18 raise TypeError("Connector must be None or a pair of string (connector name) and "
19 "integer/string (connector number), not {!r}"
21 names
= ["{}_{}:{}".format(conn_name
, conn_number
, name
) for name
in names
]
23 if dir not in ("i", "o", "io", "oe"):
24 raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not {!r}"
27 if assert_width
is not None and len(names
) != assert_width
:
28 raise AssertionError("{} names are specified ({}), but {} names are expected"
29 .format(len(names
), " ".join(names
), assert_width
))
33 self
.invert
= bool(invert
)
36 return len(self
.names
)
39 return iter(self
.names
)
41 def map_names(self
, mapping
, resource
):
43 for name
in self
.names
:
45 if name
not in mapping
:
46 raise NameError("Resource {!r} refers to nonexistent connector pin {}"
47 .format(resource
, name
))
49 mapped_names
.append(name
)
53 return "(pins{} {} {})".format("-n" if self
.invert
else "",
54 self
.dir, " ".join(self
.names
))
57 def PinsN(*args
, **kwargs
):
58 pins
= Pins(*args
, **kwargs
)
64 def __init__(self
, p
, n
, *, dir="io", conn
=None, assert_width
=None):
65 self
.p
= Pins(p
, dir=dir, conn
=conn
, assert_width
=assert_width
)
66 self
.n
= Pins(n
, dir=dir, conn
=conn
, assert_width
=assert_width
)
68 if len(self
.p
.names
) != len(self
.n
.names
):
69 raise TypeError("Positive and negative pins must have the same width, but {!r} "
71 .format(self
.p
, self
.n
))
77 return len(self
.p
.names
)
80 return zip(self
.p
.names
, self
.n
.names
)
83 return "(diffpairs{} {} (p {}) (n {}))".format("-n" if self
.invert
else "",
84 self
.dir, " ".join(self
.p
.names
), " ".join(self
.n
.names
))
87 def DiffPairsN(*args
, **kwargs
):
88 diff_pairs
= DiffPairs(*args
, **kwargs
)
89 diff_pairs
.invert
= True
93 class Attrs(OrderedDict
):
94 def __init__(self
, **attrs
):
95 for key
, value
in attrs
.items():
96 if not (value
is None or isinstance(value
, (str, int)) or hasattr(value
, "__call__")):
97 raise TypeError("Value of attribute {} must be None, int, str, or callable, "
101 super().__init
__(**attrs
)
105 for key
, value
in self
.items():
107 items
.append("!" + key
)
109 items
.append(key
+ "=" + repr(value
))
110 return "(attrs {})".format(" ".join(items
))
114 def __init__(self
, frequency
):
115 if not isinstance(frequency
, (float, int)):
116 raise TypeError("Clock frequency must be a number")
118 self
.frequency
= float(frequency
)
122 return 1 / self
.frequency
125 return "(clock {})".format(self
.frequency
)
129 def __init__(self
, name
, *args
):
136 raise ValueError("Missing I/O constraints")
138 if isinstance(arg
, (Pins
, DiffPairs
)):
142 raise TypeError("Pins and DiffPairs are incompatible with other location or "
143 "subsignal constraints, but {!r} appears after {!r}"
144 .format(arg
, self
.ios
[-1]))
145 elif isinstance(arg
, Subsignal
):
146 if not self
.ios
or isinstance(self
.ios
[-1], Subsignal
):
149 raise TypeError("Subsignal is incompatible with location constraints, but "
150 "{!r} appears after {!r}"
151 .format(arg
, self
.ios
[-1]))
152 elif isinstance(arg
, Attrs
):
153 self
.attrs
.update(arg
)
154 elif isinstance(arg
, Clock
):
155 if self
.ios
and isinstance(self
.ios
[-1], (Pins
, DiffPairs
)):
156 if self
.clock
is None:
159 raise ValueError("Clock constraint can be applied only once")
161 raise TypeError("Clock constraint can only be applied to Pins or DiffPairs, "
163 .format(self
.ios
[-1]))
165 raise TypeError("Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, "
169 def _content_repr(self
):
172 parts
.append(repr(io
))
173 if self
.clock
is not None:
174 parts
.append(repr(self
.clock
))
176 parts
.append(repr(self
.attrs
))
177 return " ".join(parts
)
180 return "(subsignal {} {})".format(self
.name
, self
._content
_repr
())
183 class Resource(Subsignal
):
185 def family(cls
, name_or_number
, number
=None, *, ios
, default_name
, name_suffix
=""):
186 # This constructor accepts two different forms:
187 # 1. Number-only form:
188 # Resource.family(0, default_name="name", ios=[Pins("A0 A1")])
189 # 2. Name-and-number (name override) form:
190 # Resource.family("override", 0, default_name="name", ios=...)
191 # This makes it easier to build abstractions for resources, e.g. an SPIResource abstraction
192 # could simply delegate to `Resource.family(*args, default_name="spi", ios=ios)`.
193 # The name_suffix argument is meant to support creating resources with
194 # similar names, such as spi_flash, spi_flash_2x, etc.
195 if name_suffix
: # Only add "_" if we actually have a suffix.
196 name_suffix
= "_" + name_suffix
198 if number
is None: # name_or_number is number
199 return cls(default_name
+ name_suffix
, name_or_number
, *ios
)
200 else: # name_or_number is name
201 return cls(name_or_number
+ name_suffix
, number
, *ios
)
203 def __init__(self
, name
, number
, *args
):
204 super().__init
__(name
, *args
)
209 return "(resource {} {} {})".format(self
.name
, self
.number
, self
._content
_repr
())
213 def __init__(self
, name
, number
, io
, *, conn
=None):
216 mapping
= OrderedDict()
218 if isinstance(io
, dict):
219 for conn_pin
, plat_pin
in io
.items():
220 if not isinstance(conn_pin
, str):
221 raise TypeError("Connector pin name must be a string, not {!r}"
223 if not isinstance(plat_pin
, str):
224 raise TypeError("Platform pin name must be a string, not {!r}"
226 mapping
[conn_pin
] = plat_pin
228 elif isinstance(io
, str):
229 for conn_pin
, plat_pin
in enumerate(io
.split(), start
=1):
233 mapping
[str(conn_pin
)] = plat_pin
235 raise TypeError("Connector I/Os must be a dictionary or a string, not {!r}"
239 conn_name
, conn_number
= conn
240 if not (isinstance(conn_name
, str) and isinstance(conn_number
, (int, str))):
241 raise TypeError("Connector must be None or a pair of string (connector name) and "
242 "integer/string (connector number), not {!r}"
245 for conn_pin
, plat_pin
in mapping
.items():
246 mapping
[conn_pin
] = "{}_{}:{}".format(conn_name
, conn_number
, plat_pin
)
248 self
.mapping
= mapping
251 return "(connector {} {} {})".format(self
.name
, self
.number
,
252 " ".join("{}=>{}".format(conn
, plat
)
253 for conn
, plat
in self
.mapping
.items()))
256 return len(self
.mapping
)
259 for conn_pin
, plat_pin
in self
.mapping
.items():
260 yield "{}_{}:{}".format(self
.name
, self
.number
, conn_pin
), plat_pin