17c8752a2b493d3834e0c08e43d8aeef1e52195f
[litex.git] / mibuild / mibuild / generic_platform.py
1 import os, argparse
2
3 from migen.fhdl.std import *
4 from migen.fhdl.structure import _Fragment
5 from migen.genlib.record import Record
6 from migen.fhdl import verilog, edif
7
8 from mibuild import tools
9
10 class ConstraintError(Exception):
11 pass
12
13 class Pins:
14 def __init__(self, *identifiers):
15 self.identifiers = []
16 for i in identifiers:
17 self.identifiers += i.split()
18
19 class IOStandard:
20 def __init__(self, name):
21 self.name = name
22
23 class Drive:
24 def __init__(self, strength):
25 self.strength = strength
26
27 class Misc:
28 def __init__(self, misc):
29 self.misc = misc
30
31 class Subsignal:
32 def __init__(self, name, *constraints):
33 self.name = name
34 self.constraints = list(constraints)
35
36 class PlatformInfo:
37 def __init__(self, info):
38 self.info = info
39
40 def _lookup(description, name, number):
41 for resource in description:
42 if resource[0] == name and (number is None or resource[1] == number):
43 return resource
44 raise ConstraintError("Resource not found: " + name + ":" + str(number))
45
46 def _resource_type(resource):
47 t = None
48 for element in resource[2:]:
49 if isinstance(element, Pins):
50 assert(t is None)
51 t = len(element.identifiers)
52 elif isinstance(element, Subsignal):
53 if t is None:
54 t = []
55 assert(isinstance(t, list))
56 n_bits = None
57 for c in element.constraints:
58 if isinstance(c, Pins):
59 assert(n_bits is None)
60 n_bits = len(c.identifiers)
61 t.append((element.name, n_bits))
62 return t
63
64 def _separate_pins(constraints):
65 pins = None
66 others = []
67 for c in constraints:
68 if isinstance(c, Pins):
69 assert(pins is None)
70 pins = c.identifiers
71 else:
72 others.append(c)
73 return pins, others
74
75 class ConstraintManager:
76 def __init__(self, description):
77 self.available = list(description)
78 self.matched = []
79 self.platform_commands = []
80
81 def request(self, name, number=None):
82 resource = _lookup(self.available, name, number)
83 rt = _resource_type(resource)
84 if isinstance(rt, int):
85 obj = Signal(rt, name_override=resource[0])
86 else:
87 obj = Record(rt, name=resource[0])
88 for element in resource[2:]:
89 if isinstance(element, PlatformInfo):
90 obj.platform_info = element.info
91 break
92 self.available.remove(resource)
93 self.matched.append((resource, obj))
94 return obj
95
96 def lookup_request(self, name, number=None):
97 for resource, obj in self.matched:
98 if resource[0] == name and (number is None or resource[1] == number):
99 return obj
100 raise ConstraintError("Resource not found: " + name + ":" + str(number))
101
102 def add_platform_command(self, command, **signals):
103 self.platform_commands.append((command, signals))
104
105 def get_io_signals(self):
106 r = set()
107 for resource, obj in self.matched:
108 if isinstance(obj, Signal):
109 r.add(obj)
110 else:
111 r.update(obj.flatten())
112 return r
113
114 def get_sig_constraints(self):
115 r = []
116 for resource, obj in self.matched:
117 name = resource[0]
118 number = resource[1]
119 has_subsignals = False
120 top_constraints = []
121 for element in resource[2:]:
122 if isinstance(element, Subsignal):
123 has_subsignals = True
124 else:
125 top_constraints.append(element)
126 if has_subsignals:
127 for element in resource[2:]:
128 if isinstance(element, Subsignal):
129 sig = getattr(obj, element.name)
130 pins, others = _separate_pins(top_constraints + element.constraints)
131 r.append((sig, pins, others, (name, number, element.name)))
132 else:
133 pins, others = _separate_pins(top_constraints)
134 r.append((obj, pins, others, (name, number, None)))
135 return r
136
137 def get_platform_commands(self):
138 return self.platform_commands
139
140
141 class GenericPlatform:
142 def __init__(self, device, io, default_crg_factory=None, name=None):
143 self.device = device
144 self.constraint_manager = ConstraintManager(io)
145 self.default_crg_factory = default_crg_factory
146 if name is None:
147 name = self.__module__.split(".")[-1]
148 self.name = name
149 self.sources = []
150 self.finalized = False
151
152 def request(self, *args, **kwargs):
153 return self.constraint_manager.request(*args, **kwargs)
154
155 def lookup_request(self, *args, **kwargs):
156 return self.constraint_manager.lookup_request(*args, **kwargs)
157
158 def add_platform_command(self, *args, **kwargs):
159 return self.constraint_manager.add_platform_command(*args, **kwargs)
160
161 def finalize(self, fragment, *args, **kwargs):
162 if self.finalized:
163 raise ConstraintError("Already finalized")
164 # if none exists, create a default clock domain and drive it
165 if not fragment.clock_domains:
166 if self.default_crg_factory is None:
167 raise NotImplementedError("No clock/reset generator defined by either platform or user")
168 crg = self.default_crg_factory(self)
169 fragment += crg.get_fragment()
170 self.do_finalize(fragment, *args, **kwargs)
171 self.finalized = True
172
173 def do_finalize(self, fragment, *args, **kwargs):
174 """overload this and e.g. add_platform_command()'s after the
175 modules had their say"""
176 pass
177
178 def add_source(self, filename, language=None):
179 if language is None:
180 language = tools.language_by_filename(filename)
181 if language is None:
182 language = "verilog" # default to Verilog
183 filename = os.path.abspath(filename)
184 self.sources.append((filename, language))
185
186 def add_sources(self, path, *filenames, language=None):
187 for f in filenames:
188 self.add_source(os.path.join(path, f), language)
189
190 def add_source_dir(self, path):
191 for root, dirs, files in os.walk(path):
192 for filename in files:
193 language = tools.language_by_filename(filename)
194 if language is not None:
195 self.add_source(os.path.join(root, filename), language)
196
197 def _resolve_signals(self, vns):
198 # resolve signal names in constraints
199 sc = self.constraint_manager.get_sig_constraints()
200 named_sc = [(vns.get_name(sig), pins, others, resource) for sig, pins, others, resource in sc]
201 # resolve signal names in platform commands
202 pc = self.constraint_manager.get_platform_commands()
203 named_pc = []
204 for template, args in pc:
205 name_dict = dict((k, vns.get_name(sig)) for k, sig in args.items())
206 named_pc.append(template.format(**name_dict))
207 return named_sc, named_pc
208
209 def _get_source(self, fragment, gen_fn):
210 if not isinstance(fragment, _Fragment):
211 fragment = fragment.get_fragment()
212 # generate source
213 src, vns = gen_fn(fragment)
214 named_sc, named_pc = self._resolve_signals(vns)
215 return src, named_sc, named_pc
216
217 def get_verilog(self, fragment, **kwargs):
218 return self._get_source(fragment, lambda f: verilog.convert(f, self.constraint_manager.get_io_signals(),
219 return_ns=True, create_clock_domains=False, **kwargs))
220
221 def get_edif(self, fragment, cell_library, vendor, device, **kwargs):
222 return self._get_source(fragment, lambda f: edif.convert(f, self.constraint_manager.get_io_signals(),
223 cell_library, vendor, device, return_ns=True, **kwargs))
224
225 def build(self, fragment):
226 raise NotImplementedError("GenericPlatform.build must be overloaded")
227
228 def add_arguments(self, parser):
229 pass # default: no arguments
230
231 def build_arg_ns(self, ns, *args, **kwargs):
232 self.build(*args, **kwargs)
233
234 def build_cmdline(self, *args, **kwargs):
235 parser = argparse.ArgumentParser(description="FPGA bitstream build system")
236 self.add_arguments(parser)
237 ns = parser.parse_args()
238 self.build_arg_ns(ns, *args, **kwargs)