vendor.lattice_ice40: add iCECube support.
authorwhitequark <cz@m-labs.hk>
Sat, 21 Sep 2019 14:27:35 +0000 (14:27 +0000)
committerwhitequark <cz@m-labs.hk>
Sat, 21 Sep 2019 14:45:17 +0000 (14:45 +0000)
This also makes some iCE40 and ECP5 overrides more consistent.

nmigen/vendor/lattice_ecp5.py
nmigen/vendor/lattice_ice40.py

index f89ad63b5811d8d3fe9e45fe05af9b2809d1c93d..fecd42e289c7c67e644654cf83ee982bca51616f 100644 (file)
@@ -29,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.
@@ -51,8 +52,8 @@ class LatticeECP5Platform(TemplatedPlatform):
     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 in LPF file.
-        * ``add_constraints``: inserts commands in XDC file.
+        * ``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.
@@ -91,6 +92,11 @@ class LatticeECP5Platform(TemplatedPlatform):
         "BG756": "caBGA756",
     }
 
+    _trellis_required_tools = [
+        "yosys",
+        "nextpnr-ecp5",
+        "ecppack"
+    ]
     _trellis_file_templates = {
         **TemplatedPlatform.build_script_templates,
         "{{name}}.il": r"""
@@ -123,6 +129,7 @@ class LatticeECP5Platform(TemplatedPlatform):
             {% for signal, frequency in platform.iter_clock_constraints() -%}
                 FREQUENCY NET "{{signal|hierarchy(".")}}" {{frequency}} HZ;
             {% endfor %}
+            {{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
         """
     }
     _trellis_command_templates = [
@@ -157,6 +164,10 @@ class LatticeECP5Platform(TemplatedPlatform):
 
     # Diamond templates
 
+    _diamond_required_tools = [
+        "pnmainc",
+        "ddtcmd"
+    ]
     _diamond_file_templates = {
         **TemplatedPlatform.build_script_templates,
         "build_{{name}}.sh": r"""
@@ -243,9 +254,9 @@ class LatticeECP5Platform(TemplatedPlatform):
     @property
     def required_tools(self):
         if self.toolchain == "Trellis":
-            return ["yosys", "nextpnr-ecp5", "ecppack"]
+            return self._trellis_required_tools
         if self.toolchain == "Diamond":
-            return ["pnmainc", "ddtcmd"]
+            return self._diamond_required_tools
         assert False
 
     @property
index 26ec67b8ccdeefe1aec8e90f3129b9517c6e78d1..cc6aa663b28ef521dbe1e924617794fd225ec579 100644 (file)
@@ -9,6 +9,9 @@ __all__ = ["LatticeICE40Platform"]
 
 class LatticeICE40Platform(TemplatedPlatform):
     """
+    IceStorm toolchain
+    ------------------
+
     Required tools:
         * ``yosys``
         * ``nextpnr-ice40``
@@ -25,6 +28,8 @@ class LatticeICE40Platform(TemplatedPlatform):
         * ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script.
         * ``yosys_opts``: adds extra options for ``yosys``.
         * ``nextpnr_opts``: adds extra options for ``nextpnr-ice40``.
+        * ``add_pre_pack``: inserts commands at the end in pre-pack Python script.
+        * ``add_constraints``: inserts commands at the end in the PCF file.
 
     Build products:
         * ``{{name}}.rpt``: Yosys log.
@@ -32,18 +37,42 @@ class LatticeICE40Platform(TemplatedPlatform):
         * ``{{name}}.tim``: nextpnr log.
         * ``{{name}}.asc``: ASCII bitstream.
         * ``{{name}}.bin``: binary bitstream.
+
+    iCECube2 toolchain
+    ------------------
+
+    This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``.
+
+    Required tools:
+        * iCECube2 toolchain
+        * ``tclsh``
+
+    The environment is populated by setting the necessary environment variables based on
+    ``NMIGEN_ENV_iCECube2``, which must point to the root of the iCECube2 installation, and
+    is required.
+
+    Available overrides:
+        * ``verbose``: enables logging of informational messages to standard error.
+        * ``lse_opts``: adds options for LSE.
+        * ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script.
+        * ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script.
+        * ``add_constraints``: inserts commands in SDC file.
+        * ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT
+          Tcl script.
+
+    Build products:
+        * ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log.
+        * ``sbt/outputs/router/{{name}}_timing.rpt``: timing report.
+        * ``{{name}}.edf``: EDIF netlist.
+        * ``{{name}}.bin``: binary bitstream.
     """
 
-    toolchain = "IceStorm"
+    toolchain = None # selected when creating platform
 
     device  = abstractproperty()
     package = abstractproperty()
 
-    required_tools = [
-        "yosys",
-        "nextpnr-ice40",
-        "icepack",
-    ]
+    # IceStorm templates
 
     _nextpnr_device_options = {
         "iCE40LP384": "--lp384",
@@ -67,7 +96,12 @@ class LatticeICE40Platform(TemplatedPlatform):
         "iCE5LP1K":   "",
     }
 
-    file_templates = {
+    _icestorm_required_tools = [
+        "yosys",
+        "nextpnr-ice40",
+        "icepack",
+    ]
+    _icestorm_file_templates = {
         **TemplatedPlatform.build_script_templates,
         "{{name}}.il": r"""
             # {{autogenerated}}
@@ -87,21 +121,23 @@ class LatticeICE40Platform(TemplatedPlatform):
             {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
             write_json {{name}}.json
         """,
-        "{{name}}.pcf": r"""
-            # {{autogenerated}}
-            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
-                set_io {{port_name}} {{pin_name}}
-            {% endfor %}
-        """,
         "{{name}}_pre_pack.py": r"""
             # {{autogenerated}}
             {% for signal, frequency in platform.iter_clock_constraints() -%}
             {# Clock in MHz #}
             ctx.addClock("{{signal|hierarchy(".")}}", {{frequency/1000000}})
             {% endfor%}
+            {{get_override("add_pre_pack")|default("# (add_pre_pack placeholder)")}}
+        """,
+        "{{name}}.pcf": r"""
+            # {{autogenerated}}
+            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
+                set_io {{port_name}} {{pin_name}}
+            {% endfor %}
+            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
         """,
     }
-    command_templates = [
+    _icestorm_command_templates = [
         r"""
         {{get_tool("yosys")}}
             {{quiet("-q")}}
@@ -130,6 +166,161 @@ class LatticeICE40Platform(TemplatedPlatform):
         """
     ]
 
+    # iCECube2 templates
+
+    _icecube2_required_tools = [
+        "tclsh",
+    ]
+    _icecube2_file_templates = {
+        **TemplatedPlatform.build_script_templates,
+        "build_{{name}}.sh": r"""
+            # {{autogenerated}}
+            set -e{{verbose("x")}}
+            if [ -n "${{platform._toolchain_env_var}}" ]; then
+                # LSE environment
+                export LD_LIBRARY_PATH=${{platform._toolchain_env_var}}/LSE/bin/lin64:$LD_LIBRARY_PATH
+                export PATH=${{platform._toolchain_env_var}}/LSE/bin/lin64:$PATH
+                export FOUNDRY=${{platform._toolchain_env_var}}/LSE
+                # Synplify environment
+                export LD_LIBRARY_PATH=${{platform._toolchain_env_var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH
+                export PATH=${{platform._toolchain_env_var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH
+                export SYNPLIFY_PATH=${{platform._toolchain_env_var}}/synpbase
+                # Common environment
+                export SBT_DIR=${{platform._toolchain_env_var}}/sbt_backend
+            else
+                echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1
+            fi
+            {{emit_commands("sh")}}
+        """,
+        "{{name}}.v": r"""
+            /* {{autogenerated}} */
+            {{emit_design("verilog")}}
+        """,
+        "{{name}}_lse.prj": r"""
+            # {{autogenerated}}
+            -a SBT{{platform.family}}
+            -d {{platform.device}}
+            -t {{platform.package}}
+            {{get_override("lse_opts")|options|default("# (lse_opts placeholder)")}}
+            {% for file in platform.iter_extra_files(".v") -%}
+                -ver {{file}}
+            {% endfor %}
+            -ver {{name}}.v
+            -sdc {{name}}.sdc
+            -top {{name}}
+            -output_edif {{name}}.edf
+            -logfile {{name}}_lse.log
+        """,
+        "{{name}}_syn.prj": r"""
+            # {{autogenerated}}
+            {% for file in platform.iter_extra_files(".v", ".sv", ".vhd", ".vhdl") -%}
+                add_file -verilog {{file}}
+            {% endfor %}
+            add_file -verilog {{name}}.v
+            add_file -constraint {{name}}.sdc
+            {{get_override("script_after_add")|default("# (script_after_add placeholder)")}}
+            impl -add {{name}}_design -type fpga
+            set_option -technology SBT{{platform.family}}
+            set_option -part {{platform.device}}
+            set_option -package {{platform.package}}
+            {{get_override("script_after_options")|default("# (script_after_options placeholder)")}}
+            project -result_format edif
+            project -result_file {{name}}.edf
+            impl -active {{name}}_design
+            project -run compile
+            project -run map
+            project -run fpga_mapper
+            file copy -force -- {{name}}_design/{{name}}.edf {{name}}.edf
+        """,
+        "{{name}}.sdc": r"""
+            # {{autogenerated}}
+            {% for signal, frequency in platform.iter_clock_constraints() -%}
+                create_clock -name {{signal.name}} -period {{1000000000/frequency}} [get_nets {{signal|hierarchy("/")}}]
+            {% endfor %}
+            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
+        """,
+        "{{name}}.tcl": r"""
+            # {{autogenerated}}
+            set device {{platform.device}}-{{platform.package}}
+            set top_module {{name}}
+            set proj_dir .
+            set output_dir .
+            set edif_file {{name}}
+            set tool_options ":edifparser -y {{name}}.pcf"
+            set sbt_root $::env(SBT_DIR)
+            append sbt_tcl $sbt_root "/tcl/sbt_backend_synpl.tcl"
+            source $sbt_tcl
+            run_sbt_backend_auto $device $top_module $proj_dir $output_dir $tool_options $edif_file
+            {{get_override("script_after_file")|default("# (script_after_file placeholder)")}}
+            file copy -force -- sbt/outputs/bitmap/{{name}}_bitmap.bin {{name}}.bin
+            exit
+        """,
+        "{{name}}.pcf": r"""
+            # {{autogenerated}}
+            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
+                set_io {{port_name}} {{pin_name}}
+            {% endfor %}
+        """,
+    }
+    _lse_icecube2_command_templates = [
+        r"""synthesis -f {{name}}_lse.prj""",
+        r"""tclsh {{name}}.tcl""",
+    ]
+    _synplify_icecube2_command_templates = [
+        r"""synpwrap -prj {{name}}_syn.prj -log {{name}}_syn.log""",
+        r"""tclsh {{name}}.tcl""",
+    ]
+
+    # Common logic
+
+    def __init__(self, *, toolchain="IceStorm"):
+        super().__init__()
+
+        assert toolchain in ("IceStorm", "LSE-iCECube2", "Synplify-iCECube2")
+        self.toolchain = toolchain
+
+    @property
+    def family(self):
+        if self.device.startswith("iCE40"):
+            return "iCE40"
+        if self.device.startswith("iCE5"):
+            return "iCE5"
+        assert False
+
+    @property
+    def _toolchain_env_var(self):
+        if self.toolchain == "IceStorm":
+            return f"NMIGEN_ENV_{self.toolchain}"
+        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
+            return f"NMIGEN_ENV_iCECube2"
+        assert False
+
+    @property
+    def required_tools(self):
+        if self.toolchain == "IceStorm":
+            return self._icestorm_required_tools
+        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
+            return self._icecube2_required_tools
+        assert False
+
+    @property
+    def file_templates(self):
+        if self.toolchain == "IceStorm":
+            return self._icestorm_file_templates
+        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
+            return self._icecube2_file_templates
+        assert False
+
+    @property
+    def command_templates(self):
+        if self.toolchain == "IceStorm":
+            return self._icestorm_command_templates
+        if self.toolchain == "LSE-iCECube2":
+            return self._lse_icecube2_command_templates
+        if self.toolchain == "Synplify-iCECube2":
+            return self._synplify_icecube2_command_templates
+        assert False
+
     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
@@ -287,7 +478,7 @@ class LatticeICE40Platform(TemplatedPlatform):
                 o_type = 0b0100   # PIN_OUTPUT_DDR
             elif pin.xdr == 2:
                 o_type = 0b1100   # PIN_OUTPUT_DDR_ENABLE_REGISTERED
-            io_args.append(("p", "PIN_TYPE", (o_type << 2) | i_type))
+            io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6)))
 
             if hasattr(pin, "i_clk"):
                 io_args.append(("i", "INPUT_CLK",  pin.i_clk))