build.plat,vendor: always synchronize reset in default sync domain.
authorwhitequark <cz@m-labs.hk>
Wed, 9 Oct 2019 20:02:33 +0000 (20:02 +0000)
committerwhitequark <cz@m-labs.hk>
Wed, 9 Oct 2019 20:02:33 +0000 (20:02 +0000)
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
nmigen/vendor/lattice_ice40.py
nmigen/vendor/xilinx_7series.py
nmigen/vendor/xilinx_spartan_3_6.py

index 0a2d28e0c6010cf1347867f84bf2d0c00e1e8da1..acad494d46e3684d1ddf089466a622b65bf43730 100644 (file)
@@ -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):
index cdd488f0c9b494c304b39854bdf0d099c2062718..cc395c081bb353ebb09e48a4687de4dfc9d3153f 100644 (file)
@@ -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
index 0ff197247a1c2cb5d4b17b1a7b69d64031f9180f..0d64e3e939d374e6a7636f3e3e46c0e22655f157 100644 (file)
@@ -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):
index 6945542c5f27f87b8823a862eb43cf024a13e28e..65f4a0a5ad3be36b5e628cedf6236547f962e830 100644 (file)
@@ -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):