build.plat,vendor: automatically create sync domain from default_clk.
authorwhitequark <whitequark@whitequark.org>
Sat, 3 Aug 2019 18:36:58 +0000 (18:36 +0000)
committerwhitequark <whitequark@whitequark.org>
Sat, 3 Aug 2019 18:36:58 +0000 (18:36 +0000)
But only if it is not defined by the programmer.

Closes #57.

nmigen/build/plat.py
nmigen/vendor/lattice_ecp5.py
nmigen/vendor/lattice_ice40.py
nmigen/vendor/xilinx_7series.py
nmigen/vendor/xilinx_spartan_3_6.py

index f11dce63e0346f3629b047b34a7fc901d749f45a..dc917e7cb68e7c6bb63141fd8aa6cfefb979f153 100644 (file)
@@ -7,6 +7,7 @@ import jinja2
 
 from .. import __version__
 from ..hdl.ast import *
+from ..hdl.cd import *
 from ..hdl.dsl import *
 from ..hdl.ir import *
 from ..back import rtlil, verilog
@@ -71,11 +72,27 @@ class Platform(ResourceManager, metaclass=ABCMeta):
 
         self.toolchain_program(products, name, **(program_opts or {}))
 
+    @abstractmethod
+    def create_missing_domain(self, 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()
+            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)
+            return m
+
     def prepare(self, fragment, name="top", **kwargs):
         assert not self._prepared
         self._prepared = True
 
         fragment = Fragment.get(fragment, self)
+        fragment = fragment.prepare(ports=list(self.iter_ports()),
+                                    missing_domain=self.create_missing_domain)
 
         def add_pin_fragment(pin, pin_fragment):
             pin_fragment = Fragment.get(pin_fragment, self)
@@ -226,9 +243,8 @@ class TemplatedPlatform(Platform):
         autogenerated = "Automatically generated by nMigen {}. Do not edit.".format(__version__)
 
         def emit_design(backend):
-            return {"rtlil": rtlil, "verilog": verilog}[backend].convert(
-                fragment, name=name, platform=self, ports=list(self.iter_ports()),
-                missing_domain=lambda name: None)
+            return {"rtlil": rtlil, "verilog": verilog}[backend].convert(fragment, name=name,
+                ports=list(self.iter_ports()), missing_domain=lambda name: None)
 
         def emit_commands(format):
             commands = []
index b90e50efbebe30d9505c8f8b78455796636e28e5..d22a583ae0295430235fda32cd1530775c147dbb 100644 (file)
@@ -127,6 +127,10 @@ class LatticeECP5Platform(TemplatedPlatform):
         """
     ]
 
+    def create_missing_domain(self, name):
+        # No additional reset logic needed.
+        return super().create_missing_domain(name)
+
     _single_ended_io_types = [
         "HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33",
         "SSTL135_I", "SSTL135_II", "SSTL15_I", "SSTL15_II", "SSTL18_I", "SSTL18_II",
index ee6f5ce1bef0b068a799f5164a2a46faad14f5ff..74fc36f436d3f32e2ce9f2718b1ed8d51e26dacc 100644 (file)
@@ -119,6 +119,46 @@ class LatticeICE40Platform(TemplatedPlatform):
         """
     ]
 
+    def create_missing_domain(self, name):
+        # For unknown reasons (no errata was ever published, and no documentation mentions this
+        # issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal
+        # global reset. Note that this is a *time-based* delay, generated purely by the internal
+        # oscillator, which may not be observed nor influenced directly. For details, see links:
+        #  * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411
+        #  * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673
+        #
+        # To handle this, it is necessary to have a global reset in any iCE40 design that may
+        # potentially instantiate BRAMs, and assert this reset for >3 us after configuration.
+        # (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated
+        # reset line, this line is ORed with the power on reset.
+        #
+        # The power-on reset timer counts up because the vendor tools do not support initialization
+        # of flip-flops.
+        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()
+            # Power-on-reset domain
+            m.domains += ClockDomain("ice40_por", reset_less=True)
+            delay = int(15e-6 * self.default_clk_frequency)
+            timer = Signal(max=delay)
+            ready = Signal()
+            m.d.comb += ClockSignal("ice40_por").eq(clk_i)
+            with m.If(timer == delay):
+                m.d.ice40_por += ready.eq(1)
+            with m.Else():
+                m.d.ice40_por += timer.eq(timer + 1)
+            # Primary domain
+            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)
+            else:
+                m.d.comb += ResetSignal("sync").eq(~ready)
+            return m
+
     def should_skip_port_component(self, port, attrs, component):
         # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
         # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
index 2fbcec5279b9db88f27651b078e4c3b48d740873..34452ef68de40c756b870ecea24668c7b1382eae 100644 (file)
@@ -122,6 +122,10 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
         """
     ]
 
+    def create_missing_domain(self, name):
+        # No additional reset logic needed.
+        csuper().create_missing_domain(name)
+
     def _get_xdr_buffer(self, m, pin, i_invert=None, o_invert=None):
         def get_dff(clk, d, q):
             # SDR I/O is performed by packing a flip-flop into the pad IOB.
index 754b2a97e0196eaacb238e29ec4e5bde050bb716..aea391d041daff1a3062cdc51f75c07d45d8daf3 100644 (file)
@@ -59,6 +59,23 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
     package = abstractproperty()
     speed   = abstractproperty()
 
+    @property
+    def family(self):
+        device = self.device.upper()
+        if device.startswith("XC3S"):
+            if device.endswith("A"):
+                return "3A"
+            elif device.endswith("E"):
+                raise NotImplementedError("""Spartan 3E family is not supported
+                                           as a nMigen platform.""")
+            else:
+                raise NotImplementedError("""Spartan 3 family is not supported
+                                           as a nMigen platform.""")
+        elif device.startswith("XC6S"):
+            return "6"
+        else:
+            assert False
+
     file_templates = {
         **TemplatedPlatform.build_script_templates,
         "{{name}}.v": r"""
@@ -142,22 +159,9 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
         """
     ]
 
-    @property
-    def family(self):
-        device = self.device.upper()
-        if device.startswith("XC3S"):
-            if device.endswith("A"):
-                return "3A"
-            elif device.endswith("E"):
-                raise NotImplementedError("""Spartan 3E family is not supported
-                                           as a nMigen platform.""")
-            else:
-                raise NotImplementedError("""Spartan 3 family is not supported
-                                           as a nMigen platform.""")
-        elif device.startswith("XC6S"):
-            return "6"
-        else:
-            assert False
+    def create_missing_domain(self, name):
+        # No additional reset logic needed.
+        return super().create_missing_domain(name)
 
     def _get_xdr_buffer(self, m, pin, i_invert=None, o_invert=None):
         def get_dff(clk, d, q):