vendor.lattice_ecp5: implement.
authorwhitequark <cz@m-labs.hk>
Tue, 25 Jun 2019 15:47:53 +0000 (15:47 +0000)
committerwhitequark <cz@m-labs.hk>
Tue, 25 Jun 2019 15:48:07 +0000 (15:48 +0000)
Note that because of issues with Yosys and nextpnr, it is not yet
possible to use either SDR or DDR I/O.

nmigen/vendor/lattice_ecp5.py [new file with mode: 0644]

diff --git a/nmigen/vendor/lattice_ecp5.py b/nmigen/vendor/lattice_ecp5.py
new file mode 100644 (file)
index 0000000..d34dbca
--- /dev/null
@@ -0,0 +1,361 @@
+from abc import abstractproperty
+
+from ..hdl import *
+from ..build import *
+
+
+__all__ = ["LatticeECP5Platform"]
+
+
+class LatticeECP5Platform(TemplatedPlatform):
+    """
+    Required tools:
+        * ``yosys``
+        * ``nextpnr-ecp5``
+        * ``ecppack``
+
+    Available overrides:
+        * ``verbose``: enables logging of informational messages to standard error.
+        * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
+        * ``synth_opts``: adds options for ``synth_ecp5`` Yosys command.
+        * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
+        * ``script_after_synth``: inserts commands after ``synth_ecp5`` in Yosys script.
+        * ``yosys_opts``: adds extra options for Yosys.
+        * ``nextpnr_opts``: adds extra options for nextpnr.
+        * ``ecppack_opts``: adds extra options for ecppack.
+
+    Build products:
+        * ``{{name}}.rpt``: Yosys log.
+        * ``{{name}}.json``: synthesized RTL.
+        * ``{{name}}.tim``: nextpnr log.
+        * ``{{name}}.config``: ASCII bitstream.
+        * ``{{name}}.bit``: binary bitstream.
+        * ``{{name}}.svf``: JTAG programming vector.
+    """
+
+    device  = abstractproperty()
+    package = abstractproperty()
+    speed   = abstractproperty()
+
+    _nextpnr_device_options = {
+        "LFE5U-12F":    "--25k",
+        "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",
+    }
+    _nextpnr_package_options = {
+        "BG256": "caBGA256",
+        "MG285": "csfBGA285",
+        "BG381": "caBGA381",
+        "BG554": "caBGA554",
+        "BG756": "caBGA756",
+    }
+
+    file_templates = {
+        **TemplatedPlatform.build_script_templates,
+        "{{name}}.il": r"""
+            # {{autogenerated}}
+            {{emit_design("rtlil")}}
+        """,
+        "{{name}}.ys": r"""
+            # {{autogenerated}}
+            {% for file in platform.extra_files %}
+                {% if file.endswith(".v") -%}
+                    read_verilog {{get_override("read_opts")|join(" ")}} {{file}}
+                {% elif file.endswith(".sv") -%}
+                    read_verilog -sv {{get_override("read_opts")|join(" ")}} {{file}}
+                {% endif %}
+            {% endfor %}
+            read_ilang {{name}}.il
+            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
+            synth_ecp5 {{get_override("synth_opts")|join(" ")}} -top {{name}}
+            {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
+            write_json {{name}}.json
+        """,
+        "{{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 %}
+            {% for signal, frequency in platform.iter_clock_constraints() -%}
+                FREQUENCY PORT "{{signal.name}}" {{frequency}} HZ;
+            {% endfor %}
+        """
+    }
+    command_templates = [
+        r"""
+        {{get_tool("yosys")}}
+            {{quiet("-q")}}
+            {{get_override("yosys_opts")|join(" ")}}
+            -l {{name}}.rpt
+            {{name}}.ys
+        """,
+        r"""
+        {{get_tool("nextpnr-ecp5")}}
+            {{quiet("--quiet")}}
+            {{get_override("nextpnr_opts")|join(" ")}}
+            --log {{name}}.tim
+            {{platform._nextpnr_device_options[platform.device]}}
+            --package {{platform._nextpnr_package_options[platform.package]|upper}}
+            --speed {{platform.speed}}
+            --json {{name}}.json
+            --lpf {{name}}.lpf
+            --textcfg {{name}}.config
+        """,
+        r"""
+        {{get_tool("ecppack")}}
+            {{verbose("--verbose")}}
+            --input {{name}}.config
+            --bit {{name}}.bit
+            --svf {{name}}.svf
+        """
+    ]
+
+    _single_ended_io_types = [
+        "HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33",
+        "SSTL135_I", "SSTL135_II", "SSTL15_I", "SSTL15_II", "SSTL18_I", "SSTL18_II",
+    ]
+    _differential_io_types = [
+        "BLVDS25", "BLVDS25E", "HSUL12D", "LVCMOS18D", "LVCMOS25D", "LVCMOS33D",
+        "LVDS", "LVDS25E", "LVPECL33", "LVPECL33E", "LVTTL33D", "MLVDS", "MLVDS25E",
+        "SLVS", "SSTL135D_II", "SSTL15D_II", "SSTL18D_II", "SUBLVDS",
+    ]
+
+    def should_skip_port_component(self, port, attrs, component):
+        # On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at
+        # the PIOA or PIOC location, which is always the non-inverting pin.
+        if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n":
+            return True
+        return False
+
+    def _get_xdr_buffer(self, m, pin, i_invert=None, o_invert=None):
+        def get_ireg(clk, d, q):
+            for bit in range(len(q)):
+                m.submodules += Instance("IFS1P3DX",
+                    i_SCLK=clk,
+                    i_SP=Const(1),
+                    i_CD=Const(0),
+                    i_D=d[bit],
+                    o_Q=q[bit]
+                )
+
+        def get_oreg(clk, d, q):
+            for bit in range(len(q)):
+                m.submodules += Instance("OFS1P3DX",
+                    i_SCLK=clk,
+                    i_SP=Const(1),
+                    i_CD=Const(0),
+                    i_D=d[bit],
+                    o_Q=q[bit]
+                )
+
+        def get_iddr(sclk, d, q0, q1):
+            for bit in range(len(d)):
+                m.submodules += Instance("IDDRX1F",
+                    i_SCLK=sclk,
+                    i_RST=Const(0),
+                    i_D=d[bit],
+                    o_Q0=q0[bit], o_Q1=q1[bit]
+                )
+
+        def get_oddr(sclk, d0, d1, q):
+            for bit in range(len(q)):
+                m.submodules += Instance("ODDRX1F",
+                    i_SCLK=sclk,
+                    i_RST=Const(0),
+                    i_D0=d0[bit], i_D1=d1[bit],
+                    o_Q=q[bit]
+                )
+
+        def get_ixor(z, invert):
+            if invert is None:
+                return z
+            else:
+                a = Signal.like(z, name_suffix="_x{}".format(1 if invert else 0))
+                for bit in range(len(z)):
+                    m.submodules += Instance("LUT4",
+                        p_INIT=0x5555 if invert else 0xaaaa,
+                        i_A=a[bit],
+                        o_Z=z[bit]
+                    )
+                return a
+
+        def get_oxor(a, invert):
+            if invert is None:
+                return a
+            else:
+                z = Signal.like(a, name_suffix="_x{}".format(1 if invert else 0))
+                for bit in range(len(a)):
+                    m.submodules += Instance("LUT4",
+                        p_INIT=0x5555 if invert else 0xaaaa,
+                        i_A=a[bit],
+                        o_Z=z[bit]
+                    )
+                return z
+
+        if "i" in pin.dir:
+            if pin.xdr < 2:
+                pin_i  = get_ixor(pin.i,  i_invert)
+            elif pin.xdr == 2:
+                pin_i0 = get_ixor(pin.i0, i_invert)
+                pin_i1 = get_ixor(pin.i1, i_invert)
+        if "o" in pin.dir:
+            if pin.xdr < 2:
+                pin_o  = get_oxor(pin.o,  o_invert)
+            elif pin.xdr == 2:
+                pin_o0 = get_oxor(pin.o0, o_invert)
+                pin_o1 = get_oxor(pin.o1, o_invert)
+
+        i = o = t = None
+        if "i" in pin.dir:
+            i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
+        if "o" in pin.dir:
+            o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
+        if pin.dir in ("oe", "io"):
+            t = Signal(1,         name="{}_xdr_t".format(pin.name))
+
+        if pin.xdr == 0:
+            if "i" in pin.dir:
+                i = pin_i
+            if "o" in pin.dir:
+                o = pin_o
+            if pin.dir in ("oe", "io"):
+                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:
+                get_ireg(pin.i_clk, i, pin_i)
+            if "o" in pin.dir:
+                get_oreg(pin.o_clk, pin_o, o)
+            if pin.dir in ("oe", "io"):
+                get_oreg(pin.o_clk, ~pin.oe, t)
+        elif pin.xdr == 2:
+            if "i" in pin.dir:
+                get_iddr(pin.i_clk, i, pin_i0, pin_i1)
+            if "o" in pin.dir:
+                get_oddr(pin.o_clk, pin_o0, pin_o1, o)
+            if pin.dir in ("oe", "io"):
+                # It looks like Diamond will not pack an OREG as a tristate register in a DDR PIO.
+                # It is not clear what is the recommended set of primitives for this task.
+                # Similarly, nextpnr will not pack anything as a tristate register in a DDR PIO.
+                get_oreg(pin.o_clk, ~pin.oe, t)
+        else:
+            assert False
+
+        return (i, o, t)
+
+    def get_input(self, pin, port, attrs, invert):
+        self._check_feature("single-ended input", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        t, o, t = self._get_xdr_buffer(m, pin, i_invert=True if invert else None)
+        for bit in range(len(port)):
+            m.submodules += Instance("IB",
+                i_I=port[bit],
+                o_O=i[bit]
+            )
+        return m
+
+    def get_output(self, pin, port, attrs, invert):
+        self._check_feature("single-ended output", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        i, o, t = self._get_xdr_buffer(m, pin, o_invert=True if invert else None)
+        for bit in range(len(port)):
+            m.submodules += Instance("OB",
+                i_I=o[bit],
+                o_O=port[bit]
+            )
+        return m
+
+    def get_tristate(self, pin, port, attrs, invert):
+        self._check_feature("single-ended tristate", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        i, o, t = self._get_xdr_buffer(m, pin, o_invert=True if invert else None)
+        for bit in range(len(port)):
+            m.submodules += Instance("OBZ",
+                i_T=t,
+                i_I=o[bit],
+                o_O=port[bit]
+            )
+        return m
+
+    def get_input_output(self, pin, port, attrs, invert):
+        self._check_feature("single-ended input/output", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        i, o, t = self._get_xdr_buffer(m, pin, i_invert=True if invert else None,
+                                               o_invert=True if invert else None)
+        for bit in range(len(port)):
+            m.submodules += Instance("BB",
+                i_T=t,
+                i_I=o[bit],
+                o_O=i[bit],
+                io_B=port[bit]
+            )
+        return m
+
+    def get_diff_input(self, pin, p_port, n_port, attrs, invert):
+        self._check_feature("differential input", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        i, o, t = self._get_xdr_buffer(m, pin, i_invert=True if invert else None)
+        for bit in range(len(p_port)):
+            m.submodules += Instance("IB",
+                i_I=p_port[bit],
+                o_O=i[bit]
+            )
+        return m
+
+    def get_diff_output(self, pin, p_port, n_port, attrs, invert):
+        self._check_feature("differential output", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        i, o, t = self._get_xdr_buffer(m, pin, o_invert=True if invert else None)
+        for bit in range(len(p_port)):
+            m.submodules += Instance("OB",
+                i_I=o[bit],
+                o_O=p_port[bit],
+            )
+        return m
+
+    def get_diff_tristate(self, pin, p_port, n_port, attrs, invert):
+        self._check_feature("differential tristate", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        i, o, t = self._get_xdr_buffer(m, pin, o_invert=True if invert else None)
+        for bit in range(len(p_port)):
+            m.submodules += Instance("OBZ",
+                i_T=t,
+                i_I=o[bit],
+                o_O=p_port[bit],
+            )
+        return m
+
+    def get_diff_input_output(self, pin, p_port, n_port, attrs, invert):
+        self._check_feature("differential input/output", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        m = Module()
+        i, o, t = self._get_xdr_buffer(m, pin, i_invert=True if invert else None,
+                                               o_invert=True if invert else None)
+        for bit in range(len(p_port)):
+            m.submodules += Instance("BB",
+                i_T=t,
+                i_I=o[bit],
+                o_O=i[bit],
+                io_B=p_port[bit],
+            )
+        return m