2019-06-01 10:43:27 -06:00
|
|
|
from collections import OrderedDict
|
|
|
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
|
|
|
import os
|
|
|
|
import textwrap
|
|
|
|
import re
|
|
|
|
import jinja2
|
|
|
|
|
|
|
|
from .. import __version__
|
2019-08-28 05:32:18 -06:00
|
|
|
from .._toolchain import *
|
build.plat,vendor: always synchronize reset in default sync domain.
This change achieves two related goals.
First, default_rst is no longer assumed to be synchronous to
default_clk, which is the safer option, since it can be connected to
e.g. buttons on some evaluation boards.
Second, since the power-on / configuration reset is inherently
asynchronous to any user clock, the default create_missing_domain()
behavior is to use a reset synchronizer with `0` as input. Since,
like all reset synchronizers, it uses Signal(reset=1) for its
synchronization stages, after power-on reset it keeps its subordinate
clock domain in reset, and releases it after fabric flops start
toggling.
The latter change is helpful to architectures that lack an end-of-
configuration signal, i.e. most of them. ECP5 was already using
a similar scheme (and is not changed here). Xilinx devices with EOS
use EOS to drive a BUFGMUX, which is more efficient than using
a global reset when the design does not need one; Xilinx devices
without EOS use the new scheme. iCE40 requires a post-configuration
timer because of BRAM silicon bug, and was changed to add a reset
synchronizer if user clock is provided.
2019-10-09 14:02:33 -06:00
|
|
|
from ..hdl import *
|
build.plat: align pipeline with Fragment.prepare().
Since commit 7257c20a, platform code calls create_missing_domains()
before _propagate_domains_up() (as a part of prepare() call). Since
commit a7be3b48, without a platform, create_missing_domains() is
calle after _propagate_domains_up(); because of that, it adds
the missing domain to the fragment. When platform code then calls
prepare() again, this causes an assertion failure.
The true intent behind the platform code being written this way is
that it *overrides* a part of prepare()'s mechanism. Because it was
not changed when prepare() was modified in 7257c20a, the override,
which happened to work by coincidence, stopped working. This is
now fixed by inlining the relevant parts of Fragment.prepare() into
Platform.prepare().
This is not a great solution, but given the amount of breakage this
causes (no platform-using code works), it is acceptable for now.
Fixes #307.
2020-01-31 20:24:26 -07:00
|
|
|
from ..hdl.xfrm import SampleLowerer, DomainLowerer
|
build.plat,vendor: always synchronize reset in default sync domain.
This change achieves two related goals.
First, default_rst is no longer assumed to be synchronous to
default_clk, which is the safer option, since it can be connected to
e.g. buttons on some evaluation boards.
Second, since the power-on / configuration reset is inherently
asynchronous to any user clock, the default create_missing_domain()
behavior is to use a reset synchronizer with `0` as input. Since,
like all reset synchronizers, it uses Signal(reset=1) for its
synchronization stages, after power-on reset it keeps its subordinate
clock domain in reset, and releases it after fabric flops start
toggling.
The latter change is helpful to architectures that lack an end-of-
configuration signal, i.e. most of them. ECP5 was already using
a similar scheme (and is not changed here). Xilinx devices with EOS
use EOS to drive a BUFGMUX, which is more efficient than using
a global reset when the design does not need one; Xilinx devices
without EOS use the new scheme. iCE40 requires a post-configuration
timer because of BRAM silicon bug, and was changed to add a reset
synchronizer if user clock is provided.
2019-10-09 14:02:33 -06:00
|
|
|
from ..lib.cdc import ResetSynchronizer
|
2019-06-01 10:43:27 -06:00
|
|
|
from ..back import rtlil, verilog
|
2019-06-04 01:53:34 -06:00
|
|
|
from .res import *
|
|
|
|
from .run import *
|
2019-06-01 10:43:27 -06:00
|
|
|
|
|
|
|
|
|
|
|
__all__ = ["Platform", "TemplatedPlatform"]
|
|
|
|
|
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
class Platform(ResourceManager, metaclass=ABCMeta):
|
2019-08-30 17:27:22 -06:00
|
|
|
resources = abstractproperty()
|
|
|
|
connectors = abstractproperty()
|
|
|
|
default_clk = None
|
|
|
|
default_rst = None
|
|
|
|
required_tools = abstractproperty()
|
2019-06-01 10:43:27 -06:00
|
|
|
|
|
|
|
def __init__(self):
|
2019-06-05 02:48:36 -06:00
|
|
|
super().__init__(self.resources, self.connectors)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
|
|
|
self.extra_files = OrderedDict()
|
|
|
|
|
|
|
|
self._prepared = False
|
|
|
|
|
2019-08-03 10:18:46 -06:00
|
|
|
@property
|
|
|
|
def default_clk_constraint(self):
|
|
|
|
if self.default_clk is None:
|
|
|
|
raise AttributeError("Platform '{}' does not define a default clock"
|
2019-09-24 08:14:45 -06:00
|
|
|
.format(type(self).__name__))
|
2019-08-03 10:18:46 -06:00
|
|
|
return self.lookup(self.default_clk).clock
|
|
|
|
|
|
|
|
@property
|
|
|
|
def default_clk_frequency(self):
|
|
|
|
constraint = self.default_clk_constraint
|
|
|
|
if constraint is None:
|
|
|
|
raise AttributeError("Platform '{}' does not constrain its default clock"
|
2019-09-24 08:14:45 -06:00
|
|
|
.format(type(self).__name__))
|
2019-08-03 10:18:46 -06:00
|
|
|
return constraint.frequency
|
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
def add_file(self, filename, content):
|
|
|
|
if not isinstance(filename, str):
|
2019-11-15 16:35:55 -07:00
|
|
|
raise TypeError("File name must be a string, not {!r}"
|
|
|
|
.format(filename))
|
2019-06-01 10:43:27 -06:00
|
|
|
if hasattr(content, "read"):
|
|
|
|
content = content.read()
|
|
|
|
elif not isinstance(content, (str, bytes)):
|
2019-11-15 16:35:55 -07:00
|
|
|
raise TypeError("File contents must be str, bytes, or a file-like object, not {!r}"
|
|
|
|
.format(content))
|
2019-11-15 16:40:44 -07:00
|
|
|
if filename in self.extra_files:
|
|
|
|
if self.extra_files[filename] != content:
|
|
|
|
raise ValueError("File {!r} already exists"
|
|
|
|
.format(filename))
|
|
|
|
else:
|
|
|
|
self.extra_files[filename] = content
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2020-11-09 22:30:21 -07:00
|
|
|
def iter_files(self, *suffixes):
|
|
|
|
for filename in self.extra_files:
|
|
|
|
if filename.endswith(suffixes):
|
|
|
|
yield filename
|
|
|
|
|
2019-09-12 15:56:48 -06:00
|
|
|
@property
|
|
|
|
def _toolchain_env_var(self):
|
2019-09-21 06:23:53 -06:00
|
|
|
return f"NMIGEN_ENV_{self.toolchain}"
|
2019-09-12 15:56:48 -06:00
|
|
|
|
2019-08-19 16:32:50 -06:00
|
|
|
def build(self, elaboratable, name="top",
|
2019-06-01 10:43:27 -06:00
|
|
|
build_dir="build", do_build=True,
|
|
|
|
program_opts=None, do_program=False,
|
|
|
|
**kwargs):
|
2020-04-11 21:28:29 -06:00
|
|
|
# The following code performs a best-effort check for presence of required tools upfront,
|
|
|
|
# before performing any build actions, to provide a better diagnostic. It does not handle
|
|
|
|
# several corner cases:
|
|
|
|
# 1. `require_tool` does not source toolchain environment scripts, so if such a script
|
|
|
|
# is used, the check is skipped, and `execute_local()` may fail;
|
|
|
|
# 2. if the design is not built (do_build=False), most of the tools are not required and
|
|
|
|
# in fact might not be available if the design will be built manually with a different
|
|
|
|
# environment script specified, or on a different machine; however, Yosys is required
|
|
|
|
# by virtually every platform anyway, to provide debug Verilog output, and `prepare()`
|
|
|
|
# may fail.
|
|
|
|
# This is OK because even if `require_tool` succeeds, the toolchain might be broken anyway.
|
|
|
|
# The check only serves to catch common errors earlier.
|
|
|
|
if do_build and self._toolchain_env_var not in os.environ:
|
2019-09-12 15:56:48 -06:00
|
|
|
for tool in self.required_tools:
|
|
|
|
require_tool(tool)
|
2019-08-30 17:27:22 -06:00
|
|
|
|
2019-08-19 16:32:50 -06:00
|
|
|
plan = self.prepare(elaboratable, name, **kwargs)
|
2019-06-01 10:43:27 -06:00
|
|
|
if not do_build:
|
|
|
|
return plan
|
|
|
|
|
2019-07-06 18:07:55 -06:00
|
|
|
products = plan.execute_local(build_dir)
|
2019-06-01 10:43:27 -06:00
|
|
|
if not do_program:
|
|
|
|
return products
|
|
|
|
|
|
|
|
self.toolchain_program(products, name, **(program_opts or {}))
|
|
|
|
|
2019-08-30 17:27:22 -06:00
|
|
|
def has_required_tools(self):
|
2019-09-12 15:56:48 -06:00
|
|
|
if self._toolchain_env_var in os.environ:
|
|
|
|
return True
|
2019-08-30 17:27:22 -06:00
|
|
|
return all(has_tool(name) for name in self.required_tools)
|
|
|
|
|
2019-08-03 12:36:58 -06:00
|
|
|
def create_missing_domain(self, name):
|
2019-08-04 17:27:47 -06:00
|
|
|
# Simple instantiation of a clock domain driven directly by the board clock and reset.
|
build.plat,vendor: always synchronize reset in default sync domain.
This change achieves two related goals.
First, default_rst is no longer assumed to be synchronous to
default_clk, which is the safer option, since it can be connected to
e.g. buttons on some evaluation boards.
Second, since the power-on / configuration reset is inherently
asynchronous to any user clock, the default create_missing_domain()
behavior is to use a reset synchronizer with `0` as input. Since,
like all reset synchronizers, it uses Signal(reset=1) for its
synchronization stages, after power-on reset it keeps its subordinate
clock domain in reset, and releases it after fabric flops start
toggling.
The latter change is helpful to architectures that lack an end-of-
configuration signal, i.e. most of them. ECP5 was already using
a similar scheme (and is not changed here). Xilinx devices with EOS
use EOS to drive a BUFGMUX, which is more efficient than using
a global reset when the design does not need one; Xilinx devices
without EOS use the new scheme. iCE40 requires a post-configuration
timer because of BRAM silicon bug, and was changed to add a reset
synchronizer if user clock is provided.
2019-10-09 14:02:33 -06:00
|
|
|
# This implementation uses a single ResetSynchronizer to ensure that:
|
|
|
|
# * an external reset is definitely synchronized to the system clock;
|
|
|
|
# * release of power-on reset, which is inherently asynchronous, is synchronized to
|
|
|
|
# the system clock.
|
|
|
|
# Many device families provide advanced primitives for tackling reset. If these exist,
|
|
|
|
# they should be used instead.
|
2019-08-03 12:36:58 -06:00
|
|
|
if name == "sync" and self.default_clk is not None:
|
|
|
|
clk_i = self.request(self.default_clk).i
|
|
|
|
if self.default_rst is not None:
|
|
|
|
rst_i = self.request(self.default_rst).i
|
build.plat,vendor: always synchronize reset in default sync domain.
This change achieves two related goals.
First, default_rst is no longer assumed to be synchronous to
default_clk, which is the safer option, since it can be connected to
e.g. buttons on some evaluation boards.
Second, since the power-on / configuration reset is inherently
asynchronous to any user clock, the default create_missing_domain()
behavior is to use a reset synchronizer with `0` as input. Since,
like all reset synchronizers, it uses Signal(reset=1) for its
synchronization stages, after power-on reset it keeps its subordinate
clock domain in reset, and releases it after fabric flops start
toggling.
The latter change is helpful to architectures that lack an end-of-
configuration signal, i.e. most of them. ECP5 was already using
a similar scheme (and is not changed here). Xilinx devices with EOS
use EOS to drive a BUFGMUX, which is more efficient than using
a global reset when the design does not need one; Xilinx devices
without EOS use the new scheme. iCE40 requires a post-configuration
timer because of BRAM silicon bug, and was changed to add a reset
synchronizer if user clock is provided.
2019-10-09 14:02:33 -06:00
|
|
|
else:
|
|
|
|
rst_i = Const(0)
|
2019-08-03 12:36:58 -06:00
|
|
|
|
|
|
|
m = Module()
|
2019-10-09 14:44:07 -06:00
|
|
|
m.domains += ClockDomain("sync")
|
2019-08-03 12:36:58 -06:00
|
|
|
m.d.comb += ClockSignal("sync").eq(clk_i)
|
build.plat,vendor: always synchronize reset in default sync domain.
This change achieves two related goals.
First, default_rst is no longer assumed to be synchronous to
default_clk, which is the safer option, since it can be connected to
e.g. buttons on some evaluation boards.
Second, since the power-on / configuration reset is inherently
asynchronous to any user clock, the default create_missing_domain()
behavior is to use a reset synchronizer with `0` as input. Since,
like all reset synchronizers, it uses Signal(reset=1) for its
synchronization stages, after power-on reset it keeps its subordinate
clock domain in reset, and releases it after fabric flops start
toggling.
The latter change is helpful to architectures that lack an end-of-
configuration signal, i.e. most of them. ECP5 was already using
a similar scheme (and is not changed here). Xilinx devices with EOS
use EOS to drive a BUFGMUX, which is more efficient than using
a global reset when the design does not need one; Xilinx devices
without EOS use the new scheme. iCE40 requires a post-configuration
timer because of BRAM silicon bug, and was changed to add a reset
synchronizer if user clock is provided.
2019-10-09 14:02:33 -06:00
|
|
|
m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
|
2019-08-03 12:36:58 -06:00
|
|
|
return m
|
|
|
|
|
2019-08-19 16:32:50 -06:00
|
|
|
def prepare(self, elaboratable, name="top", **kwargs):
|
2019-06-01 10:43:27 -06:00
|
|
|
assert not self._prepared
|
|
|
|
self._prepared = True
|
|
|
|
|
2019-08-19 16:32:50 -06:00
|
|
|
fragment = Fragment.get(elaboratable, self)
|
build.plat: align pipeline with Fragment.prepare().
Since commit 7257c20a, platform code calls create_missing_domains()
before _propagate_domains_up() (as a part of prepare() call). Since
commit a7be3b48, without a platform, create_missing_domains() is
calle after _propagate_domains_up(); because of that, it adds
the missing domain to the fragment. When platform code then calls
prepare() again, this causes an assertion failure.
The true intent behind the platform code being written this way is
that it *overrides* a part of prepare()'s mechanism. Because it was
not changed when prepare() was modified in 7257c20a, the override,
which happened to work by coincidence, stopped working. This is
now fixed by inlining the relevant parts of Fragment.prepare() into
Platform.prepare().
This is not a great solution, but given the amount of breakage this
causes (no platform-using code works), it is acceptable for now.
Fixes #307.
2020-01-31 20:24:26 -07:00
|
|
|
fragment = SampleLowerer()(fragment)
|
|
|
|
fragment._propagate_domains(self.create_missing_domain, platform=self)
|
|
|
|
fragment = DomainLowerer()(fragment)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2019-06-02 19:58:43 -06:00
|
|
|
def add_pin_fragment(pin, pin_fragment):
|
|
|
|
pin_fragment = Fragment.get(pin_fragment, self)
|
|
|
|
if not isinstance(pin_fragment, Instance):
|
|
|
|
pin_fragment.flatten = True
|
|
|
|
fragment.add_subfragment(pin_fragment, name="pin_{}".format(pin.name))
|
|
|
|
|
2019-06-12 08:42:39 -06:00
|
|
|
for pin, port, attrs, invert in self.iter_single_ended_pins():
|
2019-06-01 10:43:27 -06:00
|
|
|
if pin.dir == "i":
|
2019-06-12 08:42:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_input(pin, port, attrs, invert))
|
2019-06-01 10:43:27 -06:00
|
|
|
if pin.dir == "o":
|
2019-06-12 08:42:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_output(pin, port, attrs, invert))
|
2019-06-02 22:39:05 -06:00
|
|
|
if pin.dir == "oe":
|
2019-06-12 08:42:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_tristate(pin, port, attrs, invert))
|
2019-06-01 10:43:27 -06:00
|
|
|
if pin.dir == "io":
|
2019-06-12 08:42:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_input_output(pin, port, attrs, invert))
|
2019-06-02 19:58:43 -06:00
|
|
|
|
2020-07-31 07:17:39 -06:00
|
|
|
for pin, port, attrs, invert in self.iter_differential_pins():
|
2019-06-01 10:43:27 -06:00
|
|
|
if pin.dir == "i":
|
2020-07-31 07:17:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_diff_input(pin, port, attrs, invert))
|
2019-06-01 10:43:27 -06:00
|
|
|
if pin.dir == "o":
|
2020-07-31 07:17:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_diff_output(pin, port, attrs, invert))
|
2019-06-02 22:39:05 -06:00
|
|
|
if pin.dir == "oe":
|
2020-07-31 07:17:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_diff_tristate(pin, port, attrs, invert))
|
2019-06-02 22:39:05 -06:00
|
|
|
if pin.dir == "io":
|
2020-07-31 07:17:39 -06:00
|
|
|
add_pin_fragment(pin, self.get_diff_input_output(pin, port, attrs, invert))
|
2019-06-01 10:43:27 -06:00
|
|
|
|
build.plat: align pipeline with Fragment.prepare().
Since commit 7257c20a, platform code calls create_missing_domains()
before _propagate_domains_up() (as a part of prepare() call). Since
commit a7be3b48, without a platform, create_missing_domains() is
calle after _propagate_domains_up(); because of that, it adds
the missing domain to the fragment. When platform code then calls
prepare() again, this causes an assertion failure.
The true intent behind the platform code being written this way is
that it *overrides* a part of prepare()'s mechanism. Because it was
not changed when prepare() was modified in 7257c20a, the override,
which happened to work by coincidence, stopped working. This is
now fixed by inlining the relevant parts of Fragment.prepare() into
Platform.prepare().
This is not a great solution, but given the amount of breakage this
causes (no platform-using code works), it is acceptable for now.
Fixes #307.
2020-01-31 20:24:26 -07:00
|
|
|
fragment._propagate_ports(ports=self.iter_ports(), all_undef_as_ports=False)
|
2019-06-01 10:43:27 -06:00
|
|
|
return self.toolchain_prepare(fragment, name, **kwargs)
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def toolchain_prepare(self, fragment, name, **kwargs):
|
|
|
|
"""
|
|
|
|
Convert the ``fragment`` and constraints recorded in this :class:`Platform` into
|
|
|
|
a :class:`BuildPlan`.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError # :nocov:
|
|
|
|
|
|
|
|
def toolchain_program(self, products, name, **kwargs):
|
|
|
|
"""
|
|
|
|
Extract bitstream for fragment ``name`` from ``products`` and download it to a target.
|
|
|
|
"""
|
2019-09-24 08:14:45 -06:00
|
|
|
raise NotImplementedError("Platform '{}' does not support programming"
|
|
|
|
.format(type(self).__name__))
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
def _check_feature(self, feature, pin, attrs, valid_xdrs, valid_attrs):
|
2020-10-15 02:54:48 -06:00
|
|
|
if len(valid_xdrs) == 0:
|
2019-09-24 08:14:45 -06:00
|
|
|
raise NotImplementedError("Platform '{}' does not support {}"
|
|
|
|
.format(type(self).__name__, feature))
|
2019-06-02 19:58:43 -06:00
|
|
|
elif pin.xdr not in valid_xdrs:
|
2019-09-24 08:14:45 -06:00
|
|
|
raise NotImplementedError("Platform '{}' does not support {} for XDR {}"
|
|
|
|
.format(type(self).__name__, feature, pin.xdr))
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2019-06-05 01:02:08 -06:00
|
|
|
if not valid_attrs and attrs:
|
2019-09-24 08:14:45 -06:00
|
|
|
raise NotImplementedError("Platform '{}' does not support attributes for {}"
|
|
|
|
.format(type(self).__name__, feature))
|
2019-06-02 19:58:43 -06:00
|
|
|
|
2019-06-12 08:42:39 -06:00
|
|
|
@staticmethod
|
|
|
|
def _invert_if(invert, value):
|
|
|
|
if invert:
|
|
|
|
return ~value
|
|
|
|
else:
|
|
|
|
return value
|
|
|
|
|
|
|
|
def get_input(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("single-ended input", pin, attrs,
|
|
|
|
valid_xdrs=(0,), valid_attrs=None)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
|
|
|
m = Module()
|
2019-06-12 08:42:39 -06:00
|
|
|
m.d.comb += pin.i.eq(self._invert_if(invert, port))
|
2019-06-01 10:43:27 -06:00
|
|
|
return m
|
|
|
|
|
2019-06-12 08:42:39 -06:00
|
|
|
def get_output(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("single-ended output", pin, attrs,
|
|
|
|
valid_xdrs=(0,), valid_attrs=None)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
|
|
|
m = Module()
|
2019-06-12 08:42:39 -06:00
|
|
|
m.d.comb += port.eq(self._invert_if(invert, pin.o))
|
2019-06-01 10:43:27 -06:00
|
|
|
return m
|
|
|
|
|
2019-06-12 08:42:39 -06:00
|
|
|
def get_tristate(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("single-ended tristate", pin, attrs,
|
|
|
|
valid_xdrs=(0,), valid_attrs=None)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2019-06-02 22:39:05 -06:00
|
|
|
m = Module()
|
|
|
|
m.submodules += Instance("$tribuf",
|
|
|
|
p_WIDTH=pin.width,
|
|
|
|
i_EN=pin.oe,
|
2019-06-12 08:42:39 -06:00
|
|
|
i_A=self._invert_if(invert, pin.o),
|
2019-06-02 22:39:05 -06:00
|
|
|
o_Y=port,
|
|
|
|
)
|
|
|
|
return m
|
|
|
|
|
2019-06-12 08:42:39 -06:00
|
|
|
def get_input_output(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("single-ended input/output", pin, attrs,
|
|
|
|
valid_xdrs=(0,), valid_attrs=None)
|
2019-06-02 22:39:05 -06:00
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
m = Module()
|
|
|
|
m.submodules += Instance("$tribuf",
|
|
|
|
p_WIDTH=pin.width,
|
|
|
|
i_EN=pin.oe,
|
2019-06-12 08:42:39 -06:00
|
|
|
i_A=self._invert_if(invert, pin.o),
|
2019-06-01 10:43:27 -06:00
|
|
|
o_Y=port,
|
|
|
|
)
|
2019-06-12 08:42:39 -06:00
|
|
|
m.d.comb += pin.i.eq(self._invert_if(invert, port))
|
2019-06-01 10:43:27 -06:00
|
|
|
return m
|
|
|
|
|
2020-07-31 07:17:39 -06:00
|
|
|
def get_diff_input(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("differential input", pin, attrs,
|
|
|
|
valid_xdrs=(), valid_attrs=None)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2020-07-31 07:17:39 -06:00
|
|
|
def get_diff_output(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("differential output", pin, attrs,
|
|
|
|
valid_xdrs=(), valid_attrs=None)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2020-07-31 07:17:39 -06:00
|
|
|
def get_diff_tristate(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("differential tristate", pin, attrs,
|
|
|
|
valid_xdrs=(), valid_attrs=None)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2020-07-31 07:17:39 -06:00
|
|
|
def get_diff_input_output(self, pin, port, attrs, invert):
|
2019-06-05 01:02:08 -06:00
|
|
|
self._check_feature("differential input/output", pin, attrs,
|
|
|
|
valid_xdrs=(), valid_attrs=None)
|
2019-06-02 22:39:05 -06:00
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
|
|
|
|
class TemplatedPlatform(Platform):
|
2019-07-06 18:41:03 -06:00
|
|
|
toolchain = abstractproperty()
|
2019-06-01 10:43:27 -06:00
|
|
|
file_templates = abstractproperty()
|
|
|
|
command_templates = abstractproperty()
|
|
|
|
|
|
|
|
build_script_templates = {
|
|
|
|
"build_{{name}}.sh": """
|
|
|
|
# {{autogenerated}}
|
|
|
|
set -e{{verbose("x")}}
|
2019-09-12 15:56:48 -06:00
|
|
|
[ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}"
|
2019-06-01 10:43:27 -06:00
|
|
|
{{emit_commands("sh")}}
|
|
|
|
""",
|
|
|
|
"build_{{name}}.bat": """
|
|
|
|
@rem {{autogenerated}}
|
2019-06-04 05:34:18 -06:00
|
|
|
{{quiet("@echo off")}}
|
2019-09-12 15:56:48 -06:00
|
|
|
if defined {{platform._toolchain_env_var}} call %{{platform._toolchain_env_var}}%
|
2019-06-01 10:43:27 -06:00
|
|
|
{{emit_commands("bat")}}
|
|
|
|
""",
|
|
|
|
}
|
|
|
|
|
2020-05-19 23:35:47 -06:00
|
|
|
def iter_clock_constraints(self):
|
|
|
|
for net_signal, port_signal, frequency in super().iter_clock_constraints():
|
|
|
|
# Skip any clock constraints placed on signals that are never used in the design.
|
|
|
|
# Otherwise, it will cause a crash in the vendor platform if it supports clock
|
|
|
|
# constraints on non-port nets.
|
|
|
|
if net_signal not in self._name_map:
|
|
|
|
continue
|
|
|
|
yield net_signal, port_signal, frequency
|
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
def toolchain_prepare(self, fragment, name, **kwargs):
|
2019-09-22 00:57:28 -06:00
|
|
|
# Restrict the name of the design to a strict alphanumeric character set. Platforms will
|
|
|
|
# interpolate the name of the design in many different contexts: filesystem paths, Python
|
|
|
|
# scripts, Tcl scripts, ad-hoc constraint files, and so on. It is not practical to add
|
|
|
|
# escaping code that handles every one of their edge cases, so make sure we never hit them
|
|
|
|
# in the first place.
|
|
|
|
invalid_char = re.match(r"[^A-Za-z0-9_]", name)
|
|
|
|
if invalid_char:
|
|
|
|
raise ValueError("Design name {!r} contains invalid character {!r}; only alphanumeric "
|
|
|
|
"characters are valid in design names"
|
|
|
|
.format(name, invalid_char.group(0)))
|
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
# This notice serves a dual purpose: to explain that the file is autogenerated,
|
2019-06-04 20:48:41 -06:00
|
|
|
# and to incorporate the nMigen version into generated code.
|
2019-06-01 10:43:27 -06:00
|
|
|
autogenerated = "Automatically generated by nMigen {}. Do not edit.".format(__version__)
|
|
|
|
|
2020-05-19 23:35:47 -06:00
|
|
|
rtlil_text, self._name_map = rtlil.convert_fragment(fragment, name=name)
|
2019-09-24 08:54:22 -06:00
|
|
|
|
|
|
|
def emit_rtlil():
|
|
|
|
return rtlil_text
|
|
|
|
|
2019-08-21 16:14:33 -06:00
|
|
|
def emit_verilog(opts=()):
|
|
|
|
return verilog._convert_rtlil_text(rtlil_text,
|
|
|
|
strip_internal_attrs=True, write_verilog_opts=opts)
|
2019-09-24 08:54:22 -06:00
|
|
|
|
2019-08-21 16:14:33 -06:00
|
|
|
def emit_debug_verilog(opts=()):
|
|
|
|
return verilog._convert_rtlil_text(rtlil_text,
|
|
|
|
strip_internal_attrs=False, write_verilog_opts=opts)
|
2019-06-01 10:43:27 -06:00
|
|
|
|
2019-10-13 07:57:48 -06:00
|
|
|
def emit_commands(syntax):
|
2019-10-13 07:53:24 -06:00
|
|
|
commands = []
|
2019-10-13 07:57:48 -06:00
|
|
|
|
2019-10-13 07:53:24 -06:00
|
|
|
for name in self.required_tools:
|
|
|
|
env_var = tool_env_var(name)
|
|
|
|
if syntax == "sh":
|
|
|
|
template = ": ${{{env_var}:={name}}}"
|
|
|
|
elif syntax == "bat":
|
|
|
|
template = \
|
2019-10-13 15:45:56 -06:00
|
|
|
"if [%{env_var}%] equ [\"\"] set {env_var}=\n" \
|
|
|
|
"if [%{env_var}%] equ [] set {env_var}={name}"
|
2019-10-13 07:53:24 -06:00
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
commands.append(template.format(env_var=env_var, name=name))
|
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
for index, command_tpl in enumerate(self.command_templates):
|
2019-10-13 07:53:24 -06:00
|
|
|
command = render(command_tpl, origin="<command#{}>".format(index + 1),
|
|
|
|
syntax=syntax)
|
2019-06-01 10:43:27 -06:00
|
|
|
command = re.sub(r"\s+", " ", command)
|
2019-10-13 07:53:24 -06:00
|
|
|
if syntax == "sh":
|
2019-06-01 10:43:27 -06:00
|
|
|
commands.append(command)
|
2019-10-13 07:53:24 -06:00
|
|
|
elif syntax == "bat":
|
2019-06-01 10:43:27 -06:00
|
|
|
commands.append(command + " || exit /b")
|
|
|
|
else:
|
|
|
|
assert False
|
2019-10-13 07:57:48 -06:00
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
return "\n".join(commands)
|
|
|
|
|
|
|
|
def get_override(var):
|
|
|
|
var_env = "NMIGEN_{}".format(var)
|
|
|
|
if var_env in os.environ:
|
2019-06-04 05:33:51 -06:00
|
|
|
# On Windows, there is no way to define an "empty but set" variable; it is tempting
|
|
|
|
# to use a quoted empty string, but it doesn't do what one would expect. Recognize
|
|
|
|
# this as a useful pattern anyway, and treat `set VAR=""` on Windows the same way
|
|
|
|
# `export VAR=` is treated on Linux.
|
|
|
|
return re.sub(r'^\"\"$', "", os.environ[var_env])
|
2019-06-01 10:43:27 -06:00
|
|
|
elif var in kwargs:
|
2019-06-27 12:56:37 -06:00
|
|
|
if isinstance(kwargs[var], str):
|
|
|
|
return textwrap.dedent(kwargs[var]).strip()
|
|
|
|
else:
|
|
|
|
return kwargs[var]
|
2019-06-01 10:43:27 -06:00
|
|
|
else:
|
|
|
|
return jinja2.Undefined(name=var)
|
|
|
|
|
2019-10-13 07:53:24 -06:00
|
|
|
@jinja2.contextfunction
|
|
|
|
def invoke_tool(context, name):
|
|
|
|
env_var = tool_env_var(name)
|
|
|
|
if context.parent["syntax"] == "sh":
|
|
|
|
return "\"${}\"".format(env_var)
|
|
|
|
elif context.parent["syntax"] == "bat":
|
|
|
|
return "%{}%".format(env_var)
|
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
|
2019-07-06 17:09:46 -06:00
|
|
|
def options(opts):
|
|
|
|
if isinstance(opts, str):
|
|
|
|
return opts
|
|
|
|
else:
|
|
|
|
return " ".join(opts)
|
|
|
|
|
2019-09-11 17:35:43 -06:00
|
|
|
def hierarchy(signal, separator):
|
2020-05-19 23:35:47 -06:00
|
|
|
return separator.join(self._name_map[signal][1:])
|
2019-09-11 17:35:43 -06:00
|
|
|
|
2020-05-02 04:41:18 -06:00
|
|
|
def ascii_escape(string):
|
|
|
|
def escape_one(match):
|
|
|
|
if match.group(1) is None:
|
|
|
|
return match.group(2)
|
|
|
|
else:
|
|
|
|
return "_{:02x}_".format(ord(match.group(1)[0]))
|
|
|
|
return "".join(escape_one(m) for m in re.finditer(r"([^A-Za-z0-9_])|(.)", string))
|
|
|
|
|
|
|
|
def tcl_escape(string):
|
|
|
|
return "{" + re.sub(r"([{}\\])", r"\\\1", string) + "}"
|
|
|
|
|
2020-05-21 03:48:42 -06:00
|
|
|
def tcl_quote(string):
|
|
|
|
return '"' + re.sub(r"([$[\\])", r"\\\1", string) + '"'
|
|
|
|
|
2019-06-01 10:43:27 -06:00
|
|
|
def verbose(arg):
|
2020-11-24 16:07:09 -07:00
|
|
|
if get_override("verbose"):
|
2019-06-01 10:43:27 -06:00
|
|
|
return arg
|
|
|
|
else:
|
|
|
|
return jinja2.Undefined(name="quiet")
|
|
|
|
|
|
|
|
def quiet(arg):
|
2020-11-24 16:07:09 -07:00
|
|
|
if get_override("verbose"):
|
2019-06-01 10:43:27 -06:00
|
|
|
return jinja2.Undefined(name="quiet")
|
|
|
|
else:
|
|
|
|
return arg
|
|
|
|
|
2019-10-13 07:53:24 -06:00
|
|
|
def render(source, origin, syntax=None):
|
2019-06-01 10:43:27 -06:00
|
|
|
try:
|
|
|
|
source = textwrap.dedent(source).strip()
|
2020-04-14 00:17:16 -06:00
|
|
|
compiled = jinja2.Template(source,
|
|
|
|
trim_blocks=True, lstrip_blocks=True, undefined=jinja2.StrictUndefined)
|
2019-07-06 17:09:46 -06:00
|
|
|
compiled.environment.filters["options"] = options
|
2019-09-11 17:35:43 -06:00
|
|
|
compiled.environment.filters["hierarchy"] = hierarchy
|
2020-05-02 04:41:18 -06:00
|
|
|
compiled.environment.filters["ascii_escape"] = ascii_escape
|
|
|
|
compiled.environment.filters["tcl_escape"] = tcl_escape
|
2020-05-21 03:48:42 -06:00
|
|
|
compiled.environment.filters["tcl_quote"] = tcl_quote
|
2019-06-01 10:43:27 -06:00
|
|
|
except jinja2.TemplateSyntaxError as e:
|
|
|
|
e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),)
|
|
|
|
raise
|
|
|
|
return compiled.render({
|
|
|
|
"name": name,
|
|
|
|
"platform": self,
|
2019-09-24 08:54:22 -06:00
|
|
|
"emit_rtlil": emit_rtlil,
|
|
|
|
"emit_verilog": emit_verilog,
|
|
|
|
"emit_debug_verilog": emit_debug_verilog,
|
2019-06-01 10:43:27 -06:00
|
|
|
"emit_commands": emit_commands,
|
2019-10-13 07:53:24 -06:00
|
|
|
"syntax": syntax,
|
|
|
|
"invoke_tool": invoke_tool,
|
2019-06-01 10:43:27 -06:00
|
|
|
"get_override": get_override,
|
|
|
|
"verbose": verbose,
|
|
|
|
"quiet": quiet,
|
|
|
|
"autogenerated": autogenerated,
|
|
|
|
})
|
|
|
|
|
|
|
|
plan = BuildPlan(script="build_{}".format(name))
|
|
|
|
for filename_tpl, content_tpl in self.file_templates.items():
|
|
|
|
plan.add_file(render(filename_tpl, origin=filename_tpl),
|
2019-06-05 02:48:36 -06:00
|
|
|
render(content_tpl, origin=content_tpl))
|
2019-06-01 10:43:27 -06:00
|
|
|
for filename, content in self.extra_files.items():
|
|
|
|
plan.add_file(filename, content)
|
|
|
|
return plan
|