build/generic_plaform: add loose parameter to return None when not available/existing.
[litex.git] / litex / build / generic_platform.py
1 # This file is Copyright (c) 2013-2014 Sebastien Bourdeauducq <sb@m-labs.hk>
2 # This file is Copyright (c) 2014-2019 Florent Kermarrec <florent@enjoy-digital.fr>
3 # This file is Copyright (c) 2015 Yann Sionneau <ys@m-labs.hk>
4 # License: BSD
5
6 import os
7
8 from migen.fhdl.structure import Signal
9 from migen.genlib.record import Record
10
11 from litex.gen.fhdl import verilog
12
13 from litex.build.io import CRG
14 from litex.build import tools
15
16
17 class ConstraintError(Exception):
18 pass
19
20
21 class Pins:
22 def __init__(self, *identifiers):
23 self.identifiers = []
24 for i in identifiers:
25 if isinstance(i, int):
26 self.identifiers += ["X"]*i
27 else:
28 self.identifiers += i.split()
29
30 def __repr__(self):
31 return "{}('{}')".format(self.__class__.__name__,
32 " ".join(self.identifiers))
33
34
35 class IOStandard:
36 def __init__(self, name):
37 self.name = name
38
39 def __repr__(self):
40 return "{}('{}')".format(self.__class__.__name__, self.name)
41
42
43 class Drive:
44 def __init__(self, strength):
45 self.strength = strength
46
47 def __repr__(self):
48 return "{}('{}')".format(self.__class__.__name__, self.strength)
49
50
51 class Misc:
52 def __init__(self, misc):
53 self.misc = misc
54
55 def __repr__(self):
56 return "{}({})".format(self.__class__.__name__, repr(self.misc))
57
58
59 class Inverted:
60 def __repr__(self):
61 return "{}()".format(self.__class__.__name__)
62
63
64
65 class Subsignal:
66 def __init__(self, name, *constraints):
67 self.name = name
68 self.constraints = list(constraints)
69
70 def __repr__(self):
71 return "{}('{}', {})".format(
72 self.__class__.__name__,
73 self.name,
74 ", ".join([repr(constr) for constr in self.constraints]))
75
76
77 class PlatformInfo:
78 def __init__(self, info):
79 self.info = info
80
81 def __repr__(self):
82 return "{}({})".format(self.__class__.__name__, repr(self.info))
83
84
85 def _lookup(description, name, number, loose=True):
86 for resource in description:
87 if resource[0] == name and (number is None or resource[1] == number):
88 return resource
89 if loose:
90 return None
91 else:
92 raise ConstraintError("Resource not found: {}:{}".format(name, number))
93
94
95 def _resource_type(resource):
96 t = None
97 i = None
98 for element in resource[2:]:
99 if isinstance(element, Pins):
100 assert(t is None)
101 t = len(element.identifiers)
102 elif isinstance(element, Subsignal):
103 if t is None:
104 t = []
105 if i is None:
106 i = []
107
108 assert(isinstance(t, list))
109 n_bits = None
110 inverted = False
111 for c in element.constraints:
112 if isinstance(c, Pins):
113 assert(n_bits is None)
114 n_bits = len(c.identifiers)
115 if isinstance(c, Inverted):
116 inverted = True
117
118 t.append((element.name, n_bits))
119 i.append((element.name, inverted))
120
121 return t, i
122
123
124 class ConnectorManager:
125 def __init__(self, connectors):
126 self.connector_table = dict()
127 for connector in connectors:
128 cit = iter(connector)
129 conn_name = next(cit)
130 if isinstance(connector[1], str):
131 pin_list = []
132 for pins in cit:
133 pin_list += pins.split()
134 pin_list = [None if pin == "None" else pin for pin in pin_list]
135 elif isinstance(connector[1], dict):
136 pin_list = connector[1]
137 else:
138 raise ValueError("Unsupported pin list type {} for connector"
139 " {}".format(type(connector[1]), conn_name))
140 if conn_name in self.connector_table:
141 raise ValueError(
142 "Connector specified more than once: {}".format(conn_name))
143
144 self.connector_table[conn_name] = pin_list
145
146 def resolve_identifiers(self, identifiers):
147 r = []
148 for identifier in identifiers:
149 if ":" in identifier:
150 conn, pn = identifier.split(":")
151 if pn.isdigit():
152 pn = int(pn)
153
154 r.append(self.connector_table[conn][pn])
155 else:
156 r.append(identifier)
157
158 return r
159
160
161 def _separate_pins(constraints):
162 pins = None
163 others = []
164 for c in constraints:
165 if isinstance(c, Pins):
166 assert(pins is None)
167 pins = c.identifiers
168 else:
169 others.append(c)
170
171 return pins, others
172
173
174 class ConstraintManager:
175 def __init__(self, io, connectors):
176 self.available = list(io)
177 self.matched = []
178 self.platform_commands = []
179 self.connector_manager = ConnectorManager(connectors)
180
181 def add_extension(self, io):
182 self.available.extend(io)
183
184 def request(self, name, number=None, loose=False):
185 resource = _lookup(self.available, name, number, loose)
186 if resource is None:
187 return None
188 rt, ri = _resource_type(resource)
189 if number is None:
190 resource_name = name
191 else:
192 resource_name = name + str(number)
193 if isinstance(rt, int):
194 obj = Signal(rt, name_override=resource_name)
195 else:
196 obj = Record(rt, name=resource_name)
197 for name, inverted in ri:
198 if inverted:
199 getattr(obj, name).inverted = True
200
201 for element in resource[2:]:
202 if isinstance(element, Inverted):
203 if isinstance(obj, Signal):
204 obj.inverted = True
205 if isinstance(element, PlatformInfo):
206 obj.platform_info = element.info
207 break
208
209 self.available.remove(resource)
210 self.matched.append((resource, obj))
211 return obj
212
213 def lookup_request(self, name, number=None, loose=False):
214 subname = None
215 if ":" in name: name, subname = name.split(":")
216 for resource, obj in self.matched:
217 if resource[0] == name and (number is None or
218 resource[1] == number):
219 if subname is not None:
220 return getattr(obj, subname)
221 else:
222 return obj
223
224 if loose:
225 return None
226 else:
227 raise ConstraintError("Resource not found: {}:{}".format(name, number))
228
229 def add_platform_command(self, command, **signals):
230 self.platform_commands.append((command, signals))
231
232 def get_io_signals(self):
233 r = set()
234 for resource, obj in self.matched:
235 if isinstance(obj, Signal):
236 r.add(obj)
237 else:
238 r.update(obj.flatten())
239
240 return r
241
242 def get_sig_constraints(self):
243 r = []
244 for resource, obj in self.matched:
245 name = resource[0]
246 number = resource[1]
247 has_subsignals = False
248 top_constraints = []
249 for element in resource[2:]:
250 if isinstance(element, Subsignal):
251 has_subsignals = True
252 else:
253 top_constraints.append(element)
254
255 if has_subsignals:
256 for element in resource[2:]:
257 if isinstance(element, Subsignal):
258 sig = getattr(obj, element.name)
259 pins, others = _separate_pins(top_constraints +
260 element.constraints)
261 pins = self.connector_manager.resolve_identifiers(pins)
262 r.append((sig, pins, others,
263 (name, number, element.name)))
264 else:
265 pins, others = _separate_pins(top_constraints)
266 pins = self.connector_manager.resolve_identifiers(pins)
267 r.append((obj, pins, others, (name, number, None)))
268
269 return r
270
271 def get_platform_commands(self):
272 return self.platform_commands
273
274
275 class GenericPlatform:
276 def __init__(self, device, io, connectors=[], name=None):
277 self.device = device
278 self.constraint_manager = ConstraintManager(io, connectors)
279 if name is None:
280 name = self.__module__.split(".")[-1]
281 self.name = name
282 self.sources = []
283 self.verilog_include_paths = []
284 self.output_dir = None
285 self.finalized = False
286 self.use_default_clk = False
287
288 def request(self, *args, **kwargs):
289 return self.constraint_manager.request(*args, **kwargs)
290
291 def lookup_request(self, *args, **kwargs):
292 return self.constraint_manager.lookup_request(*args, **kwargs)
293
294 def add_period_constraint(self, clk, period):
295 raise NotImplementedError
296
297 def add_false_path_constraint(self, from_, to):
298 raise NotImplementedError
299
300 def add_false_path_constraints(self, *clk):
301 for a in clk:
302 for b in clk:
303 if a is not b:
304 self.add_false_path_constraint(a, b)
305
306 def add_platform_command(self, *args, **kwargs):
307 return self.constraint_manager.add_platform_command(*args, **kwargs)
308
309 def add_extension(self, *args, **kwargs):
310 return self.constraint_manager.add_extension(*args, **kwargs)
311
312 def finalize(self, fragment, *args, **kwargs):
313 if self.finalized:
314 raise ConstraintError("Already finalized")
315 # if none exists, create a default clock domain and drive it
316 if not fragment.clock_domains:
317 if not hasattr(self, "default_clk_name"):
318 raise NotImplementedError(
319 "No default clock and no clock domain defined")
320 crg = CRG(self.request(self.default_clk_name))
321 fragment += crg.get_fragment()
322 self.user_default_clk = True
323
324 self.do_finalize(fragment, *args, **kwargs)
325 self.finalized = True
326
327 def do_finalize(self, fragment, *args, **kwargs):
328 """overload this and e.g. add_platform_command()'s after the modules
329 had their say"""
330 if self.use_default_clk:
331 try:
332 self.add_period_constraint(
333 self.lookup_request(self.default_clk_name),
334 self.default_clk_period)
335 except ConstraintError:
336 pass
337
338 def add_source(self, filename, language=None, library=None):
339 if language is None:
340 language = tools.language_by_filename(filename)
341 if library is None:
342 library = "work"
343 for f, _, _ in self.sources:
344 if f == filename:
345 return
346 self.sources.append((os.path.abspath(filename), language, library))
347
348 def add_sources(self, path, *filenames, language=None, library=None):
349 for f in filenames:
350 self.add_source(os.path.join(path, f), language, library)
351
352 def add_source_dir(self, path, recursive=True, language=None, library=None):
353 dir_files = []
354 if recursive:
355 for root, dirs, files in os.walk(path):
356 for filename in files:
357 dir_files.append(os.path.join(root, filename))
358 else:
359 for item in os.listdir(path):
360 if os.path.isfile(os.path.join(path, item)):
361 dir_files.append(os.path.join(path, item))
362 for filename in dir_files:
363 _language = language
364 if _language is None:
365 _language = tools.language_by_filename(filename)
366 if _language is not None:
367 self.add_source(filename, _language, library)
368
369 def add_verilog_include_path(self, path):
370 self.verilog_include_paths.append(os.path.abspath(path))
371
372 def resolve_signals(self, vns):
373 # resolve signal names in constraints
374 sc = self.constraint_manager.get_sig_constraints()
375 named_sc = [(vns.get_name(sig), pins, others, resource)
376 for sig, pins, others, resource in sc]
377 # resolve signal names in platform commands
378 pc = self.constraint_manager.get_platform_commands()
379 named_pc = []
380 for template, args in pc:
381 name_dict = dict((k, vns.get_name(sig)) for k, sig in args.items())
382 named_pc.append(template.format(**name_dict))
383
384 return named_sc, named_pc
385
386 def get_verilog(self, fragment, **kwargs):
387 return verilog.convert(
388 fragment,
389 self.constraint_manager.get_io_signals(),
390 create_clock_domains=False, **kwargs)
391
392 def get_edif(self, fragment, cell_library, vendor, device, **kwargs):
393 return edif.convert(
394 fragment,
395 self.constraint_manager.get_io_signals(),
396 cell_library, vendor, device, **kwargs)
397
398 def build(self, fragment):
399 raise NotImplementedError("GenericPlatform.build must be overloaded")
400
401 def create_programmer(self):
402 raise NotImplementedError