From: whitequark Date: Wed, 21 Aug 2019 22:14:33 +0000 (+0000) Subject: vendor.intel: add Quartus support. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=54a3fc24906a54be1f20750460cb868d8279e1e4;p=nmigen.git vendor.intel: add Quartus support. Co-authored-by: Dan Ravensloft --- diff --git a/nmigen/back/verilog.py b/nmigen/back/verilog.py index 118d0ad..1e97dd4 100644 --- a/nmigen/back/verilog.py +++ b/nmigen/back/verilog.py @@ -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", "-"], diff --git a/nmigen/build/plat.py b/nmigen/build/plat.py index 2b01cab..a006c7e 100644 --- a/nmigen/build/plat.py +++ b/nmigen/build/plat.py @@ -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 index 0000000..07943ec --- /dev/null +++ b/nmigen/vendor/intel.py @@ -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