vendor: `yosys` is not a required tool for proprietary toolchains.
[nmigen.git] / nmigen / vendor / lattice_ecp5.py
index 833cd7b26ca37312d334f4ca31426eb5ed450578..c1a1366148903e3ac3d0deaf8bbf691681cecfc7 100644 (file)
@@ -9,13 +9,16 @@ __all__ = ["LatticeECP5Platform"]
 
 class LatticeECP5Platform(TemplatedPlatform):
     """
+    Trellis toolchain
+    -----------------
+
     Required tools:
         * ``yosys``
         * ``nextpnr-ecp5``
         * ``ecppack``
 
     The environment is populated by running the script specified in the environment variable
-    ``NMIGEN_Trellis_env``, if present.
+    ``NMIGEN_ENV_Trellis``, if present.
 
     Available overrides:
         * ``verbose``: enables logging of informational messages to standard error.
@@ -26,6 +29,7 @@ class LatticeECP5Platform(TemplatedPlatform):
         * ``yosys_opts``: adds extra options for ``yosys``.
         * ``nextpnr_opts``: adds extra options for ``nextpnr-ecp5``.
         * ``ecppack_opts``: adds extra options for ``ecppack``.
+        * ``add_preferences``: inserts commands at the end of the LPF file.
 
     Build products:
         * ``{{name}}.rpt``: Yosys log.
@@ -34,24 +38,46 @@ class LatticeECP5Platform(TemplatedPlatform):
         * ``{{name}}.config``: ASCII bitstream.
         * ``{{name}}.bit``: binary bitstream.
         * ``{{name}}.svf``: JTAG programming vector.
+
+    Diamond toolchain
+    -----------------
+
+    Required tools:
+        * ``pnmainc``
+        * ``ddtcmd``
+
+    The environment is populated by running the script specified in the environment variable
+    ``NMIGEN_ENV_Diamond``, if present.
+
+    Available overrides:
+        * ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
+        * ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
+        * ``add_preferences``: inserts commands at the end of the LPF file.
+        * ``add_constraints``: inserts commands at the end of the XDC file.
+
+    Build products:
+        * ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
+        * ``{{name}}.bit``: binary bitstream.
+        * ``{{name}}.svf``: JTAG programming vector.
     """
 
-    toolchain = "Trellis"
+    toolchain = None # selected when creating platform
 
     device  = abstractproperty()
     package = abstractproperty()
     speed   = abstractproperty()
+    grade   = "C" # [C]ommercial, [I]ndustrial
+
+    # Trellis templates
 
     _nextpnr_device_options = {
-        "LFE5U-12F":    "--25k",
+        "LFE5U-12F":    "--12k",
         "LFE5U-25F":    "--25k",
         "LFE5U-45F":    "--45k",
         "LFE5U-85F":    "--85k",
-        "LFE5UM-12F":   "--um-25k",
         "LFE5UM-25F":   "--um-25k",
         "LFE5UM-45F":   "--um-45k",
         "LFE5UM-85F":   "--um-85k",
-        "LFE5UM5G-12F": "--um5g-25k",
         "LFE5UM5G-25F": "--um5g-25k",
         "LFE5UM5G-45F": "--um5g-45k",
         "LFE5UM5G-85F": "--um5g-85k",
@@ -64,19 +90,31 @@ class LatticeECP5Platform(TemplatedPlatform):
         "BG756": "caBGA756",
     }
 
-    file_templates = {
+    _trellis_required_tools = [
+        "yosys",
+        "nextpnr-ecp5",
+        "ecppack"
+    ]
+    _trellis_file_templates = {
         **TemplatedPlatform.build_script_templates,
         "{{name}}.il": r"""
             # {{autogenerated}}
-            {{emit_design("rtlil")}}
+            {{emit_rtlil()}}
+        """,
+        "{{name}}.debug.v": r"""
+            /* {{autogenerated}} */
+            {{emit_debug_verilog()}}
         """,
         "{{name}}.ys": r"""
             # {{autogenerated}}
             {% for file in platform.iter_extra_files(".v") -%}
-                read_verilog {{get_override("read_opts")|options}} {{file}}
+                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
             {% endfor %}
             {% for file in platform.iter_extra_files(".sv") -%}
-                read_verilog -sv {{get_override("read_opts")|options}} {{file}}
+                read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
+            {% endfor %}
+            {% for file in platform.iter_extra_files(".il") -%}
+                read_ilang {{file}}
             {% endfor %}
             read_ilang {{name}}.il
             {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
@@ -88,26 +126,33 @@ class LatticeECP5Platform(TemplatedPlatform):
             # {{autogenerated}}
             BLOCK ASYNCPATHS;
             BLOCK RESETPATHS;
-            {% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%}
+            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                 LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
+                {% if attrs -%}
                 IOBUF PORT "{{port_name}}"
-                    {%- for key, value in extras.items() %} {{key}}={{value}}{% endfor %};
+                    {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
+                {% endif %}
             {% endfor %}
-            {% for signal, frequency in platform.iter_clock_constraints() -%}
-                FREQUENCY PORT "{{signal.name}}" {{frequency}} HZ;
+            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
+                {% if port_signal is not none -%}
+                    FREQUENCY PORT "{{port_signal.name}}" {{frequency}} HZ;
+                {% else -%}
+                    FREQUENCY NET "{{net_signal|hierarchy(".")}}" {{frequency}} HZ;
+                {% endif %}
             {% endfor %}
+            {{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
         """
     }
-    command_templates = [
+    _trellis_command_templates = [
         r"""
-        {{get_tool("yosys")}}
+        {{invoke_tool("yosys")}}
             {{quiet("-q")}}
             {{get_override("yosys_opts")|options}}
             -l {{name}}.rpt
             {{name}}.ys
         """,
         r"""
-        {{get_tool("nextpnr-ecp5")}}
+        {{invoke_tool("nextpnr-ecp5")}}
             {{quiet("--quiet")}}
             {{get_override("nextpnr_opts")|options}}
             --log {{name}}.tim
@@ -119,17 +164,183 @@ class LatticeECP5Platform(TemplatedPlatform):
             --textcfg {{name}}.config
         """,
         r"""
-        {{get_tool("ecppack")}}
+        {{invoke_tool("ecppack")}}
             {{verbose("--verbose")}}
+            {{get_override("ecppack_opts")|options}}
             --input {{name}}.config
             --bit {{name}}.bit
             --svf {{name}}.svf
         """
     ]
 
+    # Diamond templates
+
+    _diamond_required_tools = [
+        "pnmainc",
+        "ddtcmd"
+    ]
+    _diamond_file_templates = {
+        **TemplatedPlatform.build_script_templates,
+        "build_{{name}}.sh": r"""
+            # {{autogenerated}}
+            set -e{{verbose("x")}}
+            if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
+            if [ -n "${{platform._toolchain_env_var}}" ]; then
+                bindir=$(dirname "${{platform._toolchain_env_var}}")
+                . "${{platform._toolchain_env_var}}"
+            fi
+            {{emit_commands("sh")}}
+        """,
+        "{{name}}.v": r"""
+            /* {{autogenerated}} */
+            {{emit_verilog()}}
+        """,
+        "{{name}}.debug.v": r"""
+            /* {{autogenerated}} */
+            {{emit_debug_verilog()}}
+        """,
+        "{{name}}.tcl": r"""
+            prj_project new -name {{name}} -impl impl -impl_dir top_impl \
+                -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \
+                -lpf {{name}}.lpf \
+                -synthesis synplify
+            {% for file in platform.iter_extra_files(".v", ".sv", ".vhd", ".vhdl") -%}
+                prj_src add {{file|tcl_escape}}
+            {% endfor %}
+            prj_src add {{name}}.v
+            prj_impl option top {{name}}
+            prj_src add {{name}}.sdc
+            {{get_override("script_project")|default("# (script_project placeholder)")}}
+            prj_project save
+            prj_run Synthesis -impl impl -forceAll
+            prj_run Translate -impl impl -forceAll
+            prj_run Map -impl impl -forceAll
+            prj_run PAR -impl impl -forceAll
+            prj_run Export -impl impl -forceAll -task Bitgen
+            {{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
+        """,
+        "{{name}}.lpf": r"""
+            # {{autogenerated}}
+            BLOCK ASYNCPATHS;
+            BLOCK RESETPATHS;
+            {% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%}
+                LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
+                IOBUF PORT "{{port_name}}"
+                    {%- for key, value in extras.items() %} {{key}}={{value}}{% endfor %};
+            {% endfor %}
+            {{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
+        """,
+        "{{name}}.sdc": r"""
+            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
+                {% if port_signal is not none -%}
+                    create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
+                {% else -%}
+                    create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
+                {% endif %}
+            {% endfor %}
+            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
+        """,
+    }
+    _diamond_command_templates = [
+        # These don't have any usable command-line option overrides.
+        r"""
+        {{invoke_tool("pnmainc")}}
+            {{name}}.tcl
+        """,
+        r"""
+        {{invoke_tool("ddtcmd")}}
+            -oft -bit
+            -if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
+        """,
+        r"""
+        {{invoke_tool("ddtcmd")}}
+            -oft -svfsingle -revd -op "Fast Program"
+            -if {{name}}_impl/{{name}}_impl.bit -of {{name}}.svf
+        """,
+    ]
+
+    # Common logic
+
+    def __init__(self, *, toolchain="Trellis"):
+        super().__init__()
+
+        assert toolchain in ("Trellis", "Diamond")
+        self.toolchain = toolchain
+
+    @property
+    def required_tools(self):
+        if self.toolchain == "Trellis":
+            return self._trellis_required_tools
+        if self.toolchain == "Diamond":
+            return self._diamond_required_tools
+        assert False
+
+    @property
+    def file_templates(self):
+        if self.toolchain == "Trellis":
+            return self._trellis_file_templates
+        if self.toolchain == "Diamond":
+            return self._diamond_file_templates
+        assert False
+
+    @property
+    def command_templates(self):
+        if self.toolchain == "Trellis":
+            return self._trellis_command_templates
+        if self.toolchain == "Diamond":
+            return self._diamond_command_templates
+        assert False
+
+    @property
+    def default_clk_constraint(self):
+        if self.default_clk == "OSCG":
+            return Clock(310e6 / self.oscg_div)
+        return super().default_clk_constraint
+
     def create_missing_domain(self, name):
-        # No additional reset logic needed.
-        return super().create_missing_domain(name)
+        # Lattice ECP5 devices have two global set/reset signals: PUR, which is driven at startup
+        # by the configuration logic and unconditionally resets every storage element, and GSR,
+        # which is driven by user logic and each storage element may be configured as affected or
+        # unaffected by GSR. PUR is purely asynchronous, so even though it is a low-skew global
+        # network, its deassertion may violate a setup/hold constraint with relation to a user
+        # clock. To avoid this, a GSR/SGSR instance should be driven synchronized to user clock.
+        if name == "sync" and self.default_clk is not None:
+            m = Module()
+            if self.default_clk == "OSCG":
+                if not hasattr(self, "oscg_div"):
+                    raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
+                                     "and 128")
+                if not isinstance(self.oscg_div, int) or self.oscg_div < 2 or self.oscg_div > 128:
+                    raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
+                                     "and 128, not {!r}"
+                                     .format(self.oscg_div))
+                clk_i = Signal()
+                m.submodules += Instance("OSCG", p_DIV=self.oscg_div, o_OSC=clk_i)
+            else:
+                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)
+
+            gsr0 = Signal()
+            gsr1 = Signal()
+            # There is no end-of-startup signal on ECP5, but PUR is released after IOB enable, so
+            # a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
+            m.submodules += [
+                Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
+                Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=gsr0,   o_Q=gsr1),
+                # Although we already synchronize the reset input to user clock, SGSR has dedicated
+                # clock routing to the center of the FPGA; use that just in case it turns out to be
+                # more reliable. (None of this is documented.)
+                Instance("SGSR", i_CLK=clk_i, i_GSR=gsr1),
+            ]
+            # GSR implicitly connects to every appropriate storage element. As such, the sync
+            # domain is reset-less; domains driven by other clocks would need to have dedicated
+            # reset circuitry or otherwise meet setup/hold constraints on their own.
+            m.domains += ClockDomain("sync", reset_less=True)
+            m.d.comb += ClockSignal("sync").eq(clk_i)
+            return m
 
     _single_ended_io_types = [
         "HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33",
@@ -187,19 +398,19 @@ class LatticeECP5Platform(TemplatedPlatform):
                     o_Q=q[bit]
                 )
 
-        def get_ineg(y, invert):
+        def get_ineg(z, invert):
             if invert:
-                a = Signal.like(y, name_suffix="_n")
-                m.d.comb += y.eq(~a)
+                a = Signal.like(z, name_suffix="_n")
+                m.d.comb += z.eq(~a)
                 return a
             else:
-                return y
+                return z
 
         def get_oneg(a, invert):
             if invert:
-                y = Signal.like(a, name_suffix="_n")
-                m.d.comb += y.eq(~a)
-                return y
+                z = Signal.like(a, name_suffix="_n")
+                m.d.comb += z.eq(~a)
+                return z
             else:
                 return a
 
@@ -230,7 +441,7 @@ class LatticeECP5Platform(TemplatedPlatform):
             if "o" in pin.dir:
                 o = pin_o
             if pin.dir in ("oe", "io"):
-                t = ~pin_oe
+                t = ~pin.oe
         elif pin.xdr == 1:
             # Note that currently nextpnr will not pack an FF (*FS1P3DX) into the PIO.
             if "i" in pin.dir:
@@ -355,3 +566,6 @@ class LatticeECP5Platform(TemplatedPlatform):
                 io_B=p_port[bit],
             )
         return m
+
+    # CDC primitives are not currently specialized for ECP5.
+    # While Diamond supports false path constraints; nextpnr-ecp5 does not.