vendor.intel: add Quartus support.
authorwhitequark <cz@m-labs.hk>
Wed, 21 Aug 2019 22:14:33 +0000 (22:14 +0000)
committerwhitequark <cz@m-labs.hk>
Thu, 10 Oct 2019 00:35:13 +0000 (00:35 +0000)
Co-authored-by: Dan Ravensloft <dan.ravensloft@gmail.com>
nmigen/back/verilog.py
nmigen/build/plat.py
nmigen/vendor/intel.py [new file with mode: 0644]

index 118d0adf2f09db0294b86cb5c385041d7440d196..1e97dd45c991510594062e6d2d973553d749bddc 100644 (file)
@@ -21,7 +21,7 @@ def _yosys_version():
     return tuple(map(int, tag.split("."))), offset
 
 
-def _convert_rtlil_text(rtlil_text, *, strip_internal_attrs=False):
+def _convert_rtlil_text(rtlil_text, *, strip_internal_attrs=False, write_verilog_opts=()):
     version, offset = _yosys_version()
     if version < (0, 9):
         raise YosysError("Yosys %d.%d is not suppored", *version)
@@ -47,10 +47,11 @@ proc_clean
 memory_collect
 attrmap {attr_map}
 attrmap -modattr {attr_map}
-write_verilog -norename
+write_verilog -norename {write_verilog_opts}
 """.format(rtlil_text,
         prune="# " if version == (0, 9) and offset == 0 else "",
         attr_map=" ".join(attr_map),
+        write_verilog_opts=" ".join(write_verilog_opts),
     )
 
     popen = subprocess.Popen([require_tool("yosys"), "-q", "-"],
index 2b01cabe561bd45b3552bfcaec788331edcffabc..a006c7e603ed91ba3d7d1c412d241945967c855f 100644 (file)
@@ -278,11 +278,13 @@ class TemplatedPlatform(Platform):
         def emit_rtlil():
             return rtlil_text
 
-        def emit_verilog():
-            return verilog._convert_rtlil_text(rtlil_text, strip_internal_attrs=True)
+        def emit_verilog(opts=()):
+            return verilog._convert_rtlil_text(rtlil_text,
+                strip_internal_attrs=True, write_verilog_opts=opts)
 
-        def emit_debug_verilog():
-            return verilog._convert_rtlil_text(rtlil_text, strip_internal_attrs=False)
+        def emit_debug_verilog(opts=()):
+            return verilog._convert_rtlil_text(rtlil_text,
+                strip_internal_attrs=False, write_verilog_opts=opts)
 
         def emit_commands(format):
             commands = []
diff --git a/nmigen/vendor/intel.py b/nmigen/vendor/intel.py
new file mode 100644 (file)
index 0000000..07943ec
--- /dev/null
@@ -0,0 +1,393 @@
+from abc import abstractproperty
+
+from ..hdl import *
+from ..build import *
+
+
+__all__ = ["IntelPlatform"]
+
+
+class IntelPlatform(TemplatedPlatform):
+    """
+    Required tools:
+        * ``quartus_map``
+        * ``quartus_fit``
+        * ``quartus_asm``
+        * ``quartus_sta``
+
+    The environment is populated by running the script specified in the environment variable
+    ``NMIGEN_ENV_Quartus``, if present.
+
+    Available overrides:
+        * ``nproc``: sets the number of cores used by all tools.
+        * ``quartus_map_opts``: adds extra options for ``quartus_map``.
+        * ``quartus_fit_opts``: adds extra options for ``quartus_fit``.
+        * ``quartus_asm_opts``: adds extra options for ``quartus_asm``.
+        * ``quartus_sta_opts``: adds extra options for ``quartus_sta``.
+
+    Build products:
+        * ``*.rpt``: toolchain reports.
+        * ``{{name}}.sof``: bitstream as SRAM object file.
+        * ``{{name}}.rbf``: bitstream as raw binary file.
+    """
+
+    toolchain = "Quartus"
+
+    device  = abstractproperty()
+    package = abstractproperty()
+    speed   = abstractproperty()
+    suffix  = ""
+
+    required_tools = [
+        "quartus_map",
+        "quartus_fit",
+        "quartus_asm",
+        "quartus_sta",
+    ]
+
+    file_templates = {
+        **TemplatedPlatform.build_script_templates,
+        "build_{{name}}.sh": r"""
+            # {{autogenerated}}
+            if [ -n "${{platform._toolchain_env_var}}" ]; then
+                QUARTUS_ROOTDIR=$(dirname $(dirname "${{platform._toolchain_env_var}}"))
+                # Quartus' qenv.sh does not work with `set -e`.
+                . "${{platform._toolchain_env_var}}"
+            fi
+            set -e{{verbose("x")}}
+            {{emit_commands("sh")}}
+        """,
+        # Quartus doesn't like constructs like (* keep = 32'd1 *), even though they mean the same
+        # thing as (* keep = 1 *); use -decimal to work around that.
+        "{{name}}.v": r"""
+            /* {{autogenerated}} */
+            {{emit_verilog(["-decimal"])}}
+        """,
+        "{{name}}.debug.v": r"""
+            /* {{autogenerated}} */
+            {{emit_debug_verilog(["-decimal"])}}
+        """,
+        "{{name}}.qsf": r"""
+            # {{autogenerated}}
+            {% if get_override("nproc") -%}
+                set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}}
+            {% endif %}
+
+            {% for file in platform.iter_extra_files(".v") -%}
+                set_global_assignment -name VERILOG_FILE "{{file}}"
+            {% endfor %}
+            {% for file in platform.iter_extra_files(".sv") -%}
+                set_global_assignment -name SYSTEMVERILOG_FILE "{{file}}"
+            {% endfor %}
+            {% for file in platform.iter_extra_files(".vhd", ".vhdl") -%}
+                set_global_assignment -name VHDL_FILE "{{file}}"
+            {% endfor %}
+            set_global_assignment -name VERILOG_FILE {{name}}.v
+            set_global_assignment -name TOP_LEVEL_ENTITY {{name}}
+
+            set_global_assignment -name DEVICE {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
+            {% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%}
+                set_location_assignment -to "{{port_name}}" PIN_{{pin_name}}
+                {% for key, value in extras.items() -%}
+                    set_instance_assignment -to "{{port_name}}" -name {{key}} "{{value}}"
+                {% endfor %}
+            {% endfor %}
+
+            set_global_assignment -name GENERATE_RBF_FILE ON
+        """,
+        "{{name}}.sdc": r"""
+            {% for signal, frequency in platform.iter_clock_constraints() -%}
+                create_clock -period {{1000000000/frequency}} [get_nets {{signal|hierarchy("|")}}]
+            {% endfor %}
+        """,
+    }
+    command_templates = [
+        r"""
+        {{get_tool("quartus_map")}}
+            {{get_override("quartus_map_opts")|options}}
+            --rev={{name}} {{name}}
+        """,
+        r"""
+        {{get_tool("quartus_fit")}}
+            {{get_override("quartus_fit_opts")|options}}
+            --rev={{name}} {{name}}
+        """,
+        r"""
+        {{get_tool("quartus_asm")}}
+            {{get_override("quartus_asm_opts")|options}}
+            --rev={{name}} {{name}}
+        """,
+        r"""
+        {{get_tool("quartus_sta")}}
+            {{get_override("quartus_sta_opts")|options}}
+            --rev={{name}} {{name}}
+        """,
+    ]
+
+    def add_clock_constraint(self, clock, frequency):
+        super().add_clock_constraint(clock, frequency)
+        # Make sure the net constrained in the SDC file is kept through synthesis; it is redundant
+        # after Quartus flattens the hierarchy and will be eliminated if not explicitly kept.
+        clock.attrs["keep"] = 1
+
+    # The altiobuf_* and altddio_* primitives are explained in the following Intel documents:
+    # * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altiobuf.pdf
+    # * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altddio.pdf
+    # See also errata mentioned in: https://www.intel.com/content/www/us/en/programmable/support/support-resources/knowledge-base/solutions/rd11192012_735.html.
+
+    @staticmethod
+    def _get_ireg(m, pin, invert):
+        def get_ineg(i):
+            if invert:
+                i_neg = Signal.like(i, name_suffix="_neg")
+                m.d.comb += i.eq(~i_neg)
+                return i_neg
+            else:
+                return i
+
+        if pin.xdr == 0:
+            return get_ineg(pin.i)
+        elif pin.xdr == 1:
+            i_sdr = Signal(pin.width, name="{}_i_sdr")
+            m.submodules += Instance("$dff",
+                p_CLK_POLARITY=1,
+                p_WIDTH=pin.width,
+                i_CLK=pin.i_clk,
+                i_D=i_sdr,
+                o_Q=get_ineg(pin.i),
+            )
+            return i_sdr
+        elif pin.xdr == 2:
+            i_ddr = Signal(pin.width, name="{}_i_ddr".format(pin.name))
+            m.submodules["{}_i_ddr".format(pin.name)] = Instance("altddio_in",
+                p_width=pin.width,
+                i_datain=i_ddr,
+                i_inclock=pin.i_clk,
+                o_dataout_h=get_ineg(pin.i0),
+                o_dataout_l=get_ineg(pin.i1),
+            )
+            return i_ddr
+        assert False
+
+    @staticmethod
+    def _get_oreg(m, pin, invert):
+        def get_oneg(o):
+            if invert:
+                o_neg = Signal.like(o, name_suffix="_neg")
+                m.d.comb += o_neg.eq(~o)
+                return o_neg
+            else:
+                return o
+
+        if pin.xdr == 0:
+            return get_oneg(pin.o)
+        elif pin.xdr == 1:
+            o_sdr = Signal(pin.width, name="{}_o_sdr".format(pin.name))
+            m.submodules += Instance("$dff",
+                p_CLK_POLARITY=1,
+                p_WIDTH=pin.width,
+                i_CLK=pin.o_clk,
+                i_D=get_oneg(pin.o),
+                o_Q=o_sdr,
+            )
+            return o_sdr
+        elif pin.xdr == 2:
+            o_ddr = Signal(pin.width, name="{}_o_ddr".format(pin.name))
+            m.submodules["{}_o_ddr".format(pin.name)] = Instance("altddio_out",
+                p_width=pin.width,
+                o_dataout=o_ddr,
+                i_outclock=pin.o_clk,
+                i_datain_h=get_oneg(pin.o0),
+                i_datain_l=get_oneg(pin.o1),
+            )
+            return o_ddr
+        assert False
+
+    @staticmethod
+    def _get_oereg(m, pin):
+        if pin.xdr == 0:
+            return pin.oe
+        elif pin.xdr in (1, 2):
+            oe_reg = Signal(pin.width, name="{}_oe_reg".format(pin.name))
+            oe_reg.attrs["useioff"] = "1"
+            m.submodules += Instance("$dff",
+                p_CLK_POLARITY=1,
+                p_WIDTH=pin.width,
+                i_CLK=pin.o_clk,
+                i_D=pin.oe,
+                o_Q=oe_reg,
+            )
+            return oe_reg
+        assert False
+
+    def get_input(self, pin, port, attrs, invert):
+        self._check_feature("single-ended input", pin, attrs,
+                            valid_xdrs=(0, 1, 2), valid_attrs=True)
+        if pin.xdr == 1:
+            port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_in",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="FALSE",
+            i_datain=port,
+            o_dataout=self._get_ireg(m, pin, invert)
+        )
+        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)
+        if pin.xdr == 1:
+            port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_out",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="FALSE",
+            p_use_oe="FALSE",
+            i_datain=self._get_oreg(m, pin, invert),
+            o_dataout=port,
+        )
+        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)
+        if pin.xdr == 1:
+            port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_out",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="FALSE",
+            p_use_oe="TRUE",
+            i_datain=self._get_oreg(m, pin, invert),
+            o_dataout=port,
+            i_oe=pin.oe,
+        )
+        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)
+        if pin.xdr == 1:
+            port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_bidir",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="FALSE",
+            i_datain=self._get_oreg(m, pin, invert),
+            io_dataio=port,
+            o_dataout=self._get_ireg(m, pin, invert),
+            i_oe=self._get_oereg(m, pin),
+        )
+        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)
+        if pin.xdr == 1:
+            p_port.attrs["useioff"] = 1
+            n_port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_in",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="TRUE",
+            i_datain=p_port,
+            i_datain_b=n_port,
+            o_dataout=self._get_ireg(m, pin, invert)
+        )
+        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)
+        if pin.xdr == 1:
+            p_port.attrs["useioff"] = 1
+            n_port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_out",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="TRUE",
+            p_use_oe="FALSE",
+            i_datain=self._get_oreg(m, pin, invert),
+            o_dataout=p_port,
+            o_dataout_b=n_port,
+        )
+        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)
+        if pin.xdr == 1:
+            p_port.attrs["useioff"] = 1
+            n_port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_out",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="TRUE",
+            p_use_oe="TRUE",
+            i_datain=self._get_oreg(m, pin, invert),
+            o_dataout=p_port,
+            o_dataout_b=n_port,
+            i_oe=self._get_oereg(m, pin),
+        )
+        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)
+        if pin.xdr == 1:
+            p_port.attrs["useioff"] = 1
+            n_port.attrs["useioff"] = 1
+
+        m = Module()
+        m.submodules[pin.name] = Instance("altiobuf_bidir",
+            p_enable_bus_hold="FALSE",
+            p_number_of_channels=pin.width,
+            p_use_differential_mode="TRUE",
+            i_datain=self._get_oreg(m, pin, invert),
+            io_dataio=p_port,
+            io_dataio_b=n_port,
+            o_dataout=self._get_ireg(m, pin, invert),
+            i_oe=self._get_oereg(m, pin),
+        )
+        return m
+
+    # The altera_std_synchronizer{,_bundle} megafunctions embed SDC constraints that mark false
+    # paths, so use them instead of our default implementation.
+
+    def get_ff_sync(self, ff_sync):
+        return Instance("altera_std_synchronizer_bundle",
+            p_width=len(ff_sync.i),
+            p_depth=ff_sync._stages,
+            i_clk=ClockSignal(ff_sync._o_domain),
+            i_reset_n=Const(1),
+            i_din=ff_sync.i,
+            o_dout=ff_sync.o,
+        )
+
+    def get_reset_sync(self, reset_sync):
+        m = Module()
+        rst_n = Signal()
+        m.submodules += Instance("altera_std_synchronizer",
+            p_depth=reset_sync._stages,
+            i_clk=ClockSignal(reset_sync._domain),
+            i_reset_n=~reset_sync.arst,
+            i_din=Const(1),
+            o_dout=rst_n,
+        )
+        m.d.comb += ResetSignal(reset_sync._domain).eq(~rst_n)
+        return m