17f147baff21513412e33a01814a81e04eb473c7
1 from collections
import OrderedDict
2 from abc
import ABCMeta
, abstractmethod
, abstractproperty
8 from .. import __version__
9 from .._toolchain
import *
11 from ..hdl
.xfrm
import SampleLowerer
, DomainLowerer
12 from ..lib
.cdc
import ResetSynchronizer
13 from ..back
import rtlil
, verilog
18 __all__
= ["Platform", "TemplatedPlatform"]
21 class Platform(ResourceManager
, metaclass
=ABCMeta
):
22 resources
= abstractproperty()
23 connectors
= abstractproperty()
26 required_tools
= abstractproperty()
29 super().__init
__(self
.resources
, self
.connectors
)
31 self
.extra_files
= OrderedDict()
33 self
._prepared
= False
36 def default_clk_constraint(self
):
37 if self
.default_clk
is None:
38 raise AttributeError("Platform '{}' does not define a default clock"
39 .format(type(self
).__name
__))
40 return self
.lookup(self
.default_clk
).clock
43 def default_clk_frequency(self
):
44 constraint
= self
.default_clk_constraint
45 if constraint
is None:
46 raise AttributeError("Platform '{}' does not constrain its default clock"
47 .format(type(self
).__name
__))
48 return constraint
.frequency
50 def add_file(self
, filename
, content
):
51 if not isinstance(filename
, str):
52 raise TypeError("File name must be a string, not {!r}"
54 if hasattr(content
, "read"):
55 content
= content
.read()
56 elif not isinstance(content
, (str, bytes
)):
57 raise TypeError("File contents must be str, bytes, or a file-like object, not {!r}"
59 if filename
in self
.extra_files
:
60 if self
.extra_files
[filename
] != content
:
61 raise ValueError("File {!r} already exists"
64 self
.extra_files
[filename
] = content
67 def _toolchain_env_var(self
):
68 return f
"NMIGEN_ENV_{self.toolchain}"
70 def build(self
, elaboratable
, name
="top",
71 build_dir
="build", do_build
=True,
72 program_opts
=None, do_program
=False,
74 # The following code performs a best-effort check for presence of required tools upfront,
75 # before performing any build actions, to provide a better diagnostic. It does not handle
76 # several corner cases:
77 # 1. `require_tool` does not source toolchain environment scripts, so if such a script
78 # is used, the check is skipped, and `execute_local()` may fail;
79 # 2. if the design is not built (do_build=False), most of the tools are not required and
80 # in fact might not be available if the design will be built manually with a different
81 # environment script specified, or on a different machine; however, Yosys is required
82 # by virtually every platform anyway, to provide debug Verilog output, and `prepare()`
84 # This is OK because even if `require_tool` succeeds, the toolchain might be broken anyway.
85 # The check only serves to catch common errors earlier.
86 if do_build
and self
._toolchain
_env
_var
not in os
.environ
:
87 for tool
in self
.required_tools
:
90 plan
= self
.prepare(elaboratable
, name
, **kwargs
)
94 products
= plan
.execute_local(build_dir
)
98 self
.toolchain_program(products
, name
, **(program_opts
or {}))
100 def has_required_tools(self
):
101 if self
._toolchain
_env
_var
in os
.environ
:
103 return all(has_tool(name
) for name
in self
.required_tools
)
105 def create_missing_domain(self
, name
):
106 # Simple instantiation of a clock domain driven directly by the board clock and reset.
107 # This implementation uses a single ResetSynchronizer to ensure that:
108 # * an external reset is definitely synchronized to the system clock;
109 # * release of power-on reset, which is inherently asynchronous, is synchronized to
111 # Many device families provide advanced primitives for tackling reset. If these exist,
112 # they should be used instead.
113 if name
== "sync" and self
.default_clk
is not None:
114 clk_i
= self
.request(self
.default_clk
).i
115 if self
.default_rst
is not None:
116 rst_i
= self
.request(self
.default_rst
).i
121 m
.domains
+= ClockDomain("sync")
122 m
.d
.comb
+= ClockSignal("sync").eq(clk_i
)
123 m
.submodules
.reset_sync
= ResetSynchronizer(rst_i
, domain
="sync")
126 def prepare(self
, elaboratable
, name
="top", **kwargs
):
127 assert not self
._prepared
128 self
._prepared
= True
130 fragment
= Fragment
.get(elaboratable
, self
)
131 fragment
= SampleLowerer()(fragment
)
132 fragment
._propagate
_domains
(self
.create_missing_domain
, platform
=self
)
133 fragment
= DomainLowerer()(fragment
)
135 def add_pin_fragment(pin
, pin_fragment
):
136 pin_fragment
= Fragment
.get(pin_fragment
, self
)
137 if not isinstance(pin_fragment
, Instance
):
138 pin_fragment
.flatten
= True
139 fragment
.add_subfragment(pin_fragment
, name
="pin_{}".format(pin
.name
))
141 for pin
, port
, attrs
, invert
in self
.iter_single_ended_pins():
143 add_pin_fragment(pin
, self
.get_input(pin
, port
, attrs
, invert
))
145 add_pin_fragment(pin
, self
.get_output(pin
, port
, attrs
, invert
))
147 add_pin_fragment(pin
, self
.get_tristate(pin
, port
, attrs
, invert
))
149 add_pin_fragment(pin
, self
.get_input_output(pin
, port
, attrs
, invert
))
151 for pin
, p_port
, n_port
, attrs
, invert
in self
.iter_differential_pins():
153 add_pin_fragment(pin
, self
.get_diff_input(pin
, p_port
, n_port
, attrs
, invert
))
155 add_pin_fragment(pin
, self
.get_diff_output(pin
, p_port
, n_port
, attrs
, invert
))
157 add_pin_fragment(pin
, self
.get_diff_tristate(pin
, p_port
, n_port
, attrs
, invert
))
159 add_pin_fragment(pin
,
160 self
.get_diff_input_output(pin
, p_port
, n_port
, attrs
, invert
))
162 fragment
._propagate
_ports
(ports
=self
.iter_ports(), all_undef_as_ports
=False)
163 return self
.toolchain_prepare(fragment
, name
, **kwargs
)
166 def toolchain_prepare(self
, fragment
, name
, **kwargs
):
168 Convert the ``fragment`` and constraints recorded in this :class:`Platform` into
169 a :class:`BuildPlan`.
171 raise NotImplementedError # :nocov:
173 def toolchain_program(self
, products
, name
, **kwargs
):
175 Extract bitstream for fragment ``name`` from ``products`` and download it to a target.
177 raise NotImplementedError("Platform '{}' does not support programming"
178 .format(type(self
).__name
__))
180 def _check_feature(self
, feature
, pin
, attrs
, valid_xdrs
, valid_attrs
):
182 raise NotImplementedError("Platform '{}' does not support {}"
183 .format(type(self
).__name
__, feature
))
184 elif pin
.xdr
not in valid_xdrs
:
185 raise NotImplementedError("Platform '{}' does not support {} for XDR {}"
186 .format(type(self
).__name
__, feature
, pin
.xdr
))
188 if not valid_attrs
and attrs
:
189 raise NotImplementedError("Platform '{}' does not support attributes for {}"
190 .format(type(self
).__name
__, feature
))
193 def _invert_if(invert
, value
):
199 def get_input(self
, pin
, port
, attrs
, invert
):
200 self
._check
_feature
("single-ended input", pin
, attrs
,
201 valid_xdrs
=(0,), valid_attrs
=None)
204 m
.d
.comb
+= pin
.i
.eq(self
._invert
_if
(invert
, port
))
207 def get_output(self
, pin
, port
, attrs
, invert
):
208 self
._check
_feature
("single-ended output", pin
, attrs
,
209 valid_xdrs
=(0,), valid_attrs
=None)
212 m
.d
.comb
+= port
.eq(self
._invert
_if
(invert
, pin
.o
))
215 def get_tristate(self
, pin
, port
, attrs
, invert
):
216 self
._check
_feature
("single-ended tristate", pin
, attrs
,
217 valid_xdrs
=(0,), valid_attrs
=None)
220 m
.submodules
+= Instance("$tribuf",
223 i_A
=self
._invert
_if
(invert
, pin
.o
),
228 def get_input_output(self
, pin
, port
, attrs
, invert
):
229 self
._check
_feature
("single-ended input/output", pin
, attrs
,
230 valid_xdrs
=(0,), valid_attrs
=None)
233 m
.submodules
+= Instance("$tribuf",
236 i_A
=self
._invert
_if
(invert
, pin
.o
),
239 m
.d
.comb
+= pin
.i
.eq(self
._invert
_if
(invert
, port
))
242 def get_diff_input(self
, pin
, p_port
, n_port
, attrs
, invert
):
243 self
._check
_feature
("differential input", pin
, attrs
,
244 valid_xdrs
=(), valid_attrs
=None)
246 def get_diff_output(self
, pin
, p_port
, n_port
, attrs
, invert
):
247 self
._check
_feature
("differential output", pin
, attrs
,
248 valid_xdrs
=(), valid_attrs
=None)
250 def get_diff_tristate(self
, pin
, p_port
, n_port
, attrs
, invert
):
251 self
._check
_feature
("differential tristate", pin
, attrs
,
252 valid_xdrs
=(), valid_attrs
=None)
254 def get_diff_input_output(self
, pin
, p_port
, n_port
, attrs
, invert
):
255 self
._check
_feature
("differential input/output", pin
, attrs
,
256 valid_xdrs
=(), valid_attrs
=None)
259 class TemplatedPlatform(Platform
):
260 toolchain
= abstractproperty()
261 file_templates
= abstractproperty()
262 command_templates
= abstractproperty()
264 build_script_templates
= {
265 "build_{{name}}.sh": """
267 set -e{{verbose("x")}}
268 [ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}"
269 {{emit_commands("sh")}}
271 "build_{{name}}.bat": """
272 @rem {{autogenerated}}
273 {{quiet("@echo off")}}
274 if defined {{platform._toolchain_env_var}} call %{{platform._toolchain_env_var}}%
275 {{emit_commands("bat")}}
279 def toolchain_prepare(self
, fragment
, name
, **kwargs
):
280 # Restrict the name of the design to a strict alphanumeric character set. Platforms will
281 # interpolate the name of the design in many different contexts: filesystem paths, Python
282 # scripts, Tcl scripts, ad-hoc constraint files, and so on. It is not practical to add
283 # escaping code that handles every one of their edge cases, so make sure we never hit them
284 # in the first place.
285 invalid_char
= re
.match(r
"[^A-Za-z0-9_]", name
)
287 raise ValueError("Design name {!r} contains invalid character {!r}; only alphanumeric "
288 "characters are valid in design names"
289 .format(name
, invalid_char
.group(0)))
291 # This notice serves a dual purpose: to explain that the file is autogenerated,
292 # and to incorporate the nMigen version into generated code.
293 autogenerated
= "Automatically generated by nMigen {}. Do not edit.".format(__version__
)
295 rtlil_text
, name_map
= rtlil
.convert_fragment(fragment
, name
=name
)
300 def emit_verilog(opts
=()):
301 return verilog
._convert
_rtlil
_text
(rtlil_text
,
302 strip_internal_attrs
=True, write_verilog_opts
=opts
)
304 def emit_debug_verilog(opts
=()):
305 return verilog
._convert
_rtlil
_text
(rtlil_text
,
306 strip_internal_attrs
=False, write_verilog_opts
=opts
)
308 def emit_commands(syntax
):
311 for name
in self
.required_tools
:
312 env_var
= tool_env_var(name
)
314 template
= ": ${{{env_var}:={name}}}"
315 elif syntax
== "bat":
317 "if [%{env_var}%] equ [\"\"] set {env_var}=\n" \
318 "if [%{env_var}%] equ [] set {env_var}={name}"
321 commands
.append(template
.format(env_var
=env_var
, name
=name
))
323 for index
, command_tpl
in enumerate(self
.command_templates
):
324 command
= render(command_tpl
, origin
="<command#{}>".format(index
+ 1),
326 command
= re
.sub(r
"\s+", " ", command
)
328 commands
.append(command
)
329 elif syntax
== "bat":
330 commands
.append(command
+ " || exit /b")
334 return "\n".join(commands
)
336 def get_override(var
):
337 var_env
= "NMIGEN_{}".format(var
)
338 if var_env
in os
.environ
:
339 # On Windows, there is no way to define an "empty but set" variable; it is tempting
340 # to use a quoted empty string, but it doesn't do what one would expect. Recognize
341 # this as a useful pattern anyway, and treat `set VAR=""` on Windows the same way
342 # `export VAR=` is treated on Linux.
343 return re
.sub(r
'^\"\"$', "", os
.environ
[var_env
])
345 if isinstance(kwargs
[var
], str):
346 return textwrap
.dedent(kwargs
[var
]).strip()
350 return jinja2
.Undefined(name
=var
)
352 @jinja2.contextfunction
353 def invoke_tool(context
, name
):
354 env_var
= tool_env_var(name
)
355 if context
.parent
["syntax"] == "sh":
356 return "\"${}\"".format(env_var
)
357 elif context
.parent
["syntax"] == "bat":
358 return "%{}%".format(env_var
)
363 if isinstance(opts
, str):
366 return " ".join(opts
)
368 def hierarchy(signal
, separator
):
369 return separator
.join(name_map
[signal
][1:])
371 def ascii_escape(string
):
372 def escape_one(match
):
373 if match
.group(1) is None:
374 return match
.group(2)
376 return "_{:02x}_".format(ord(match
.group(1)[0]))
377 return "".join(escape_one(m
) for m
in re
.finditer(r
"([^A-Za-z0-9_])|(.)", string
))
379 def tcl_escape(string
):
380 return "{" + re
.sub(r
"([{}\\])", r
"\\\1", string
) + "}"
383 if "NMIGEN_verbose" in os
.environ
:
386 return jinja2
.Undefined(name
="quiet")
389 if "NMIGEN_verbose" in os
.environ
:
390 return jinja2
.Undefined(name
="quiet")
394 def render(source
, origin
, syntax
=None):
396 source
= textwrap
.dedent(source
).strip()
397 compiled
= jinja2
.Template(source
,
398 trim_blocks
=True, lstrip_blocks
=True, undefined
=jinja2
.StrictUndefined
)
399 compiled
.environment
.filters
["options"] = options
400 compiled
.environment
.filters
["hierarchy"] = hierarchy
401 compiled
.environment
.filters
["ascii_escape"] = ascii_escape
402 compiled
.environment
.filters
["tcl_escape"] = tcl_escape
403 except jinja2
.TemplateSyntaxError
as e
:
404 e
.args
= ("{} (at {}:{})".format(e
.message
, origin
, e
.lineno
),)
406 return compiled
.render({
409 "emit_rtlil": emit_rtlil
,
410 "emit_verilog": emit_verilog
,
411 "emit_debug_verilog": emit_debug_verilog
,
412 "emit_commands": emit_commands
,
414 "invoke_tool": invoke_tool
,
415 "get_override": get_override
,
418 "autogenerated": autogenerated
,
421 plan
= BuildPlan(script
="build_{}".format(name
))
422 for filename_tpl
, content_tpl
in self
.file_templates
.items():
423 plan
.add_file(render(filename_tpl
, origin
=filename_tpl
),
424 render(content_tpl
, origin
=content_tpl
))
425 for filename
, content
in self
.extra_files
.items():
426 plan
.add_file(filename
, content
)
429 def iter_extra_files(self
, *endswith
):
430 return (f
for f
in self
.extra_files
if f
.endswith(endswith
))