hdl.ast: simplify Mux implementation.
[nmigen.git] / nmigen / build / res.py
1 from collections import OrderedDict
2
3 from ..hdl.ast import *
4 from ..hdl.rec import *
5 from ..lib.io import *
6
7 from .dsl import *
8
9
10 __all__ = ["ResourceError", "ResourceManager"]
11
12
13 class ResourceError(Exception):
14 pass
15
16
17 class ResourceManager:
18 def __init__(self, resources, connectors):
19 self.resources = OrderedDict()
20 self._requested = OrderedDict()
21 self._phys_reqd = OrderedDict()
22
23 self.connectors = OrderedDict()
24 self._conn_pins = OrderedDict()
25
26 # Constraint lists
27 self._ports = []
28 self._clocks = SignalDict()
29
30 self.add_resources(resources)
31 self.add_connectors(connectors)
32
33 def add_resources(self, resources):
34 for res in resources:
35 if not isinstance(res, Resource):
36 raise TypeError("Object {!r} is not a Resource".format(res))
37 if (res.name, res.number) in self.resources:
38 raise NameError("Trying to add {!r}, but {!r} has the same name and number"
39 .format(res, self.resources[res.name, res.number]))
40 self.resources[res.name, res.number] = res
41
42 def add_connectors(self, connectors):
43 for conn in connectors:
44 if not isinstance(conn, Connector):
45 raise TypeError("Object {!r} is not a Connector".format(conn))
46 if (conn.name, conn.number) in self.connectors:
47 raise NameError("Trying to add {!r}, but {!r} has the same name and number"
48 .format(conn, self.connectors[conn.name, conn.number]))
49 self.connectors[conn.name, conn.number] = conn
50
51 for conn_pin, plat_pin in conn:
52 assert conn_pin not in self._conn_pins
53 self._conn_pins[conn_pin] = plat_pin
54
55 def lookup(self, name, number=0):
56 if (name, number) not in self.resources:
57 raise ResourceError("Resource {}#{} does not exist"
58 .format(name, number))
59 return self.resources[name, number]
60
61 def request(self, name, number=0, *, dir=None, xdr=None):
62 resource = self.lookup(name, number)
63 if (resource.name, resource.number) in self._requested:
64 raise ResourceError("Resource {}#{} has already been requested"
65 .format(name, number))
66
67 def merge_options(subsignal, dir, xdr):
68 if isinstance(subsignal.ios[0], Subsignal):
69 if dir is None:
70 dir = dict()
71 if xdr is None:
72 xdr = dict()
73 if not isinstance(dir, dict):
74 raise TypeError("Directions must be a dict, not {!r}, because {!r} "
75 "has subsignals"
76 .format(dir, subsignal))
77 if not isinstance(xdr, dict):
78 raise TypeError("Data rate must be a dict, not {!r}, because {!r} "
79 "has subsignals"
80 .format(xdr, subsignal))
81 for sub in subsignal.ios:
82 sub_dir = dir.get(sub.name, None)
83 sub_xdr = xdr.get(sub.name, None)
84 dir[sub.name], xdr[sub.name] = merge_options(sub, sub_dir, sub_xdr)
85 else:
86 if dir is None:
87 dir = subsignal.ios[0].dir
88 if xdr is None:
89 xdr = 0
90 if dir not in ("i", "o", "oe", "io", "-"):
91 raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", \"io\", "
92 "or \"-\", not {!r}"
93 .format(dir))
94 if dir != subsignal.ios[0].dir and \
95 not (subsignal.ios[0].dir == "io" or dir == "-"):
96 raise ValueError("Direction of {!r} cannot be changed from \"{}\" to \"{}\"; "
97 "direction can be changed from \"io\" to \"i\", \"o\", or "
98 "\"oe\", or from anything to \"-\""
99 .format(subsignal.ios[0], subsignal.ios[0].dir, dir))
100 if not isinstance(xdr, int) or xdr < 0:
101 raise ValueError("Data rate of {!r} must be a non-negative integer, not {!r}"
102 .format(subsignal.ios[0], xdr))
103 return dir, xdr
104
105 def resolve(resource, dir, xdr, name, attrs):
106 for attr_key, attr_value in attrs.items():
107 if hasattr(attr_value, "__call__"):
108 attr_value = attr_value(self)
109 assert attr_value is None or isinstance(attr_value, str)
110 if attr_value is None:
111 del attrs[attr_key]
112 else:
113 attrs[attr_key] = attr_value
114
115 if isinstance(resource.ios[0], Subsignal):
116 fields = OrderedDict()
117 for sub in resource.ios:
118 fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
119 name="{}__{}".format(name, sub.name),
120 attrs={**attrs, **sub.attrs})
121 return Record([
122 (f_name, f.layout) for (f_name, f) in fields.items()
123 ], fields=fields, name=name)
124
125 elif isinstance(resource.ios[0], (Pins, DiffPairs)):
126 phys = resource.ios[0]
127 if isinstance(phys, Pins):
128 phys_names = phys.names
129 port = Record([("io", len(phys))], name=name)
130 if isinstance(phys, DiffPairs):
131 phys_names = []
132 record_fields = []
133 if not self.should_skip_port_component(None, attrs, "p"):
134 phys_names += phys.p.names
135 record_fields.append(("p", len(phys)))
136 if not self.should_skip_port_component(None, attrs, "n"):
137 phys_names += phys.n.names
138 record_fields.append(("n", len(phys)))
139 port = Record(record_fields, name=name)
140 if dir == "-":
141 pin = None
142 else:
143 pin = Pin(len(phys), dir, xdr=xdr, name=name)
144
145 for phys_name in phys_names:
146 if phys_name in self._phys_reqd:
147 raise ResourceError("Resource component {} uses physical pin {}, but it "
148 "is already used by resource component {} that was "
149 "requested earlier"
150 .format(name, phys_name, self._phys_reqd[phys_name]))
151 self._phys_reqd[phys_name] = name
152
153 self._ports.append((resource, pin, port, attrs))
154
155 if pin is not None and resource.clock is not None:
156 self.add_clock_constraint(pin.i, resource.clock.frequency)
157
158 return pin if pin is not None else port
159
160 else:
161 assert False # :nocov:
162
163 value = resolve(resource,
164 *merge_options(resource, dir, xdr),
165 name="{}_{}".format(resource.name, resource.number),
166 attrs=resource.attrs)
167 self._requested[resource.name, resource.number] = value
168 return value
169
170 def iter_single_ended_pins(self):
171 for res, pin, port, attrs in self._ports:
172 if pin is None:
173 continue
174 if isinstance(res.ios[0], Pins):
175 yield pin, port, attrs, res.ios[0].invert
176
177 def iter_differential_pins(self):
178 for res, pin, port, attrs in self._ports:
179 if pin is None:
180 continue
181 if isinstance(res.ios[0], DiffPairs):
182 yield pin, port, attrs, res.ios[0].invert
183
184 def should_skip_port_component(self, port, attrs, component):
185 return False
186
187 def iter_ports(self):
188 for res, pin, port, attrs in self._ports:
189 if isinstance(res.ios[0], Pins):
190 if not self.should_skip_port_component(port, attrs, "io"):
191 yield port.io
192 elif isinstance(res.ios[0], DiffPairs):
193 if not self.should_skip_port_component(port, attrs, "p"):
194 yield port.p
195 if not self.should_skip_port_component(port, attrs, "n"):
196 yield port.n
197 else:
198 assert False
199
200 def iter_port_constraints(self):
201 for res, pin, port, attrs in self._ports:
202 if isinstance(res.ios[0], Pins):
203 if not self.should_skip_port_component(port, attrs, "io"):
204 yield port.io.name, res.ios[0].map_names(self._conn_pins, res), attrs
205 elif isinstance(res.ios[0], DiffPairs):
206 if not self.should_skip_port_component(port, attrs, "p"):
207 yield port.p.name, res.ios[0].p.map_names(self._conn_pins, res), attrs
208 if not self.should_skip_port_component(port, attrs, "n"):
209 yield port.n.name, res.ios[0].n.map_names(self._conn_pins, res), attrs
210 else:
211 assert False
212
213 def iter_port_constraints_bits(self):
214 for port_name, pin_names, attrs in self.iter_port_constraints():
215 if len(pin_names) == 1:
216 yield port_name, pin_names[0], attrs
217 else:
218 for bit, pin_name in enumerate(pin_names):
219 yield "{}[{}]".format(port_name, bit), pin_name, attrs
220
221 def add_clock_constraint(self, clock, frequency):
222 if not isinstance(clock, Signal):
223 raise TypeError("Object {!r} is not a Signal".format(clock))
224 if not isinstance(frequency, (int, float)):
225 raise TypeError("Frequency must be a number, not {!r}".format(frequency))
226
227 if clock in self._clocks:
228 raise ValueError("Cannot add clock constraint on {!r}, which is already constrained "
229 "to {} Hz"
230 .format(clock, self._clocks[clock]))
231 else:
232 self._clocks[clock] = float(frequency)
233
234 def iter_clock_constraints(self):
235 # Back-propagate constraints through the input buffer. For clock constraints on pins
236 # (the majority of cases), toolchains work better if the constraint is defined on the pin
237 # and not on the buffered internal net; and if the toolchain is advanced enough that
238 # it considers clock phase and delay of the input buffer, it is *necessary* to define
239 # the constraint on the pin to match the designer's expectation of phase being referenced
240 # to the pin.
241 #
242 # Constraints on nets with no corresponding input pin (e.g. PLL or SERDES outputs) are not
243 # affected.
244 pin_i_to_port = SignalDict()
245 for res, pin, port, attrs in self._ports:
246 if hasattr(pin, "i"):
247 if isinstance(res.ios[0], Pins):
248 pin_i_to_port[pin.i] = port.io
249 elif isinstance(res.ios[0], DiffPairs):
250 pin_i_to_port[pin.i] = port.p
251 else:
252 assert False
253
254 for net_signal, frequency in self._clocks.items():
255 port_signal = pin_i_to_port.get(net_signal)
256 yield net_signal, port_signal, frequency