build.dsl: fix precondition check in Pins.
[nmigen.git] / nmigen / build / dsl.py
1 from collections import OrderedDict
2
3
4 __all__ = ["Pins", "DiffPairs", "Attrs", "Clock", "Subsignal", "Resource", "Connector"]
5
6
7 class Pins:
8 def __init__(self, names, *, dir="io", conn=None):
9 if not isinstance(names, str):
10 raise TypeError("Names must be a whitespace-separated string, not {!r}"
11 .format(names))
12 names = names.split()
13
14 if conn is not None:
15 conn_name, conn_number = conn
16 if not (isinstance(conn_name, str) and isinstance(conn_number, int)):
17 raise TypeError("Connector must be None or a pair of string and integer, not {!r}"
18 .format(conn))
19 names = ["{}_{}:{}".format(conn_name, conn_number, name) for name in names]
20
21 if dir not in ("i", "o", "io", "oe"):
22 raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not {!r}"
23 .format(dir))
24
25 self.names = names
26 self.dir = dir
27
28 def __len__(self):
29 return len(self.names)
30
31 def __iter__(self):
32 return iter(self.names)
33
34 def map_names(self, mapping, resource):
35 mapped_names = []
36 for name in self.names:
37 while ":" in name:
38 if name not in mapping:
39 raise NameError("Resource {!r} refers to nonexistent connector pin {}"
40 .format(resource, name))
41 name = mapping[name]
42 mapped_names.append(name)
43 return mapped_names
44
45 def __repr__(self):
46 return "(pins {} {})".format(self.dir, " ".join(self.names))
47
48
49 class DiffPairs:
50 def __init__(self, p, n, *, dir="io", conn=None):
51 self.p = Pins(p, dir=dir, conn=conn)
52 self.n = Pins(n, dir=dir, conn=conn)
53
54 if len(self.p.names) != len(self.n.names):
55 raise TypeError("Positive and negative pins must have the same width, but {!r} "
56 "and {!r} do not"
57 .format(self.p, self.n))
58
59 self.dir = dir
60
61 def __len__(self):
62 return len(self.p.names)
63
64 def __iter__(self):
65 return zip(self.p.names, self.n.names)
66
67 def __repr__(self):
68 return "(diffpairs {} (p {}) (n {}))".format(
69 self.dir, " ".join(self.p.names), " ".join(self.n.names))
70
71
72 class Attrs(OrderedDict):
73 def __init__(self, **attrs):
74 for attr_key, attr_value in attrs.items():
75 if not isinstance(attr_value, str):
76 raise TypeError("Attribute value must be a string, not {!r}"
77 .format(attr_value))
78
79 super().__init__(**attrs)
80
81 def __repr__(self):
82 return "(attrs {})".format(" ".join("{}={}".format(k, v)
83 for k, v in self.items()))
84
85
86 class Clock:
87 def __init__(self, frequency):
88 if not isinstance(frequency, (float, int)):
89 raise TypeError("Clock frequency must be a number")
90
91 self.frequency = float(frequency)
92
93 @property
94 def period(self):
95 return 1 / self.frequency
96
97 def __repr__(self):
98 return "(clock {})".format(self.frequency)
99
100
101 class Subsignal:
102 def __init__(self, name, *args):
103 self.name = name
104 self.ios = []
105 self.attrs = Attrs()
106 self.clock = None
107
108 if not args:
109 raise ValueError("Missing I/O constraints")
110 for arg in args:
111 if isinstance(arg, (Pins, DiffPairs)):
112 if not self.ios:
113 self.ios.append(arg)
114 else:
115 raise TypeError("Pins and DiffPairs are incompatible with other location or "
116 "subsignal constraints, but {!r} appears after {!r}"
117 .format(arg, self.ios[-1]))
118 elif isinstance(arg, Subsignal):
119 if not self.ios or isinstance(self.ios[-1], Subsignal):
120 self.ios.append(arg)
121 else:
122 raise TypeError("Subsignal is incompatible with location constraints, but "
123 "{!r} appears after {!r}"
124 .format(arg, self.ios[-1]))
125 elif isinstance(arg, Attrs):
126 self.attrs.update(arg)
127 elif isinstance(arg, Clock):
128 if self.ios and isinstance(self.ios[-1], (Pins, DiffPairs)):
129 if self.clock is None:
130 self.clock = arg
131 else:
132 raise ValueError("Clock constraint can be applied only once")
133 else:
134 raise TypeError("Clock constraint can only be applied to Pins or DiffPairs, "
135 "not {!r}"
136 .format(self.ios[-1]))
137 else:
138 raise TypeError("Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, "
139 "or Clock, not {!r}"
140 .format(arg))
141
142 def _content_repr(self):
143 parts = []
144 for io in self.ios:
145 parts.append(repr(io))
146 if self.clock is not None:
147 parts.append(repr(self.clock))
148 if self.attrs:
149 parts.append(repr(self.attrs))
150 return " ".join(parts)
151
152 def __repr__(self):
153 return "(subsignal {} {})".format(self.name, self._content_repr())
154
155
156 class Resource(Subsignal):
157 def __init__(self, name, number, *args):
158 super().__init__(name, *args)
159
160 self.number = number
161
162 def __repr__(self):
163 return "(resource {} {} {})".format(self.name, self.number, self._content_repr())
164
165
166 class Connector:
167 def __init__(self, name, number, io):
168 self.name = name
169 self.number = number
170 self.mapping = OrderedDict()
171
172 if isinstance(io, dict):
173 for conn_pin, plat_pin in io.items():
174 if not isinstance(conn_pin, str):
175 raise TypeError("Connector pin name must be a string, not {!r}"
176 .format(conn_pin))
177 if not isinstance(plat_pin, str):
178 raise TypeError("Platform pin name must be a string, not {!r}"
179 .format(plat_pin))
180 self.mapping[conn_pin] = plat_pin
181
182 elif isinstance(io, str):
183 for conn_pin, plat_pin in enumerate(io.split(), start=1):
184 if plat_pin == "-":
185 continue
186 self.mapping[str(conn_pin)] = plat_pin
187
188 else:
189 raise TypeError("Connector I/Os must be a dictionary or a string, not {!r}"
190 .format(io))
191
192 def __repr__(self):
193 return "(connector {} {} {})".format(self.name, self.number,
194 " ".join("{}=>{}".format(conn, plat)
195 for conn, plat in self.mapping.items()))
196
197 def __len__(self):
198 return len(self.mapping)
199
200 def __iter__(self):
201 for conn_pin, plat_pin in self.mapping.items():
202 yield "{}_{}:{}".format(self.name, self.number, conn_pin), plat_pin