From 7dcefcbb5891be24e23a861018f4b07f5a73ae34 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 9 Oct 2019 20:02:33 +0000 Subject: [PATCH] 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. --- nmigen/build/plat.py | 21 +++++++++++---------- nmigen/vendor/lattice_ice40.py | 5 ++++- nmigen/vendor/xilinx_7series.py | 3 ++- nmigen/vendor/xilinx_spartan_3_6.py | 18 ++++++++++-------- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/nmigen/build/plat.py b/nmigen/build/plat.py index 0a2d28e..acad494 100644 --- a/nmigen/build/plat.py +++ b/nmigen/build/plat.py @@ -7,10 +7,8 @@ import jinja2 from .. import __version__ from .._toolchain import * -from ..hdl.ast import * -from ..hdl.cd import * -from ..hdl.dsl import * -from ..hdl.ir import * +from ..hdl import * +from ..lib.cdc import ResetSynchronizer from ..back import rtlil, verilog from .res import * from .run import * @@ -87,22 +85,25 @@ class Platform(ResourceManager, metaclass=ABCMeta): return True return all(has_tool(name) for name in self.required_tools) - @abstractmethod def create_missing_domain(self, name): # Simple instantiation of a clock domain driven directly by the board clock and reset. - # Because of device-specific considerations, this implementation generally does NOT provide - # reliable power-on/post-configuration reset, and the logic should be replaced with family - # specific logic based on vendor recommendations. + # 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. 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 + else: + rst_i = Const(0) m = Module() m.domains += ClockDomain("sync", reset_less=self.default_rst is None) m.d.comb += ClockSignal("sync").eq(clk_i) - if self.default_rst is not None: - m.d.comb += ResetSignal("sync").eq(rst_i) + m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") return m def prepare(self, elaboratable, name="top", **kwargs): diff --git a/nmigen/vendor/lattice_ice40.py b/nmigen/vendor/lattice_ice40.py index cdd488f..cc395c0 100644 --- a/nmigen/vendor/lattice_ice40.py +++ b/nmigen/vendor/lattice_ice40.py @@ -1,6 +1,7 @@ from abc import abstractproperty from ..hdl import * +from ..lib.cdc import ResetSynchronizer from ..build import * @@ -346,6 +347,8 @@ class LatticeICE40Platform(TemplatedPlatform): clk_i = self.request(self.default_clk).i if self.default_rst is not None: rst_i = self.request(self.default_rst).i + else: + rst_i = Const(0) m = Module() # Power-on-reset domain @@ -362,7 +365,7 @@ class LatticeICE40Platform(TemplatedPlatform): m.domains += ClockDomain("sync") m.d.comb += ClockSignal("sync").eq(clk_i) if self.default_rst is not None: - m.d.comb += ResetSignal("sync").eq(~ready | rst_i) + m.submodules.reset_sync = ResetSynchronizer(~ready | rst_i, domain="sync") else: m.d.comb += ResetSignal("sync").eq(~ready) return m diff --git a/nmigen/vendor/xilinx_7series.py b/nmigen/vendor/xilinx_7series.py index 0ff1972..0d64e3e 100644 --- a/nmigen/vendor/xilinx_7series.py +++ b/nmigen/vendor/xilinx_7series.py @@ -1,6 +1,7 @@ from abc import abstractproperty from ..hdl import * +from ..lib.cdc import ResetSynchronizer from ..build import * @@ -164,7 +165,7 @@ class Xilinx7SeriesPlatform(TemplatedPlatform): m.domains += ClockDomain("sync", reset_less=self.default_rst is None) m.submodules += Instance("BUFGCE", i_CE=ready, i_I=clk_i, o_O=ClockSignal("sync")) if self.default_rst is not None: - m.d.comb += ResetSignal("sync").eq(rst_i) + m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") return m def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False): diff --git a/nmigen/vendor/xilinx_spartan_3_6.py b/nmigen/vendor/xilinx_spartan_3_6.py index 6945542..65f4a0a 100644 --- a/nmigen/vendor/xilinx_spartan_3_6.py +++ b/nmigen/vendor/xilinx_spartan_3_6.py @@ -1,6 +1,7 @@ from abc import abstractproperty from ..hdl import * +from ..lib.cdc import ResetSynchronizer from ..build import * @@ -187,22 +188,23 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform): # signal (if available). For details, see: # * https://www.xilinx.com/support/answers/44174.html # * https://www.xilinx.com/support/documentation/white_papers/wp272.pdf + if self.family != "6": + # Spartan 3 lacks a STARTUP primitive with EOS output; use a simple ResetSynchronizer + # in that case, as is the default. + return super().create_missing_domain(name) + 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 m = Module() - ready = Signal() - if self.family == "6": - m.submodules += Instance("STARTUP_SPARTAN6", o_EOS=ready) - else: - raise NotImplementedError("Spartan 3 devices lack an end-of-startup signal; " - "ensure the design has an appropriate reset") + eos = Signal() + m.submodules += Instance("STARTUP_SPARTAN6", o_EOS=eos) m.domains += ClockDomain("sync", reset_less=self.default_rst is None) - m.submodules += Instance("BUFGCE", i_CE=ready, i_I=clk_i, o_O=ClockSignal("sync")) + m.submodules += Instance("BUFGCE", i_CE=eos, i_I=clk_i, o_O=ClockSignal("sync")) if self.default_rst is not None: - m.d.comb += ResetSignal("sync").eq(rst_i) + m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") return m def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False): -- 2.30.2