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
, port
, attrs
, invert
in self
.iter_differential_pins():
153 add_pin_fragment(pin
, self
.get_diff_input(pin
, port
, attrs
, invert
))
155 add_pin_fragment(pin
, self
.get_diff_output(pin
, port
, attrs
, invert
))
157 add_pin_fragment(pin
, self
.get_diff_tristate(pin
, port
, attrs
, invert
))
159 add_pin_fragment(pin
, self
.get_diff_input_output(pin
, port
, attrs
, invert
))
161 fragment
._propagate
_ports
(ports
=self
.iter_ports(), all_undef_as_ports
=False)
162 return self
.toolchain_prepare(fragment
, name
, **kwargs
)
165 def toolchain_prepare(self
, fragment
, name
, **kwargs
):
167 Convert the ``fragment`` and constraints recorded in this :class:`Platform` into
168 a :class:`BuildPlan`.
170 raise NotImplementedError # :nocov:
172 def toolchain_program(self
, products
, name
, **kwargs
):
174 Extract bitstream for fragment ``name`` from ``products`` and download it to a target.
176 raise NotImplementedError("Platform '{}' does not support programming"
177 .format(type(self
).__name
__))
179 def _check_feature(self
, feature
, pin
, attrs
, valid_xdrs
, valid_attrs
):
181 raise NotImplementedError("Platform '{}' does not support {}"
182 .format(type(self
).__name
__, feature
))
183 elif pin
.xdr
not in valid_xdrs
:
184 raise NotImplementedError("Platform '{}' does not support {} for XDR {}"
185 .format(type(self
).__name
__, feature
, pin
.xdr
))
187 if not valid_attrs
and attrs
:
188 raise NotImplementedError("Platform '{}' does not support attributes for {}"
189 .format(type(self
).__name
__, feature
))
192 def _invert_if(invert
, value
):
198 def get_input(self
, pin
, port
, attrs
, invert
):
199 self
._check
_feature
("single-ended input", pin
, attrs
,
200 valid_xdrs
=(0,), valid_attrs
=None)
203 m
.d
.comb
+= pin
.i
.eq(self
._invert
_if
(invert
, port
))
206 def get_output(self
, pin
, port
, attrs
, invert
):
207 self
._check
_feature
("single-ended output", pin
, attrs
,
208 valid_xdrs
=(0,), valid_attrs
=None)
211 m
.d
.comb
+= port
.eq(self
._invert
_if
(invert
, pin
.o
))
214 def get_tristate(self
, pin
, port
, attrs
, invert
):
215 self
._check
_feature
("single-ended tristate", pin
, attrs
,
216 valid_xdrs
=(0,), valid_attrs
=None)
219 m
.submodules
+= Instance("$tribuf",
222 i_A
=self
._invert
_if
(invert
, pin
.o
),
227 def get_input_output(self
, pin
, port
, attrs
, invert
):
228 self
._check
_feature
("single-ended input/output", pin
, attrs
,
229 valid_xdrs
=(0,), valid_attrs
=None)
232 m
.submodules
+= Instance("$tribuf",
235 i_A
=self
._invert
_if
(invert
, pin
.o
),
238 m
.d
.comb
+= pin
.i
.eq(self
._invert
_if
(invert
, port
))
241 def get_diff_input(self
, pin
, port
, attrs
, invert
):
242 self
._check
_feature
("differential input", pin
, attrs
,
243 valid_xdrs
=(), valid_attrs
=None)
245 def get_diff_output(self
, pin
, port
, attrs
, invert
):
246 self
._check
_feature
("differential output", pin
, attrs
,
247 valid_xdrs
=(), valid_attrs
=None)
249 def get_diff_tristate(self
, pin
, port
, attrs
, invert
):
250 self
._check
_feature
("differential tristate", pin
, attrs
,
251 valid_xdrs
=(), valid_attrs
=None)
253 def get_diff_input_output(self
, pin
, port
, attrs
, invert
):
254 self
._check
_feature
("differential input/output", pin
, attrs
,
255 valid_xdrs
=(), valid_attrs
=None)
258 class TemplatedPlatform(Platform
):
259 toolchain
= abstractproperty()
260 file_templates
= abstractproperty()
261 command_templates
= abstractproperty()
263 build_script_templates
= {
264 "build_{{name}}.sh": """
266 set -e{{verbose("x")}}
267 [ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}"
268 {{emit_commands("sh")}}
270 "build_{{name}}.bat": """
271 @rem {{autogenerated}}
272 {{quiet("@echo off")}}
273 if defined {{platform._toolchain_env_var}} call %{{platform._toolchain_env_var}}%
274 {{emit_commands("bat")}}
278 def iter_clock_constraints(self
):
279 for net_signal
, port_signal
, frequency
in super().iter_clock_constraints():
280 # Skip any clock constraints placed on signals that are never used in the design.
281 # Otherwise, it will cause a crash in the vendor platform if it supports clock
282 # constraints on non-port nets.
283 if net_signal
not in self
._name
_map
:
285 yield net_signal
, port_signal
, frequency
287 def toolchain_prepare(self
, fragment
, name
, **kwargs
):
288 # Restrict the name of the design to a strict alphanumeric character set. Platforms will
289 # interpolate the name of the design in many different contexts: filesystem paths, Python
290 # scripts, Tcl scripts, ad-hoc constraint files, and so on. It is not practical to add
291 # escaping code that handles every one of their edge cases, so make sure we never hit them
292 # in the first place.
293 invalid_char
= re
.match(r
"[^A-Za-z0-9_]", name
)
295 raise ValueError("Design name {!r} contains invalid character {!r}; only alphanumeric "
296 "characters are valid in design names"
297 .format(name
, invalid_char
.group(0)))
299 # This notice serves a dual purpose: to explain that the file is autogenerated,
300 # and to incorporate the nMigen version into generated code.
301 autogenerated
= "Automatically generated by nMigen {}. Do not edit.".format(__version__
)
303 rtlil_text
, self
._name
_map
= rtlil
.convert_fragment(fragment
, name
=name
)
308 def emit_verilog(opts
=()):
309 return verilog
._convert
_rtlil
_text
(rtlil_text
,
310 strip_internal_attrs
=True, write_verilog_opts
=opts
)
312 def emit_debug_verilog(opts
=()):
313 return verilog
._convert
_rtlil
_text
(rtlil_text
,
314 strip_internal_attrs
=False, write_verilog_opts
=opts
)
316 def emit_commands(syntax
):
319 for name
in self
.required_tools
:
320 env_var
= tool_env_var(name
)
322 template
= ": ${{{env_var}:={name}}}"
323 elif syntax
== "bat":
325 "if [%{env_var}%] equ [\"\"] set {env_var}=\n" \
326 "if [%{env_var}%] equ [] set {env_var}={name}"
329 commands
.append(template
.format(env_var
=env_var
, name
=name
))
331 for index
, command_tpl
in enumerate(self
.command_templates
):
332 command
= render(command_tpl
, origin
="<command#{}>".format(index
+ 1),
334 command
= re
.sub(r
"\s+", " ", command
)
336 commands
.append(command
)
337 elif syntax
== "bat":
338 commands
.append(command
+ " || exit /b")
342 return "\n".join(commands
)
344 def get_override(var
):
345 var_env
= "NMIGEN_{}".format(var
)
346 if var_env
in os
.environ
:
347 # On Windows, there is no way to define an "empty but set" variable; it is tempting
348 # to use a quoted empty string, but it doesn't do what one would expect. Recognize
349 # this as a useful pattern anyway, and treat `set VAR=""` on Windows the same way
350 # `export VAR=` is treated on Linux.
351 return re
.sub(r
'^\"\"$', "", os
.environ
[var_env
])
353 if isinstance(kwargs
[var
], str):
354 return textwrap
.dedent(kwargs
[var
]).strip()
358 return jinja2
.Undefined(name
=var
)
360 @jinja2.contextfunction
361 def invoke_tool(context
, name
):
362 env_var
= tool_env_var(name
)
363 if context
.parent
["syntax"] == "sh":
364 return "\"${}\"".format(env_var
)
365 elif context
.parent
["syntax"] == "bat":
366 return "%{}%".format(env_var
)
371 if isinstance(opts
, str):
374 return " ".join(opts
)
376 def hierarchy(signal
, separator
):
377 return separator
.join(self
._name
_map
[signal
][1:])
379 def ascii_escape(string
):
380 def escape_one(match
):
381 if match
.group(1) is None:
382 return match
.group(2)
384 return "_{:02x}_".format(ord(match
.group(1)[0]))
385 return "".join(escape_one(m
) for m
in re
.finditer(r
"([^A-Za-z0-9_])|(.)", string
))
387 def tcl_escape(string
):
388 return "{" + re
.sub(r
"([{}\\])", r
"\\\1", string
) + "}"
390 def tcl_quote(string
):
391 return '"' + re
.sub(r
"([$[\\])", r
"\\\1", string
) + '"'
394 if "NMIGEN_verbose" in os
.environ
:
397 return jinja2
.Undefined(name
="quiet")
400 if "NMIGEN_verbose" in os
.environ
:
401 return jinja2
.Undefined(name
="quiet")
405 def render(source
, origin
, syntax
=None):
407 source
= textwrap
.dedent(source
).strip()
408 compiled
= jinja2
.Template(source
,
409 trim_blocks
=True, lstrip_blocks
=True, undefined
=jinja2
.StrictUndefined
)
410 compiled
.environment
.filters
["options"] = options
411 compiled
.environment
.filters
["hierarchy"] = hierarchy
412 compiled
.environment
.filters
["ascii_escape"] = ascii_escape
413 compiled
.environment
.filters
["tcl_escape"] = tcl_escape
414 compiled
.environment
.filters
["tcl_quote"] = tcl_quote
415 except jinja2
.TemplateSyntaxError
as e
:
416 e
.args
= ("{} (at {}:{})".format(e
.message
, origin
, e
.lineno
),)
418 return compiled
.render({
421 "emit_rtlil": emit_rtlil
,
422 "emit_verilog": emit_verilog
,
423 "emit_debug_verilog": emit_debug_verilog
,
424 "emit_commands": emit_commands
,
426 "invoke_tool": invoke_tool
,
427 "get_override": get_override
,
430 "autogenerated": autogenerated
,
433 plan
= BuildPlan(script
="build_{}".format(name
))
434 for filename_tpl
, content_tpl
in self
.file_templates
.items():
435 plan
.add_file(render(filename_tpl
, origin
=filename_tpl
),
436 render(content_tpl
, origin
=content_tpl
))
437 for filename
, content
in self
.extra_files
.items():
438 plan
.add_file(filename
, content
)
441 def iter_extra_files(self
, *endswith
):
442 return (f
for f
in self
.extra_files
if f
.endswith(endswith
))