From 796068a192a86accca0aaa0a1d2bd76c10b243b6 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 31 Aug 2023 23:25:38 +0000 Subject: [PATCH] Implement RFC 18: Reorganize vendor platforms --- amaranth/vendor/__init__.py | 44 + amaranth/vendor/_gowin.py | 603 +++++++++++ amaranth/vendor/_intel.py | 568 +++++++++++ amaranth/vendor/_lattice_ecp5.py | 665 ++++++++++++ amaranth/vendor/_lattice_ice40.py | 624 ++++++++++++ amaranth/vendor/_lattice_machxo_2_3l.py | 449 +++++++++ amaranth/vendor/_quicklogic.py | 184 ++++ amaranth/vendor/_xilinx.py | 1218 ++++++++++++++++++++++ amaranth/vendor/gowin.py | 613 +----------- amaranth/vendor/intel.py | 578 +---------- amaranth/vendor/lattice_ecp5.py | 675 +------------ amaranth/vendor/lattice_ice40.py | 634 +----------- amaranth/vendor/lattice_machxo_2_3l.py | 463 +-------- amaranth/vendor/quicklogic.py | 191 +--- amaranth/vendor/xilinx.py | 1225 +---------------------- docs/changes.rst | 10 +- docs/platform/gowin.rst | 4 +- docs/platform/intel.rst | 4 +- docs/platform/lattice-ecp5.rst | 4 +- docs/platform/lattice-ice40.rst | 4 +- docs/platform/lattice-machxo-2-3l.rst | 13 +- docs/platform/quicklogic.rst | 4 +- docs/platform/xilinx.rst | 4 +- 23 files changed, 4447 insertions(+), 4334 deletions(-) create mode 100644 amaranth/vendor/_gowin.py create mode 100644 amaranth/vendor/_intel.py create mode 100644 amaranth/vendor/_lattice_ecp5.py create mode 100644 amaranth/vendor/_lattice_ice40.py create mode 100644 amaranth/vendor/_lattice_machxo_2_3l.py create mode 100644 amaranth/vendor/_quicklogic.py create mode 100644 amaranth/vendor/_xilinx.py diff --git a/amaranth/vendor/__init__.py b/amaranth/vendor/__init__.py index e69de29..8e0c489 100644 --- a/amaranth/vendor/__init__.py +++ b/amaranth/vendor/__init__.py @@ -0,0 +1,44 @@ +# The machinery in this module is PEP 562 compliant. +# See https://peps.python.org/pep-0562/ for details. + + +# Keep this list sorted alphabetically. +__all__ = [ + "GowinPlatform", + "IntelPlatform", + "LatticeECP5Platform", + "LatticeICE40Platform", + "LatticeMachXO2Platform", + "LatticeMachXO3LPlatform", + "QuicklogicPlatform", + "XilinxPlatform", +] + + +def __dir__(): + return list({*globals(), *__all__}) + + +def __getattr__(name): + if name == "GowinPlatform": + from ._gowin import GowinPlatform + return GowinPlatform + if name == "IntelPlatform": + from ._intel import IntelPlatform + return IntelPlatform + if name == "LatticeECP5Platform": + from ._lattice_ecp5 import LatticeECP5Platform + return LatticeECP5Platform + if name == "LatticeICE40Platform": + from ._lattice_ice40 import LatticeICE40Platform + return LatticeICE40Platform + if name in ("LatticeMachXO2Platform", "LatticeMachXO3LPlatform"): + from ._lattice_machxo_2_3l import LatticeMachXO2Or3LPlatform + return LatticeMachXO2Or3LPlatform + if name == "QuicklogicPlatform": + from ._quicklogic import QuicklogicPlatform + return QuicklogicPlatform + if name == "XilinxPlatform": + from ._xilinx import XilinxPlatform + return XilinxPlatform + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/amaranth/vendor/_gowin.py b/amaranth/vendor/_gowin.py new file mode 100644 index 0000000..247664e --- /dev/null +++ b/amaranth/vendor/_gowin.py @@ -0,0 +1,603 @@ +from abc import abstractproperty +from fractions import Fraction +import re + +from ..hdl import * +from ..lib.cdc import ResetSynchronizer +from ..build import * + +# Acknowledgments: +# Parts of this file originate from https://github.com/tcjie/Gowin + + +class GowinPlatform(TemplatedPlatform): + """ + .. rubric:: Apicula toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-gowin`` + * ``gowin_pack`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_APICULA``, if present. + + Build products: + * ``{{name}}.fs``: binary bitstream. + + .. rubric:: Gowin toolchain + + Required tools: + * ``gw_sh`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_GOWIN``, if present. + + Build products: + * ``{{name}}.fs``: binary bitstream. + """ + + toolchain = None # selected when creating platform + + part = abstractproperty() + family = abstractproperty() + + def parse_part(self): + # These regular expressions match all >900 parts of Gowin device_info.csv + reg_series = r"(GW[12]{1}[AN]{1}[EFNRSZ]{0,3})-" + reg_voltage = r"(ZV|EV|LV|LX|UV|UX)" + reg_size = r"(1|2|4|9|18|55)" + reg_subseries = r"(?:(B|C|S|X|P5)?)" + reg_package = r"((?:PG|UG|EQ|LQ|MG|M|QN|CS|FN)(?:\d+)(?:P?)(?:A|E|M|CF|C|D|G|H|F|S|T|U|X)?)" + reg_speed = r"((?:C\d{1}/I\d{1})|ES|A\d{1}|I\d{1})" + + match = re.match(reg_series+reg_voltage+reg_size+reg_subseries+reg_package+reg_speed+"$", + self.part) + if not match: + raise ValueError("Supplied part name is invalid") + + self.series = match.group(1) + self.voltage = match.group(2) + self.size = match.group(3) + self.subseries = match.group(4) or "" + self.package = match.group(5) + self.speed = match.group(6) + + match = re.match(reg_series+reg_size+reg_subseries+"$", self.family) + if not match: + raise ValueError("Supplied device family name is invalid") + + self.series_f = match.group(1) + self.size_f = match.group(2) + self.subseries_f = match.group(3) or "" + + # subseries_f is usually more reliable than subseries. + + if self.series != self.series_f: + raise ValueError("Series extracted from supplied part name does not match " + "supplied family series") + if self.size != self.size_f: + raise ValueError("Size extracted from supplied part name does not match " + "supplied family size") + + # _chipdb_device is tied to available chipdb-*.bin files of nextpnr-gowin + @property + def _chipdb_device(self): + # GW1NR series does not have its own chipdb file, but works with GW1N + if self.series == "GW1NR": + return "GW1N-{}{}".format(self.size, self.subseries_f) + return self.family + + _dev_osc_mapping = { + "GW1N-1" : "OSCH", + "GW1N-1P5" : "OSCO", + "GW1N-1P5B" : "OSCO", + "GW1N-1S" : "OSCH", + "GW1N-2" : "OSCO", + "GW1N-2B" : "OSCO", + "GW1N-4" : "OSC", + "GW1N-4B" : "OSC", + "GW1N-9" : "OSC", + "GW1N-9C" : "OSC", + "GW1NR-1" : "OSCH", + "GW1NR-2" : "OSCO", + "GW1NR-2B" : "OSCO", + "GW1NR-4" : "OSC", + "GW1NR-4B" : "OSC", + "GW1NR-9" : "OSC", + "GW1NR-9C" : "OSC", + "GW1NRF-4B" : "OSC", + "GW1NS-2" : "OSCF", + "GW1NS-2C" : "OSCF", + "GW1NS-4" : "OSCZ", + "GW1NS-4C" : "OSCZ", + "GW1NSE-2C" : "OSCF", + "GW1NSER-4C" : "OSCZ", + "GW1NSR-2" : "OSCF", + "GW1NSR-2C" : "OSCF", + "GW1NSR-4" : "OSCZ", + "GW1NSR-4C" : "OSCZ", + "GW1NZ-1" : "OSCZ", + "GW1NZ-1C" : "OSCZ", + "GW2A-18" : "OSC", + "GW2A-18C" : "OSC", + "GW2A-55" : "OSC", + "GW2A-55C" : "OSC", + "GW2AN-18X" : "OSCW", + "GW2AN-55C" : "OSC", + "GW2AN-9X" : "OSCW", + "GW2ANR-18C" : "OSC", + "GW2AR-18" : "OSC", + "GW2AR-18C" : "OSC" + } + + @property + def _osc_type(self): + if self.family in self._dev_osc_mapping: + return self._dev_osc_mapping[self.family] + raise NotImplementedError("Device family {} does not have an assigned oscillator type" + .format(self.family)) + + @property + def _osc_base_freq(self): + osc = self._osc_type + if osc == "OSC": + if self.speed == 4 and self.subseries_f in ("B", "D"): + return 210_000_000 + else: + return 250_000_000 + elif osc in ("OSCZ", "OSCO"): + if self.series == "GW1NSR" and self.speed == "C7/I6": + return 260_000_000 + else: + return 250_000_000 + elif osc in ("OSCF", "OSCH"): + return 240_000_000 + elif osc == "OSCW": + return 200_000_000 + else: + assert False + + @property + def _osc_div(self): + div_min = 2 + div_max = 128 + div_step = 2 + div_frac = Fraction(self._osc_base_freq, self.osc_frequency) + + if div_frac.denominator != 1 or div_frac not in range(div_min, div_max, div_step): + raise ValueError( + "On-chip oscillator frequency (platform.osc_frequency) must be chosen such that " + "the oscillator divider, calculated as ({}/{}), is an integer between {} and {} in " + "steps of {}" + .format(div_frac.numerator, div_frac.denominator, div_min, div_max, div_step)) + + return div_frac.numerator + + # Common templates + + _common_file_templates = { + "{{name}}.cst": r""" + // {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + IO_LOC "{{port_name}}" {{pin_name}}; + {% for attr_name, attr_value in attrs.items() -%} + IO_PORT "{{port_name}}" {{attr_name}}={{attr_value}}; + {% endfor %} + {% endfor %} + """, + } + + # Apicula templates + + _apicula_required_tools = [ + "yosys", + "nextpnr-gowin", + "gowin_pack" + ] + _apicula_file_templates = { + **TemplatedPlatform.build_script_templates, + **_common_file_templates, + "{{name}}.il": r""" + # {{autogenerated}} + {{emit_rtlil()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.ys": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".v") -%} + read_verilog {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".sv") -%} + read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".il") -%} + read_ilang {{file}} + {% endfor %} + read_ilang {{name}}.il + delete w:$verilog_initial_trigger + {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} + synth_gowin {{get_override("synth_opts")|options}} -top {{name}} -json {{name}}.syn.json + {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} + """, + } + _apicula_command_templates = [ + r""" + {{invoke_tool("yosys")}} + {{quiet("-q")}} + {{get_override("yosys_opts")|options}} + -l {{name}}.rpt + {{name}}.ys + """, + r""" + {{invoke_tool("nextpnr-gowin")}} + {{quiet("--quiet")}} + {{get_override("nextpnr_opts")|options}} + --log {{name}}.tim + --device {{platform.part}} + --family {{platform._chipdb_device}} + --json {{name}}.syn.json + --cst {{name}}.cst + --write {{name}}.pnr.json + """, + r""" + {{invoke_tool("gowin_pack")}} + -d {{platform._chipdb_device}} + -o {{name}}.fs + {{get_override("gowin_pack_opts")|options}} + {{name}}.pnr.json + """ + ] + + # Vendor toolchain templates + + _gowin_required_tools = ["gw_sh"] + _gowin_file_templates = { + **TemplatedPlatform.build_script_templates, + **_common_file_templates, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.tcl": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".v",".sv",".vhd",".vhdl") -%} + add_file {{file}} + {% endfor %} + add_file -type verilog {{name}}.v + add_file -type cst {{name}}.cst + add_file -type sdc {{name}}.sdc + set_device -name {{platform.family}} {{platform.part}} + set_option -verilog_std v2001 -print_all_synthesis_warning 1 -show_all_warn 1 + {{get_override("add_options")|default("# (add_options placeholder)")}} + run all + file delete -force {{name}}.fs + file copy -force impl/pnr/project.fs {{name}}.fs + """, + "{{name}}.sdc": r""" + // {{autogenerated}} + {% for net_signal,port_signal,frequency in platform.iter_clock_constraints() -%} + create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}] + {% endfor %} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """, + } + _gowin_command_templates = [ + r""" + {{invoke_tool("gw_sh")}} + {{name}}.tcl + """ + ] + + def __init__(self, *, toolchain="Apicula"): + super().__init__() + + assert toolchain in ("Apicula", "Gowin") + self.toolchain = toolchain + + self.parse_part() + + @property + def required_tools(self): + if self.toolchain == "Apicula": + return self._apicula_required_tools + elif self.toolchain == "Gowin": + return self._gowin_required_tools + assert False + + @property + def file_templates(self): + if self.toolchain == "Apicula": + return self._apicula_file_templates + elif self.toolchain == "Gowin": + return self._gowin_file_templates + assert False + + @property + def command_templates(self): + if self.toolchain == "Apicula": + return self._apicula_command_templates + elif self.toolchain == "Gowin": + return self._gowin_command_templates + assert False + + def add_clock_constraint(self, clock, frequency): + super().add_clock_constraint(clock, frequency) + clock.attrs["keep"] = "true" + + @property + def default_clk_constraint(self): + if self.default_clk == "OSC": + if not hasattr(self, "osc_frequency"): + raise AttributeError( + "Using the on-chip oscillator as the default clock source requires " + "the platform.osc_frequency attribute to be set") + return Clock(self.osc_frequency) + + # Use the defined Clock resource. + return super().default_clk_constraint + + def create_missing_domain(self, name): + if name == "sync" and self.default_clk is not None: + m = Module() + + if self.default_clk == "OSC": + clk_i = Signal() + if self._osc_type == "OSCZ": + m.submodules += Instance(self._osc_type, + p_FREQ_DIV=self._osc_div, + i_OSCEN=Const(1), + o_OSCOUT=clk_i) + elif self._osc_type == "OSCO": + # TODO: Make use of regulator configurable + m.submodules += Instance(self._osc_type, + p_REGULATOR_EN=Const(1), + p_FREQ_DIV=self._osc_div, + i_OSCEN=Const(1), + o_OSCOUT=clk_i) + elif self._osc_type == "OSCF": + m.submodules += Instance(self._osc_type, + p_FREQ_DIV=self._osc_div, + o_OSCOUT30M=None, + o_OSCOUT=clk_i) + else: + m.submodules += Instance(self._osc_type, + p_FREQ_DIV=self._osc_div, + o_OSCOUT=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) + + m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") + m.domains += ClockDomain("sync") + m.d.comb += ClockSignal("sync").eq(clk_i) + + return m + + def _get_xdr_buffer(self, m, pin, i_invert=False, o_invert=False): + + def get_ireg(clk,d,q): + for bit in range(len(q)): + m.submodules += Instance("DFF", + i_CLK=clk, + i_D=d[bit], + o_Q=q[bit], + ) + + def get_oreg(clk,d,q): + for bit in range(len(q)): + m.submodules += Instance("DFF", + i_CLK=clk, + i_D=d[bit], + o_Q=q[bit] + ) + + def get_iddr(clk,d,q0,q1): + for bit in range(len(d)): + m.submodules += Instance("IDDR", + i_CLK=clk, + i_D=d[bit], + o_Q0=q0[bit], + o_Q1=q1[bit] + ) + + def get_oddr(clk,d0,d1,q): + for bit in range(len(q)): + m.submodules += Instance("ODDR", + p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK + i_CLK=clk, + i_D0=d0[bit], + i_D1=d1[bit], + o_Q0=q[bit] + ) + + def get_oeddr(clk,d0,d1,tx,q0,q1): + for bit in range(len(q0)): + m.submodules += Instance("ODDR", + p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK + i_CLK=clk, + i_D0=d0[bit], + i_D1=d1[bit], + i_TX=tx, + o_Q0=q0[bit], + o_Q1=q1 + ) + + def get_ineg(y, invert): + if invert: + a = Signal.like(y, name_suffix="_n") + m.d.comb += y.eq(~a) + return a + else: + return y + + def get_oneg(a, invert): + if invert: + y = Signal.like(a, name_suffix="_n") + m.d.comb += y.eq(~a) + return y + else: + return a + + + if "i" in pin.dir: + if pin.xdr < 2: + pin_i = get_ineg(pin.i, i_invert) + elif pin.xdr == 2: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + if "o" in pin.dir: + if pin.xdr < 2: + pin_o = get_oneg(pin.o, o_invert) + elif pin.xdr == 2: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(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: + 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 pin.dir in ("o",): + get_oddr(pin.o_clk, pin_o0, pin_o1, o) + if pin.dir in ("oe", "io"): + get_oeddr(pin.o_clk, pin_o0, pin_o1, ~pin.oe, o, 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() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF", + i_I=port.io[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, port.io, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF", + i_I=o[bit], + o_O=port.io[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=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("TBUF", + i_OEN=t, + i_I=o[bit], + o_O=port.io[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=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF", + i_OEN=t, + i_I=o[bit], + o_O=i[bit], + io_IO=port.io[bit] + ) + return m + + def get_diff_input(self, pin, 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=invert) + for bit in range(pin.wodth): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IBUF", + i_I=port.p[bit], + i_IB=port.n[bit], + o_O=i[bit] + ) + return m + + def get_diff_output(self, pin, 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=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_OBUF", + i_I=o[bit], + o_O=port.p[bit], + o_OB=port.n[bit], + ) + return m + + def get_diff_tristate(self, pin, 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=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_TBUF", + i_OEN=t, + i_I=o[bit], + o_O=port.p[bit], + o_OB=port.n[bit] + ) + return m + + def get_diff_input_output(self, pin, port, atttr, 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=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IOBUF", + i_OEN=t, + i_I=o[bit], + o_O=i[bit], + io_IO=port.p[bit], + io_IOB=port.n[bit] + ) + return m diff --git a/amaranth/vendor/_intel.py b/amaranth/vendor/_intel.py new file mode 100644 index 0000000..4964275 --- /dev/null +++ b/amaranth/vendor/_intel.py @@ -0,0 +1,568 @@ +from abc import abstractmethod + +from ..hdl import * +from ..build import * + + +class IntelPlatform(TemplatedPlatform): + """ + .. rubric:: Quartus toolchain + + Required tools: + * ``quartus_map`` + * ``quartus_fit`` + * ``quartus_asm`` + * ``quartus_sta`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_QUARTUS``, if present. + + Available overrides: + * ``add_settings``: inserts commands at the end of the QSF file. + * ``add_constraints``: inserts commands at the end of the SDC file. + * ``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. + + + .. rubric:: Mistral toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-mistral`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_MISTRAL``, if present. + + * ``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_intel_alm`` Yosys command. + * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script. + * ``script_after_synth``: inserts commands after ``synth_intel_alm`` in Yosys script. + * ``yosys_opts``: adds extra options for ``yosys``. + * ``nextpnr_opts``: adds extra options for ``nextpnr-mistral``. + """ + + toolchain = None # selected when creating platform + + device = property(abstractmethod(lambda: None)) + package = property(abstractmethod(lambda: None)) + speed = property(abstractmethod(lambda: None)) + suffix = "" + + # Quartus templates + + quartus_suppressed_warnings = [ + 10264, # All case item expressions in this case statement are onehot + 10270, # Incomplete Verilog case statement has no default case item + 10335, # Unrecognized synthesis attribute + 10763, # Verilog case statement has overlapping case item expressions with non-constant or don't care bits + 10935, # Verilog casex/casez overlaps with a previous casex/vasez item expression + 12125, # Using design file which is not specified as a design file for the current project, but contains definitions used in project + 18236, # Number of processors not specified in QSF + 292013, # Feature is only available with a valid subscription license + ] + + quartus_required_tools = [ + "quartus_map", + "quartus_fit", + "quartus_asm", + "quartus_sta", + ] + + quartus_file_templates = { + **TemplatedPlatform.build_script_templates, + "build_{{name}}.sh": r""" + # {{autogenerated}} + {% for var in platform._all_toolchain_env_vars %} + if [ -n "${{var}}" ]; then + QUARTUS_ROOTDIR=$(dirname $(dirname "${{var}}")) + # Quartus' qenv.sh does not work with `set -e`. + . "${{var}}" + fi + {% endfor %} + set -e{{verbose("x")}} + {{emit_commands("sh")}} + """, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.qsf": r""" + # {{autogenerated}} + {% if get_override("nproc") -%} + set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}} + {% endif %} + + {% for file in platform.iter_files(".v") -%} + set_global_assignment -name VERILOG_FILE {{file|tcl_quote}} + {% endfor %} + {% for file in platform.iter_files(".sv") -%} + set_global_assignment -name SYSTEMVERILOG_FILE {{file|tcl_quote}} + {% endfor %} + {% for file in platform.iter_files(".vhd", ".vhdl") -%} + set_global_assignment -name VHDL_FILE {{file|tcl_quote}} + {% 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, attrs in platform.iter_port_constraints_bits() -%} + set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}} + {% for key, value in attrs.items() -%} + set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}} + {% endfor %} + {% endfor %} + + set_global_assignment -name GENERATE_RBF_FILE ON + + {{get_override("add_settings")|default("# (add_settings 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_quote}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_quote}}] + {% else -%} + create_clock -name {{net_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("|")|tcl_quote}}] + {% endif %} + {% endfor %} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """, + "{{name}}.srf": r""" + {% for warning in platform.quartus_suppressed_warnings %} + { "" "" "" "{{name}}.v" { } { } 0 {{warning}} "" 0 0 "Design Software" 0 -1 0 ""} + {% endfor %} + """, + } + quartus_command_templates = [ + r""" + {{invoke_tool("quartus_map")}} + {{get_override("quartus_map_opts")|options}} + --rev={{name}} {{name}} + """, + r""" + {{invoke_tool("quartus_fit")}} + {{get_override("quartus_fit_opts")|options}} + --rev={{name}} {{name}} + """, + r""" + {{invoke_tool("quartus_asm")}} + {{get_override("quartus_asm_opts")|options}} + --rev={{name}} {{name}} + """, + r""" + {{invoke_tool("quartus_sta")}} + {{get_override("quartus_sta_opts")|options}} + --rev={{name}} {{name}} + """, + ] + + + # Mistral templates + + mistral_required_tools = [ + "yosys", + "nextpnr-mistral" + ] + mistral_file_templates = { + **TemplatedPlatform.build_script_templates, + "{{name}}.il": r""" + # {{autogenerated}} + {{emit_rtlil()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.ys": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".v") -%} + read_verilog {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".sv") -%} + read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".il") -%} + read_ilang {{file}} + {% endfor %} + read_ilang {{name}}.il + delete w:$verilog_initial_trigger + {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} + synth_intel_alm {{get_override("synth_opts")|options}} -top {{name}} + {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} + write_json {{name}}.json + """, + "{{name}}.qsf": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}} + {% for key, value in attrs.items() -%} + set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}} + {% endfor %} + {% endfor %} + """, + + } + mistral_command_templates = [ + r""" + {{invoke_tool("yosys")}} + {{quiet("-q")}} + {{get_override("yosys_opts")|options}} + -l {{name}}.rpt + {{name}}.ys + """, + r""" + {{invoke_tool("nextpnr-mistral")}} + {{quiet("--quiet")}} + {{get_override("nextpnr_opts")|options}} + --log {{name}}.tim + --device {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}} + --json {{name}}.json + --qsf {{name}}.qsf + --rbf {{name}}.rbf + """ + ] + + # Common logic + + def __init__(self, *, toolchain="Quartus"): + super().__init__() + + assert toolchain in ("Quartus", "Mistral") + self.toolchain = toolchain + + @property + def required_tools(self): + if self.toolchain == "Quartus": + return self.quartus_required_tools + if self.toolchain == "Mistral": + return self.mistral_required_tools + assert False + + @property + def file_templates(self): + if self.toolchain == "Quartus": + return self.quartus_file_templates + if self.toolchain == "Mistral": + return self.mistral_file_templates + assert False + + @property + def command_templates(self): + if self.toolchain == "Quartus": + return self.quartus_command_templates + if self.toolchain == "Mistral": + return self.mistral_command_templates + assert False + + def add_clock_constraint(self, clock, frequency): + super().add_clock_constraint(clock, frequency) + clock.attrs["keep"] = "true" + + @property + def default_clk_constraint(self): + # Internal high-speed oscillator on Cyclone V devices. + # It is specified to not be faster than 100MHz, but the actual + # frequency seems to vary a lot between devices. Measurements + # of 78 to 84 MHz have been observed. + if self.default_clk == "cyclonev_oscillator": + assert self.device.startswith("5C") + return Clock(100e6) + # Otherwise, use the defined Clock resource. + return super().default_clk_constraint + + def create_missing_domain(self, name): + if name == "sync" and self.default_clk == "cyclonev_oscillator": + # Use the internal high-speed oscillator for Cyclone V devices + assert self.device.startswith("5C") + m = Module() + m.domains += ClockDomain("sync") + m.submodules += Instance("cyclonev_oscillator", + i_oscena=Const(1), + o_clkout=ClockSignal("sync")) + return m + else: + return super().create_missing_domain(name) + + # 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): + # altiobuf_ requires an output enable signal for each pin, but pin.oe is 1 bit wide. + if pin.xdr == 0: + return pin.oe.replicate(pin.width) + 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.io, + 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.io, + ) + 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.io, + i_oe=self._get_oereg(m, pin) + ) + 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.io, + o_dataout=self._get_ireg(m, pin, invert), + i_oe=self._get_oereg(m, pin), + ) + return m + + def get_diff_input(self, pin, port, attrs, invert): + self._check_feature("differential input", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + if pin.xdr == 1: + port.p.attrs["useioff"] = 1 + port.n.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=port.p, + i_datain_b=port.n, + o_dataout=self._get_ireg(m, pin, invert) + ) + return m + + def get_diff_output(self, pin, port, attrs, invert): + self._check_feature("differential output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + if pin.xdr == 1: + port.p.attrs["useioff"] = 1 + port.n.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=port.p, + o_dataout_b=port.n, + ) + return m + + def get_diff_tristate(self, pin, port, attrs, invert): + self._check_feature("differential tristate", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + if pin.xdr == 1: + port.p.attrs["useioff"] = 1 + port.n.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=port.p, + o_dataout_b=port.n, + i_oe=self._get_oereg(m, pin), + ) + return m + + def get_diff_input_output(self, pin, port, attrs, invert): + self._check_feature("differential input/output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + if pin.xdr == 1: + port.p.attrs["useioff"] = 1 + port.n.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=port.p, + io_dataio_b=port.n, + 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_async_ff_sync(self, async_ff_sync): + m = Module() + sync_output = Signal() + if async_ff_sync._edge == "pos": + m.submodules += Instance("altera_std_synchronizer", + p_depth=async_ff_sync._stages, + i_clk=ClockSignal(async_ff_sync._o_domain), + i_reset_n=~async_ff_sync.i, + i_din=Const(1), + o_dout=sync_output, + ) + else: + m.submodules += Instance("altera_std_synchronizer", + p_depth=async_ff_sync._stages, + i_clk=ClockSignal(async_ff_sync._o_domain), + i_reset_n=async_ff_sync.i, + i_din=Const(1), + o_dout=sync_output, + ) + m.d.comb += async_ff_sync.o.eq(~sync_output) + return m diff --git a/amaranth/vendor/_lattice_ecp5.py b/amaranth/vendor/_lattice_ecp5.py new file mode 100644 index 0000000..f80f5e3 --- /dev/null +++ b/amaranth/vendor/_lattice_ecp5.py @@ -0,0 +1,665 @@ +from abc import abstractmethod + +from ..hdl import * +from ..build import * + + +class LatticeECP5Platform(TemplatedPlatform): + """ + .. rubric:: Trellis toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-ecp5`` + * ``ecppack`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_TRELLIS``, if present. + + 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-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. + * ``{{name}}.json``: synthesized RTL. + * ``{{name}}.tim``: nextpnr log. + * ``{{name}}.config``: ASCII bitstream. + * ``{{name}}.bit``: binary bitstream. + * ``{{name}}.svf``: JTAG programming vector. + + .. rubric:: Diamond toolchain + + Required tools: + * ``pnmainc`` + * ``ddtcmd`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond + itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``, + for instance) is known to work:: + + @echo off + set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH% + + 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 = None # selected when creating platform + + device = property(abstractmethod(lambda: None)) + package = property(abstractmethod(lambda: None)) + speed = property(abstractmethod(lambda: None)) + grade = "C" # [C]ommercial, [I]ndustrial + + # Trellis templates + + _nextpnr_device_options = { + "LFE5U-12F": "--12k", + "LFE5U-25F": "--25k", + "LFE5U-45F": "--45k", + "LFE5U-85F": "--85k", + "LFE5UM-25F": "--um-25k", + "LFE5UM-45F": "--um-45k", + "LFE5UM-85F": "--um-85k", + "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", + } + + _trellis_required_tools = [ + "yosys", + "nextpnr-ecp5", + "ecppack" + ] + _trellis_file_templates = { + **TemplatedPlatform.build_script_templates, + "{{name}}.il": r""" + # {{autogenerated}} + {{emit_rtlil()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.ys": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".v") -%} + read_verilog {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".sv") -%} + read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".il") -%} + read_ilang {{file}} + {% endfor %} + read_ilang {{name}}.il + delete w:$verilog_initial_trigger + {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} + synth_ecp5 {{get_override("synth_opts")|options}} -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, attrs in platform.iter_port_constraints_bits() -%} + LOCATE COMP "{{port_name}}" SITE "{{pin_name}}"; + {% if attrs -%} + IOBUF PORT "{{port_name}}" + {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %}; + {% endif %} + {% endfor %} + {% 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)")}} + """ + } + _trellis_command_templates = [ + r""" + {{invoke_tool("yosys")}} + {{quiet("-q")}} + {{get_override("yosys_opts")|options}} + -l {{name}}.rpt + {{name}}.ys + """, + r""" + {{invoke_tool("nextpnr-ecp5")}} + {{quiet("--quiet")}} + {{get_override("nextpnr_opts")|options}} + --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""" + {{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 + {% for var in platform._all_toolchain_env_vars %} + if [ -n "${{var}}" ]; then + bindir=$(dirname "${{var}}") + . "${{var}}" + fi + {% endfor %} + {{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 {{name}}_impl \ + -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \ + -lpf {{name}}.lpf \ + -synthesis synplify + {% for file in platform.iter_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 + prj_run Translate -impl impl + prj_run Map -impl impl + prj_run PAR -impl impl + prj_run Export -impl impl -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, attrs in platform.iter_port_constraints_bits() -%} + LOCATE COMP "{{port_name}}" SITE "{{pin_name}}"; + {% if attrs -%} + IOBUF PORT "{{port_name}}" + {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %}; + {% endif %} + {% 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): + # 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", + "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_I", "SSTL135D_II", "SSTL15D_I", "SSTL15D_II", "SSTL18D_I", + "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=False, o_invert=False): + 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_oereg(clk, oe, q): + for bit in range(len(q)): + m.submodules += Instance("OFS1P3DX", + i_SCLK=clk, + i_SP=Const(1), + i_CD=Const(0), + i_D=oe, + 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_iddrx2(sclk, eclk, d, q0, q1, q2, q3): + for bit in range(len(d)): + m.submodules += Instance("IDDRX2F", + i_SCLK=sclk, + i_ECLK=eclk, + i_RST=Const(0), + i_D=d[bit], + o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit] + ) + + def get_iddr71b(sclk, eclk, d, q0, q1, q2, q3, q4, q5, q6): + for bit in range(len(d)): + m.submodules += Instance("IDDR71B", + i_SCLK=sclk, + i_ECLK=eclk, + i_RST=Const(0), + i_D=d[bit], + o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit], + o_Q4=q4[bit], o_Q5=q5[bit], o_Q6=q6[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_oddrx2(sclk, eclk, d0, d1, d2, d3, q): + for bit in range(len(q)): + m.submodules += Instance("ODDRX2F", + i_SCLK=sclk, + i_ECLK=eclk, + i_RST=Const(0), + i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit], + o_Q=q[bit] + ) + + def get_oddr71b(sclk, eclk, d0, d1, d2, d3, d4, d5, d6, q): + for bit in range(len(q)): + m.submodules += Instance("ODDR71B", + i_SCLK=sclk, + i_ECLK=eclk, + i_RST=Const(0), + i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit], + i_D4=d4[bit], i_D5=d5[bit], i_D6=d6[bit], + o_Q=q[bit] + ) + + def get_ineg(z, invert): + if invert: + a = Signal.like(z, name_suffix="_n") + m.d.comb += z.eq(~a) + return a + else: + return z + + def get_oneg(a, invert): + if invert: + z = Signal.like(a, name_suffix="_n") + m.d.comb += z.eq(~a) + return z + else: + return a + + if "i" in pin.dir: + if pin.xdr < 2: + pin_i = get_ineg(pin.i, i_invert) + elif pin.xdr == 2: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + elif pin.xdr == 4: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + pin_i2 = get_ineg(pin.i2, i_invert) + pin_i3 = get_ineg(pin.i3, i_invert) + elif pin.xdr == 7: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + pin_i2 = get_ineg(pin.i2, i_invert) + pin_i3 = get_ineg(pin.i3, i_invert) + pin_i4 = get_ineg(pin.i4, i_invert) + pin_i5 = get_ineg(pin.i5, i_invert) + pin_i6 = get_ineg(pin.i6, i_invert) + if "o" in pin.dir: + if pin.xdr < 2: + pin_o = get_oneg(pin.o, o_invert) + elif pin.xdr == 2: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(pin.o1, o_invert) + elif pin.xdr == 4: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(pin.o1, o_invert) + pin_o2 = get_oneg(pin.o2, o_invert) + pin_o3 = get_oneg(pin.o3, o_invert) + elif pin.xdr == 7: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(pin.o1, o_invert) + pin_o2 = get_oneg(pin.o2, o_invert) + pin_o3 = get_oneg(pin.o3, o_invert) + pin_o4 = get_oneg(pin.o4, o_invert) + pin_o5 = get_oneg(pin.o5, o_invert) + pin_o6 = get_oneg(pin.o6, 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(pin.width, 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).replicate(pin.width) + elif pin.xdr == 1: + 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_oereg(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"): + get_oereg(pin.o_clk, ~pin.oe, t) + elif pin.xdr == 4: + if "i" in pin.dir: + get_iddrx2(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3) + if "o" in pin.dir: + get_oddrx2(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, o) + if pin.dir in ("oe", "io"): + get_oereg(pin.o_clk, ~pin.oe, t) + elif pin.xdr == 7: + if "i" in pin.dir: + get_iddr71b(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3, pin_i4, pin_i5, pin_i6) + if "o" in pin.dir: + get_oddr71b(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, pin_o4, pin_o5, pin_o6, o) + if pin.dir in ("oe", "io"): + get_oereg(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, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", + i_I=port.io[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, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", + i_I=o[bit], + o_O=port.io[bit] + ) + return m + + def get_tristate(self, pin, port, attrs, invert): + self._check_feature("single-ended tristate", pin, attrs, + valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", + i_T=t[bit], + i_I=o[bit], + o_O=port.io[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, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", + i_T=t[bit], + i_I=o[bit], + o_O=i[bit], + io_B=port.io[bit] + ) + return m + + def get_diff_input(self, pin, port, attrs, invert): + self._check_feature("differential input", pin, attrs, + valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", + i_I=port.p[bit], + o_O=i[bit] + ) + return m + + def get_diff_output(self, pin, port, attrs, invert): + self._check_feature("differential output", pin, attrs, + valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", + i_I=o[bit], + o_O=port.p[bit], + ) + return m + + def get_diff_tristate(self, pin, port, attrs, invert): + self._check_feature("differential tristate", pin, attrs, + valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", + i_T=t[bit], + i_I=o[bit], + o_O=port.p[bit], + ) + return m + + def get_diff_input_output(self, pin, port, attrs, invert): + self._check_feature("differential input/output", pin, attrs, + valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", + i_T=t[bit], + i_I=o[bit], + o_O=i[bit], + io_B=port.p[bit], + ) + return m + + # CDC primitives are not currently specialized for ECP5. + # While Diamond supports false path constraints; nextpnr-ecp5 does not. diff --git a/amaranth/vendor/_lattice_ice40.py b/amaranth/vendor/_lattice_ice40.py new file mode 100644 index 0000000..c186988 --- /dev/null +++ b/amaranth/vendor/_lattice_ice40.py @@ -0,0 +1,624 @@ +from abc import abstractmethod + +from ..hdl import * +from ..lib.cdc import ResetSynchronizer +from ..build import * + + +class LatticeICE40Platform(TemplatedPlatform): + """ + .. rubric:: IceStorm toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-ice40`` + * ``icepack`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_ICESTORM``, if present. + + 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_ice40`` Yosys command. + * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script. + * ``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. + * ``{{name}}.json``: synthesized RTL. + * ``{{name}}.tim``: nextpnr log. + * ``{{name}}.asc``: ASCII bitstream. + * ``{{name}}.bin``: binary bitstream. + + .. rubric:: 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 + ``AMARANTH_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 = None # selected when creating platform + + device = property(abstractmethod(lambda: None)) + package = property(abstractmethod(lambda: None)) + + # IceStorm templates + + _nextpnr_device_options = { + "iCE40LP384": "--lp384", + "iCE40LP1K": "--lp1k", + "iCE40LP4K": "--lp8k", + "iCE40LP8K": "--lp8k", + "iCE40HX1K": "--hx1k", + "iCE40HX4K": "--hx8k", + "iCE40HX8K": "--hx8k", + "iCE40UP5K": "--up5k", + "iCE40UP3K": "--up5k", + "iCE5LP4K": "--u4k", + "iCE5LP2K": "--u4k", + "iCE5LP1K": "--u4k", + } + _nextpnr_package_options = { + "iCE40LP4K": ":4k", + "iCE40HX4K": ":4k", + "iCE40UP3K": "", + "iCE5LP2K": "", + "iCE5LP1K": "", + } + + _icestorm_required_tools = [ + "yosys", + "nextpnr-ice40", + "icepack", + ] + _icestorm_file_templates = { + **TemplatedPlatform.build_script_templates, + "{{name}}.il": r""" + # {{autogenerated}} + {{emit_rtlil()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.ys": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".v") -%} + read_verilog {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".sv") -%} + read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} + {% endfor %} + {% for file in platform.iter_files(".il") -%} + read_ilang {{file}} + {% endfor %} + read_ilang {{name}}.il + delete w:$verilog_initial_trigger + {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} + synth_ice40 {{get_override("synth_opts")|options}} -top {{name}} + {{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 %} + {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} + set_frequency {{net_signal|hierarchy(".")}} {{frequency/1000000}} + {% endfor%} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """, + } + _icestorm_command_templates = [ + r""" + {{invoke_tool("yosys")}} + {{quiet("-q")}} + {{get_override("yosys_opts")|options}} + -l {{name}}.rpt + {{name}}.ys + """, + r""" + {{invoke_tool("nextpnr-ice40")}} + {{quiet("--quiet")}} + {{get_override("nextpnr_opts")|options}} + --log {{name}}.tim + {{platform._nextpnr_device_options[platform.device]}} + --package + {{platform.package|lower}}{{platform._nextpnr_package_options[platform.device]| + default("")}} + --json {{name}}.json + --pcf {{name}}.pcf + --asc {{name}}.asc + """, + r""" + {{invoke_tool("icepack")}} + {{verbose("-v")}} + {{name}}.asc + {{name}}.bin + """ + ] + + # iCECube2 templates + + _icecube2_required_tools = [ + "synthesis", + "synpwrap", + "tclsh", + ] + _icecube2_file_templates = { + **TemplatedPlatform.build_script_templates, + "build_{{name}}.sh": r""" + # {{autogenerated}} + set -e{{verbose("x")}} + {% for var in platform._all_toolchain_env_vars %} + if [ -n "${{var}}" ]; then + # LSE environment + export LD_LIBRARY_PATH=${{var}}/LSE/bin/lin64:$LD_LIBRARY_PATH + export PATH=${{var}}/LSE/bin/lin64:$PATH + export FOUNDRY=${{var}}/LSE + # Synplify environment + export LD_LIBRARY_PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH + export PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH + export SYNPLIFY_PATH=${{var}}/synpbase + # Common environment + export SBT_DIR=${{var}}/sbt_backend + else + echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1 + fi + {% endfor %} + {{emit_commands("sh")}} + """, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_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_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_files(".v", ".sv", ".vhd", ".vhdl") -%} + add_file -verilog {{file|tcl_escape}} + {% 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 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)")}} + """, + "{{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"AMARANTH_ENV_{self.toolchain}" + if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"): + return f"AMARANTH_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 + + @property + def default_clk_constraint(self): + # Internal high-speed oscillator: 48 MHz / (2 ^ div) + if self.default_clk == "SB_HFOSC": + return Clock(48e6 / 2 ** self.hfosc_div) + # Internal low-speed oscillator: 10 KHz + elif self.default_clk == "SB_LFOSC": + return Clock(10e3) + # Otherwise, use the defined Clock resource. + return super().default_clk_constraint + + 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 + # global reset. Note that this is a *time-based* delay, generated purely by the internal + # oscillator, which may not be observed nor influenced directly. For details, see links: + # * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411 + # * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673 + # + # To handle this, it is necessary to have a global reset in any iCE40 design that may + # potentially instantiate BRAMs, and assert this reset for >3 us after configuration. + # (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated + # reset line, this line is ORed with the power on reset. + # + # If an internal oscillator is selected as the default clock source, the power-on-reset + # delay is increased to 100 us, since the oscillators are only stable after that long. + # + # The power-on reset timer counts up because the vendor tools do not support initialization + # of flip-flops. + if name == "sync" and self.default_clk is not None: + m = Module() + + # Internal high-speed clock: 6 MHz, 12 MHz, 24 MHz, or 48 MHz depending on the divider. + if self.default_clk == "SB_HFOSC": + if not hasattr(self, "hfosc_div"): + raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer " + "between 0 and 3") + if not isinstance(self.hfosc_div, int) or self.hfosc_div < 0 or self.hfosc_div > 3: + raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer " + "between 0 and 3, not {!r}" + .format(self.hfosc_div)) + clk_i = Signal() + m.submodules += Instance("SB_HFOSC", + i_CLKHFEN=1, + i_CLKHFPU=1, + p_CLKHF_DIV="0b{0:02b}".format(self.hfosc_div), + o_CLKHF=clk_i) + delay = int(100e-6 * self.default_clk_frequency) + # Internal low-speed clock: 10 KHz. + elif self.default_clk == "SB_LFOSC": + clk_i = Signal() + m.submodules += Instance("SB_LFOSC", + i_CLKLFEN=1, + i_CLKLFPU=1, + o_CLKLF=clk_i) + delay = int(100e-6 * self.default_clk_frequency) + # User-defined clock signal. + else: + clk_i = self.request(self.default_clk).i + delay = int(15e-6 * self.default_clk_frequency) + + if self.default_rst is not None: + rst_i = self.request(self.default_rst).i + else: + rst_i = Const(0) + + # Power-on-reset domain + m.domains += ClockDomain("por", reset_less=True, local=True) + timer = Signal(range(delay)) + ready = Signal() + m.d.comb += ClockSignal("por").eq(clk_i) + with m.If(timer == delay): + m.d.por += ready.eq(1) + with m.Else(): + m.d.por += timer.eq(timer + 1) + + # Primary domain + m.domains += ClockDomain("sync") + m.d.comb += ClockSignal("sync").eq(clk_i) + if self.default_rst is not None: + m.submodules.reset_sync = ResetSynchronizer(~ready | rst_i, domain="sync") + else: + m.d.comb += ResetSignal("sync").eq(~ready) + + return m + + def should_skip_port_component(self, port, attrs, component): + # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for + # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs + # between LP/HX and UP series: + # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting) + # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting) + if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n": + return True + return False + + def _get_io_buffer(self, m, pin, port, attrs, *, i_invert=False, o_invert=False, + invert_lut=False): + def get_dff(clk, d, q): + m.submodules += Instance("$dff", + p_CLK_POLARITY=1, + p_WIDTH=len(d), + i_CLK=clk, + i_D=d, + o_Q=q) + + def get_ineg(y, invert): + if invert_lut: + a = Signal.like(y, name_suffix="_x{}".format(1 if invert else 0)) + for bit in range(len(y)): + m.submodules += Instance("SB_LUT4", + p_LUT_INIT=Const(0b01 if invert else 0b10, 16), + i_I0=a[bit], + i_I1=Const(0), + i_I2=Const(0), + i_I3=Const(0), + o_O=y[bit]) + return a + elif invert: + a = Signal.like(y, name_suffix="_n") + m.d.comb += y.eq(~a) + return a + else: + return y + + def get_oneg(a, invert): + if invert_lut: + y = Signal.like(a, name_suffix="_x{}".format(1 if invert else 0)) + for bit in range(len(a)): + m.submodules += Instance("SB_LUT4", + p_LUT_INIT=Const(0b01 if invert else 0b10, 16), + i_I0=a[bit], + i_I1=Const(0), + i_I2=Const(0), + i_I3=Const(0), + o_O=y[bit]) + return y + elif invert: + y = Signal.like(a, name_suffix="_n") + m.d.comb += y.eq(~a) + return y + else: + return a + + if "GLOBAL" in attrs: + is_global_input = bool(attrs["GLOBAL"]) + del attrs["GLOBAL"] + else: + is_global_input = False + assert not (is_global_input and i_invert) + + if "i" in pin.dir: + if pin.xdr < 2: + pin_i = get_ineg(pin.i, i_invert) + elif pin.xdr == 2: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + if "o" in pin.dir: + if pin.xdr < 2: + pin_o = get_oneg(pin.o, o_invert) + elif pin.xdr == 2: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(pin.o1, o_invert) + + if "i" in pin.dir and pin.xdr == 2: + i0_ff = Signal.like(pin_i0, name_suffix="_ff") + i1_ff = Signal.like(pin_i1, name_suffix="_ff") + get_dff(pin.i_clk, i0_ff, pin_i0) + get_dff(pin.i_clk, i1_ff, pin_i1) + if "o" in pin.dir and pin.xdr == 2: + o1_ff = Signal.like(pin_o1, name_suffix="_ff") + get_dff(pin.o_clk, pin_o1, o1_ff) + + for bit in range(len(port)): + io_args = [ + ("io", "PACKAGE_PIN", port[bit]), + *(("p", key, value) for key, value in attrs.items()), + ] + + if "i" not in pin.dir: + # If no input pin is requested, it is important to use a non-registered input pin + # type, because an output-only pin would not have an input clock, and if its input + # is configured as registered, this would prevent a co-located input-capable pin + # from using an input clock. + i_type = 0b01 # PIN_INPUT + elif pin.xdr == 0: + i_type = 0b01 # PIN_INPUT + elif pin.xdr > 0: + i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR + if "o" not in pin.dir: + o_type = 0b0000 # PIN_NO_OUTPUT + elif pin.xdr == 0 and pin.dir == "o": + o_type = 0b0110 # PIN_OUTPUT + elif pin.xdr == 0: + o_type = 0b1010 # PIN_OUTPUT_TRISTATE + elif pin.xdr == 1 and pin.dir == "o": + o_type = 0b0101 # PIN_OUTPUT_REGISTERED + elif pin.xdr == 1: + o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED + elif pin.xdr == 2 and pin.dir == "o": + o_type = 0b0100 # PIN_OUTPUT_DDR + elif pin.xdr == 2: + o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED + 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)) + if hasattr(pin, "o_clk"): + io_args.append(("i", "OUTPUT_CLK", pin.o_clk)) + + if "i" in pin.dir: + if pin.xdr == 0 and is_global_input: + io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit])) + elif pin.xdr < 2: + io_args.append(("o", "D_IN_0", pin_i[bit])) + elif pin.xdr == 2: + # Re-register both inputs before they enter fabric. This increases hold time + # to an entire cycle, and adds one cycle of latency. + io_args.append(("o", "D_IN_0", i0_ff[bit])) + io_args.append(("o", "D_IN_1", i1_ff[bit])) + if "o" in pin.dir: + if pin.xdr < 2: + io_args.append(("i", "D_OUT_0", pin_o[bit])) + elif pin.xdr == 2: + # Re-register negedge output after it leaves fabric. This increases setup time + # to an entire cycle, and doesn't add latency. + io_args.append(("i", "D_OUT_0", pin_o0[bit])) + io_args.append(("i", "D_OUT_1", o1_ff[bit])) + + if pin.dir in ("oe", "io"): + io_args.append(("i", "OUTPUT_ENABLE", pin.oe)) + + if is_global_input: + m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_GB_IO", *io_args) + else: + m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_IO", *io_args) + + 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() + self._get_io_buffer(m, pin, port.io, attrs, i_invert=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) + m = Module() + self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) + 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() + self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) + 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() + self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert) + return m + + def get_diff_input(self, pin, port, attrs, invert): + self._check_feature("differential input", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + # See comment in should_skip_port_component above. + self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert) + return m + + def get_diff_output(self, pin, port, attrs, invert): + self._check_feature("differential output", pin, attrs, + valid_xdrs=(0, 1, 2), valid_attrs=True) + m = Module() + # Note that the non-inverting output pin is not driven the same way as a regular + # output pin. The inverter introduces a delay, so for a non-inverting output pin, + # an identical delay is introduced by instantiating a LUT. This makes the waveform + # perfectly symmetric in the xdr=0 case. + self._get_io_buffer(m, pin, port.p, attrs, o_invert= invert, invert_lut=True) + self._get_io_buffer(m, pin, port.n, attrs, o_invert=not invert, invert_lut=True) + return m + + # Tristate bidirectional buffers are not supported on iCE40 because it requires external + # termination, which is different for differential pins configured as inputs and outputs. + + # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports + # the necessary attributes; nextpnr-ice40 does not. diff --git a/amaranth/vendor/_lattice_machxo_2_3l.py b/amaranth/vendor/_lattice_machxo_2_3l.py new file mode 100644 index 0000000..e3ca282 --- /dev/null +++ b/amaranth/vendor/_lattice_machxo_2_3l.py @@ -0,0 +1,449 @@ +from abc import abstractmethod + +from ..hdl import * +from ..build import * + + +# MachXO2 and MachXO3L primitives are the same. Handle both using +# one class and expose user-aliases for convenience. +class LatticeMachXO2Or3LPlatform(TemplatedPlatform): + """ + Required tools: + * ``pnmainc`` + * ``ddtcmd`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond + itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``, + for instance) is known to work:: + + @echo off + set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH% + + 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}}.jed``: JEDEC fuse file. + * ``{{name}}.bit``: binary bitstream. + * ``{{name}}.svf``: JTAG programming vector for FLASH programming. + * ``{{name}}_flash.svf``: JTAG programming vector for FLASH programming. + * ``{{name}}_sram.svf``: JTAG programming vector for SRAM programming. + """ + + toolchain = "Diamond" + + device = property(abstractmethod(lambda: None)) + package = property(abstractmethod(lambda: None)) + speed = property(abstractmethod(lambda: None)) + grade = "C" # [C]ommercial, [I]ndustrial + + required_tools = [ + "pnmainc", + "ddtcmd" + ] + file_templates = { + **TemplatedPlatform.build_script_templates, + "build_{{name}}.sh": r""" + # {{autogenerated}} + set -e{{verbose("x")}} + if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi + {% for var in platform._all_toolchain_env_vars %} + if [ -n "${{var}}" ]; then + bindir=$(dirname "${{var}}") + . "${{var}}" + fi + {% endfor %} + {{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 {{name}}_impl \ + -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \ + -lpf {{name}}.lpf \ + -synthesis synplify + {% for file in platform.iter_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 + prj_run Translate -impl impl + prj_run Map -impl impl + prj_run PAR -impl impl + prj_run Export -impl impl -task Bitgen + prj_run Export -impl impl -task Jedecgen + {{get_override("script_after_export")|default("# (script_after_export placeholder)")}} + """, + "{{name}}.lpf": r""" + # {{autogenerated}} + BLOCK ASYNCPATHS; + BLOCK RESETPATHS; + {% 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 attrs.items() %} {{key}}={{value}}{% endfor %}; + {% endif %} + {% 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)")}} + """, + } + 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 -jed + -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} + -if {{name}}_impl/{{name}}_impl.jed -of {{name}}.jed + """, + r""" + {{invoke_tool("ddtcmd")}} + -oft -svfsingle -revd -op "FLASH Erase,Program,Verify" + -if {{name}}_impl/{{name}}_impl.jed -of {{name}}_flash.svf + """, + # TODO(amaranth-0.4): remove + r""" + {% if syntax == "bat" -%} + copy {{name}}_flash.svf {{name}}.svf + {% else -%} + cp {{name}}_flash.svf {{name}}.svf + {% endif %} + """, + r""" + {{invoke_tool("ddtcmd")}} + -oft -svfsingle -revd -op "SRAM Fast Program" + -if {{name}}_impl/{{name}}_impl.bit -of {{name}}_sram.svf + """, + ] + # These numbers were extracted from + # "MachXO2 sysCLOCK PLL Design and Usage Guide" + _supported_osch_freqs = [ + 2.08, 2.15, 2.22, 2.29, 2.38, 2.46, 2.56, 2.66, 2.77, 2.89, + 3.02, 3.17, 3.33, 3.50, 3.69, 3.91, 4.16, 4.29, 4.43, 4.59, + 4.75, 4.93, 5.12, 5.32, 5.54, 5.78, 6.05, 6.33, 6.65, 7.00, + 7.39, 7.82, 8.31, 8.58, 8.87, 9.17, 9.50, 9.85, 10.23, 10.64, + 11.08, 11.57, 12.09, 12.67, 13.30, 14.00, 14.78, 15.65, 15.65, 16.63, + 17.73, 19.00, 20.46, 22.17, 24.18, 26.60, 29.56, 33.25, 38.00, 44.33, + 53.20, 66.50, 88.67, 133.00 + ] + + @property + def default_clk_constraint(self): + # Internal high-speed oscillator on MachXO2/MachXO3L devices. + # It can have a range of frequencies. + if self.default_clk == "OSCH": + assert self.osch_frequency in self._supported_osch_freqs + return Clock(int(self.osch_frequency * 1e6)) + # Otherwise, use the defined Clock resource. + return super().default_clk_constraint + + def create_missing_domain(self, name): + # Lattice MachXO2/MachXO3L 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: + using_osch = False + if self.default_clk == "OSCH": + using_osch = True + clk_i = Signal() + 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() + m = Module() + # There is no end-of-startup signal on MachXO2/MachXO3L, 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), + ] + if using_osch: + osch_freq = self.osch_frequency + if osch_freq not in self._supported_osch_freqs: + raise ValueError("Frequency {!r} is not valid for OSCH clock. Valid frequencies are {!r}" + .format(osch_freq, self._supported_osch_freqs)) + osch_freq_param = "{:.2f}".format(float(osch_freq)) + m.submodules += [ Instance("OSCH", p_NOM_FREQ=osch_freq_param, i_STDBY=Const(0), o_OSC=clk_i, o_SEDSTDBY=Signal()) ] + # 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 = [ + "PCI33", "LVTTL33", "LVCMOS33", "LVCMOS25", "LVCMOS18", "LVCMOS15", "LVCMOS12", + "LVCMOS25R33", "LVCMOS18R33", "LVCMOS18R25", "LVCMOS15R33", "LVCMOS15R25", "LVCMOS12R33", + "LVCMOS12R25", "LVCMOS10R33", "LVCMOS10R25", "SSTL25_I", "SSTL25_II", "SSTL18_I", + "SSTL18_II", "HSTL18_I", "HSTL18_II", + ] + _differential_io_types = [ + "LVDS25", "LVDS25E", "RSDS25", "RSDS25E", "BLVDS25", "BLVDS25E", "MLVDS25", "MLVDS25E", + "LVPECL33", "LVPECL33E", "SSTL25D_I", "SSTL25D_II", "SSTL18D_I", "SSTL18D_II", + "HSTL18D_I", "HSTL18D_II", "LVTTL33D", "LVCMOS33D", "LVCMOS25D", "LVCMOS18D", "LVCMOS15D", + "LVCMOS12D", "MIPI", + ] + + 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=False, o_invert=False): + 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("IDDRXE", + 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("ODDRXE", + i_SCLK=sclk, + i_RST=Const(0), + i_D0=d0[bit], i_D1=d1[bit], + o_Q=q[bit] + ) + + def get_ineg(z, invert): + if invert: + a = Signal.like(z, name_suffix="_n") + m.d.comb += z.eq(~a) + return a + else: + return z + + def get_oneg(a, invert): + if invert: + z = Signal.like(a, name_suffix="_n") + m.d.comb += z.eq(~a) + return z + else: + return a + + if "i" in pin.dir: + if pin.xdr < 2: + pin_i = get_ineg(pin.i, i_invert) + elif pin.xdr == 2: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + if "o" in pin.dir: + if pin.xdr < 2: + pin_o = get_oneg(pin.o, o_invert) + elif pin.xdr == 2: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(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() + i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) + for bit in range(len(port)): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", + i_I=port.io[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=invert) + for bit in range(len(port)): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", + i_I=o[bit], + o_O=port.io[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=invert) + for bit in range(len(port)): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", + i_T=t, + i_I=o[bit], + o_O=port.io[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=invert, o_invert=invert) + for bit in range(len(port)): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", + i_T=t, + i_I=o[bit], + o_O=i[bit], + io_B=port.io[bit] + ) + return m + + def get_diff_input(self, pin, 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=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", + i_I=port.p[bit], + o_O=i[bit] + ) + return m + + def get_diff_output(self, pin, 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=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", + i_I=o[bit], + o_O=port.p[bit], + ) + return m + + def get_diff_tristate(self, pin, 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=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", + i_T=t, + i_I=o[bit], + o_O=port.p[bit], + ) + return m + + def get_diff_input_output(self, pin, 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=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", + i_T=t, + i_I=o[bit], + o_O=i[bit], + io_B=port.p[bit], + ) + return m + + # CDC primitives are not currently specialized for MachXO2/MachXO3L. diff --git a/amaranth/vendor/_quicklogic.py b/amaranth/vendor/_quicklogic.py new file mode 100644 index 0000000..697ec4c --- /dev/null +++ b/amaranth/vendor/_quicklogic.py @@ -0,0 +1,184 @@ +from abc import abstractmethod + +from ..hdl import * +from ..lib.cdc import ResetSynchronizer +from ..build import * + + +__all__ = ["QuicklogicPlatform"] + + +class QuicklogicPlatform(TemplatedPlatform): + """ + .. rubric:: Symbiflow toolchain + + Required tools: + * ``symbiflow_synth`` + * ``symbiflow_pack`` + * ``symbiflow_place`` + * ``symbiflow_route`` + * ``symbiflow_write_fasm`` + * ``symbiflow_write_bitstream`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_QLSYMBIFLOW``, if present. + + Available overrides: + * ``add_constraints``: inserts commands in XDC file. + """ + + device = property(abstractmethod(lambda: None)) + package = property(abstractmethod(lambda: None)) + + # Since the QuickLogic version of SymbiFlow toolchain is not upstreamed yet + # we should distinguish the QuickLogic version from mainline one. + # QuickLogic toolchain: https://github.com/QuickLogic-Corp/quicklogic-fpga-toolchain/releases + toolchain = "QLSymbiflow" + + required_tools = [ + "symbiflow_synth", + "symbiflow_pack", + "symbiflow_place", + "symbiflow_route", + "symbiflow_write_fasm", + "symbiflow_write_bitstream", + "symbiflow_write_openocd", + ] + file_templates = { + **TemplatedPlatform.build_script_templates, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.pcf": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + set_io {{port_name}} {{pin_name}} + {% endfor %} + """, + "{{name}}.xdc": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + {% for attr_name, attr_value in attrs.items() -%} + set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }] + {% endfor %} + {% endfor %} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """, + "{{name}}.sdc": r""" + # {{autogenerated}} + {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} + {% if port_signal is not none -%} + create_clock -period {{100000000/frequency}} {{port_signal.name|ascii_escape}} + {% endif %} + {% endfor %} + """ + } + command_templates = [ + r""" + {{invoke_tool("symbiflow_synth")}} + -t {{name}} + -v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v + -d {{platform.device}} + -p {{name}}.pcf + -P {{platform.package}} + -x {{name}}.xdc + """, + r""" + {{invoke_tool("symbiflow_pack")}} + -e {{name}}.eblif + -d {{platform.device}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_place")}} + -e {{name}}.eblif + -d {{platform.device}} + -p {{name}}.pcf + -n {{name}}.net + -P {{platform.package}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_route")}} + -e {{name}}.eblif + -d {{platform.device}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_write_fasm")}} + -e {{name}}.eblif + -d {{platform.device}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_write_bitstream")}} + -f {{name}}.fasm + -d {{platform.device}} + -P {{platform.package}} + -b {{name}}.bit + """, + # This should be `invoke_tool("symbiflow_write_openocd")`, but isn't because of a bug in + # the QLSymbiflow v1.3.0 toolchain release. + r""" + python3 -m quicklogic_fasm.bitstream_to_openocd + {{name}}.bit + {{name}}.openocd + --osc-freq {{platform.osc_freq}} + --fpga-clk-divider {{platform.osc_div}} + """, + ] + + # Common logic + + @property + def default_clk_constraint(self): + if self.default_clk == "sys_clk0": + return Clock(self.osc_freq / self.osc_div) + return super().default_clk_constraint + + def add_clock_constraint(self, clock, frequency): + super().add_clock_constraint(clock, frequency) + clock.attrs["keep"] = "TRUE" + + def create_missing_domain(self, name): + if name == "sync" and self.default_clk is not None: + m = Module() + if self.default_clk == "sys_clk0": + if not hasattr(self, "osc_div"): + raise ValueError("OSC divider (osc_div) must be an integer between 2 " + "and 512") + if not isinstance(self.osc_div, int) or self.osc_div < 2 or self.osc_div > 512: + raise ValueError("OSC divider (osc_div) must be an integer between 2 " + "and 512, not {!r}" + .format(self.osc_div)) + if not hasattr(self, "osc_freq"): + raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 " + "and 80000000") + if not isinstance(self.osc_freq, int) or self.osc_freq < 2100000 or self.osc_freq > 80000000: + raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 " + "and 80000000, not {!r}" + .format(self.osc_freq)) + clk_i = Signal() + sys_clk0 = Signal() + m.submodules += Instance("qlal4s3b_cell_macro", + o_Sys_Clk0=sys_clk0) + m.submodules += Instance("gclkbuff", + o_A=sys_clk0, + o_Z=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) + + m.domains += ClockDomain("sync") + m.d.comb += ClockSignal("sync").eq(clk_i) + m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") + return m diff --git a/amaranth/vendor/_xilinx.py b/amaranth/vendor/_xilinx.py new file mode 100644 index 0000000..47699e3 --- /dev/null +++ b/amaranth/vendor/_xilinx.py @@ -0,0 +1,1218 @@ +import re +from abc import abstractmethod + +from ..hdl import * +from ..lib.cdc import ResetSynchronizer +from ..build import * + + +__all__ = ["XilinxPlatform"] + + +class XilinxPlatform(TemplatedPlatform): + """ + .. rubric:: Vivado toolchain + + Required tools: + * ``vivado`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_VIVADO``, if present. + + Available overrides: + * ``script_after_read``: inserts commands after ``read_xdc`` in Tcl script. + * ``synth_design_opts``: sets options for ``synth_design``. + * ``script_after_synth``: inserts commands after ``synth_design`` in Tcl script. + * ``script_after_place``: inserts commands after ``place_design`` in Tcl script. + * ``script_after_route``: inserts commands after ``route_design`` in Tcl script. + * ``script_before_bitstream``: inserts commands before ``write_bitstream`` in Tcl script. + * ``script_after_bitstream``: inserts commands after ``write_bitstream`` in Tcl script. + * ``add_constraints``: inserts commands in XDC file. + * ``vivado_opts``: adds extra options for ``vivado``. + + Build products: + * ``{{name}}.log``: Vivado log. + * ``{{name}}_timing_synth.rpt``: Vivado report. + * ``{{name}}_utilization_hierarchical_synth.rpt``: Vivado report. + * ``{{name}}_utilization_synth.rpt``: Vivado report. + * ``{{name}}_utilization_hierarchical_place.rpt``: Vivado report. + * ``{{name}}_utilization_place.rpt``: Vivado report. + * ``{{name}}_io.rpt``: Vivado report. + * ``{{name}}_control_sets.rpt``: Vivado report. + * ``{{name}}_clock_utilization.rpt``: Vivado report. + * ``{{name}}_route_status.rpt``: Vivado report. + * ``{{name}}_drc.rpt``: Vivado report. + * ``{{name}}_methodology.rpt``: Vivado report. + * ``{{name}}_timing.rpt``: Vivado report. + * ``{{name}}_power.rpt``: Vivado report. + * ``{{name}}_route.dcp``: Vivado design checkpoint. + * ``{{name}}.bit``: binary bitstream with metadata. + * ``{{name}}.bin``: binary bitstream. + + .. rubric:: ISE toolchain + + Required tools: + * ``xst`` + * ``ngdbuild`` + * ``map`` + * ``par`` + * ``bitgen`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_ISE``, if present. + + Available overrides: + * ``script_after_run``: inserts commands after ``run`` in XST script. + * ``add_constraints``: inserts commands in UCF file. + * ``xst_opts``: adds extra options for ``xst``. + * ``ngdbuild_opts``: adds extra options for ``ngdbuild``. + * ``map_opts``: adds extra options for ``map``. + * ``par_opts``: adds extra options for ``par``. + * ``bitgen_opts``: adds extra and overrides default options for ``bitgen``; + default options: ``-g Compress``. + + Build products: + * ``{{name}}.srp``: synthesis report. + * ``{{name}}.ngc``: synthesized RTL. + * ``{{name}}.bld``: NGDBuild log. + * ``{{name}}.ngd``: design database. + * ``{{name}}_map.map``: MAP log. + * ``{{name}}_map.mrp``: mapping report. + * ``{{name}}_map.ncd``: mapped netlist. + * ``{{name}}.pcf``: physical constraints. + * ``{{name}}_par.par``: PAR log. + * ``{{name}}_par_pad.txt``: I/O usage report. + * ``{{name}}_par.ncd``: place and routed netlist. + * ``{{name}}.drc``: DRC report. + * ``{{name}}.bgn``: BitGen log. + * ``{{name}}.bit``: binary bitstream with metadata. + * ``{{name}}.bin``: raw binary bitstream. + + .. rubric:: Symbiflow toolchain + + Required tools: + * ``symbiflow_synth`` + * ``symbiflow_pack`` + * ``symbiflow_place`` + * ``symbiflow_route`` + * ``symbiflow_write_fasm`` + * ``symbiflow_write_bitstream`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_SYMBIFLOW``, if present. + + Available overrides: + * ``add_constraints``: inserts commands in XDC file. + + .. rubric:: Xray toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-xilinx`` + * ``fasm2frames`` + * ``xc7frames2bit`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_XRAY``, if present. + """ + + toolchain = None # selected when creating platform + + device = property(abstractmethod(lambda: None)) + package = property(abstractmethod(lambda: None)) + speed = property(abstractmethod(lambda: None)) + + @property + def _part(self): + if self.family in {"ultrascale", "ultrascaleplus"}: + return "{}-{}-{}".format(self.device, self.package, self.speed) + else: + return "{}{}-{}".format(self.device, self.package, self.speed) + + @property + def vendor_toolchain(self): + return self.toolchain in ["Vivado", "ISE"] + + # Vivado templates + + _vivado_required_tools = ["vivado"] + _vivado_file_templates = { + **TemplatedPlatform.build_script_templates, + "build_{{name}}.sh": r""" + # {{autogenerated}} + set -e{{verbose("x")}} + if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi + {% for var in platform._all_toolchain_env_vars %} + [ -n "${{var}}" ] && . "${{var}}" + {% endfor %} + {{emit_commands("sh")}} + """, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.tcl": r""" + # {{autogenerated}} + create_project -force -name {{name}} -part {{platform._part}} + {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} + add_files {{file|tcl_escape}} + {% endfor %} + add_files {{name}}.v + read_xdc {{name}}.xdc + {% for file in platform.iter_files(".xdc") -%} + read_xdc {{file|tcl_escape}} + {% endfor %} + {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} + synth_design -top {{name}} {{get_override("synth_design_opts")}} + foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.false_path == "TRUE"}] { + set_false_path -to $cell + } + foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay != ""}] { + set clock [get_clocks -of_objects \ + [all_fanin -flat -startpoints_only [get_pin $cell/D]]] + if {[llength $clock] != 0} { + set_max_delay -datapath_only -from $clock \ + -to [get_cells $cell] [get_property amaranth.vivado.max_delay $cell] + } + } + {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} + report_timing_summary -file {{name}}_timing_synth.rpt + report_utilization -hierarchical -file {{name}}_utilization_hierarchical_synth.rpt + report_utilization -file {{name}}_utilization_synth.rpt + opt_design + place_design + {{get_override("script_after_place")|default("# (script_after_place placeholder)")}} + report_utilization -hierarchical -file {{name}}_utilization_hierarchical_place.rpt + report_utilization -file {{name}}_utilization_place.rpt + report_io -file {{name}}_io.rpt + report_control_sets -verbose -file {{name}}_control_sets.rpt + report_clock_utilization -file {{name}}_clock_utilization.rpt + route_design + {{get_override("script_after_route")|default("# (script_after_route placeholder)")}} + phys_opt_design + report_timing_summary -no_header -no_detailed_paths + write_checkpoint -force {{name}}_route.dcp + report_route_status -file {{name}}_route_status.rpt + report_drc -file {{name}}_drc.rpt + report_methodology -file {{name}}_methodology.rpt + report_timing_summary -datasheet -max_paths 10 -file {{name}}_timing.rpt + report_power -file {{name}}_power.rpt + {{get_override("script_before_bitstream")|default("# (script_before_bitstream placeholder)")}} + write_bitstream -force -bin_file {{name}}.bit + {{get_override("script_after_bitstream")|default("# (script_after_bitstream placeholder)")}} + quit + """, + "{{name}}.xdc": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + set_property LOC {{pin_name}} [get_ports {{port_name|tcl_escape}}] + {% for attr_name, attr_value in attrs.items() -%} + set_property {{attr_name}} {{attr_value|tcl_escape}} [get_ports {{port_name|tcl_escape}}] + {% endfor %} + {% endfor %} + {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} + {% if port_signal is not none -%} + create_clock -name {{port_signal.name|ascii_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}] + {% else -%} + create_clock -name {{net_signal.name|ascii_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}] + {% endif %} + {% endfor %} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """ + } + _vivado_command_templates = [ + r""" + {{invoke_tool("vivado")}} + {{verbose("-verbose")}} + {{get_override("vivado_opts")|options}} + -mode batch + -log {{name}}.log + -source {{name}}.tcl + """ + ] + + # ISE toolchain + + _ise_required_tools = [ + "xst", + "ngdbuild", + "map", + "par", + "bitgen", + ] + _ise_file_templates = { + **TemplatedPlatform.build_script_templates, + "build_{{name}}.sh": r""" + # {{autogenerated}} + set -e{{verbose("x")}} + if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi + {% for var in platform._all_toolchain_env_vars %} + [ -n "${{var}}" ] && . "${{var}}" + {% endfor %} + {{emit_commands("sh")}} + """, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.prj": r""" + # {{autogenerated}} + {% for file in platform.iter_files(".vhd", ".vhdl") -%} + vhdl work {{file}} + {% endfor %} + {% for file in platform.iter_files(".v") -%} + verilog work {{file}} + {% endfor %} + verilog work {{name}}.v + """, + "{{name}}.xst": r""" + # {{autogenerated}} + run + -ifn {{name}}.prj + -ofn {{name}}.ngc + -top {{name}} + -use_new_parser yes + -p {{platform.device}}{{platform.package}}-{{platform.speed}} + {{get_override("script_after_run")|default("# (script_after_run placeholder)")}} + """, + "{{name}}.ucf": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + {% set port_name = port_name|replace("[", "<")|replace("]", ">") -%} + NET "{{port_name}}" LOC={{pin_name}}; + {% for attr_name, attr_value in attrs.items() -%} + NET "{{port_name}}" {{attr_name}}={{attr_value}}; + {% endfor %} + {% endfor %} + {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} + NET "{{net_signal|hierarchy("/")}}" TNM_NET="PRD{{net_signal|hierarchy("/")}}"; + TIMESPEC "TS{{net_signal|hierarchy("__")}}"=PERIOD "PRD{{net_signal|hierarchy("/")}}" {{1000000000/frequency}} ns HIGH 50%; + {% endfor %} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """ + } + _ise_command_templates = [ + r""" + {{invoke_tool("xst")}} + {{get_override("xst_opts")|options}} + -ifn {{name}}.xst + """, + r""" + {{invoke_tool("ngdbuild")}} + {{quiet("-quiet")}} + {{verbose("-verbose")}} + {{get_override("ngdbuild_opts")|options}} + -uc {{name}}.ucf + {{name}}.ngc + """, + r""" + {{invoke_tool("map")}} + {{verbose("-detail")}} + {{get_override("map_opts")|default([])|options}} + -w + -o {{name}}_map.ncd + {{name}}.ngd + {{name}}.pcf + """, + r""" + {{invoke_tool("par")}} + {{get_override("par_opts")|default([])|options}} + -w + {{name}}_map.ncd + {{name}}_par.ncd + {{name}}.pcf + """, + r""" + {{invoke_tool("bitgen")}} + {{get_override("bitgen_opts")|default(["-g Compress"])|options}} + -w + -g Binary:Yes + {{name}}_par.ncd + {{name}}.bit + """ + ] + + # Symbiflow templates + + # symbiflow does not distinguish between speed grades + # TODO: join with _xray_part + @property + def _symbiflow_part(self): + # drop the trailing speed grade letter(s), if any + part = re.sub(r"[^\d]+$", "", self._part) + # drop temp/speed grade letters after family name, if any + part = re.sub(r"(.{4}\d+t)[il]", r"\1", part) + return part + + # bitstream device name according to prjxray-db path + # TODO: join with _xray_family + @property + def _symbiflow_bitstream_device(self): + if self._part.startswith("xc7a"): + return "artix7" + elif self._part.startswith("xc7k"): + return "kintex7" + elif self._part.startswith("xc7z"): + return "zynq7" + elif self._part.startswith("xc7s"): + return "spartan7" + else: + print("Unknown bitstream device for part {}".format(self._part)) + raise ValueError + + # device naming according to part_db.yml of f4pga project + @property + def _symbiflow_device(self): + if self._part.startswith("xc7a35") or self._part.startswith("xc7a50"): + return "xc7a50t_test" + elif self._part.startswith("xc7a100"): + return "xc7a100t_test" + elif self._part.startswith("xc7a200"): + return "xc7a200t_test" + else: + print("Unknown symbiflow device for part {}".format(self._part)) + raise ValueError + + + _symbiflow_required_tools = [ + "symbiflow_synth", + "symbiflow_pack", + "symbiflow_place", + "symbiflow_route", + "symbiflow_write_fasm", + "symbiflow_write_bitstream" + ] + _symbiflow_file_templates = { + **TemplatedPlatform.build_script_templates, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.pcf": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + set_io {{port_name}} {{pin_name}} + {% endfor %} + """, + "{{name}}.xdc": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + {% for attr_name, attr_value in attrs.items() -%} + set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }] + {% endfor %} + {% endfor %} + {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} + """, + "{{name}}.sdc": r""" + # {{autogenerated}} + {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} + {% if port_signal is none -%} + create_clock -period {{1000000000/frequency}} {{net_signal.name|ascii_escape}} + {% endif %} + {% endfor %} + """ + } + _symbiflow_command_templates = [ + r""" + {{invoke_tool("symbiflow_synth")}} + -t {{name}} + -v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v + -p {{platform._symbiflow_part}} + -d {{platform._symbiflow_bitstream_device}} + -x {{name}}.xdc + """, + r""" + {{invoke_tool("symbiflow_pack")}} + -e {{name}}.eblif + -d {{platform._symbiflow_device}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_place")}} + -e {{name}}.eblif + -p {{name}}.pcf + -n {{name}}.net + -P {{platform._symbiflow_part}} + -d {{platform._symbiflow_device}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_route")}} + -e {{name}}.eblif + -P {{platform._symbiflow_part}} + -d {{platform._symbiflow_device}} + -s {{name}}.sdc + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_write_fasm")}} + -e {{name}}.eblif + -P {{platform._symbiflow_part}} + -d {{platform._symbiflow_device}} + """, + r""" + {{invoke_tool("symbiflow_write_bitstream")}} + -f {{name}}.fasm + -p {{platform._symbiflow_part}} + -d {{platform._symbiflow_bitstream_device}} + -b {{name}}.bit + """ + ] + + # Yosys NextPNR prjxray templates + + @property + def _xray_part(self): + return { + "xc7a35ticsg324-1L": "xc7a35tcsg324-1", # Arty-A7 35t + "xc7a100ticsg324-1L": "xc7a100tcsg324-1", # Arty-A7 100t + }.get(self._part, self._part) + + @property + def _xray_device(self): + return { + "xc7a35ti": "xc7a35t", + "xc7a100ti": "xc7a100t", + }.get(self.device, self.device) + + @property + def _xray_family(self): + return { + "xc7a": "artix7", + "xc7z": "zynq7", + }[self.device[:4]] + + _xray_required_tools = [ + "yosys", + "nextpnr-xilinx", + "fasm2frames", + "xc7frames2bit" + ] + _xray_file_templates = { + **TemplatedPlatform.build_script_templates, + "build_{{name}}.sh": r""" + # {{autogenerated}} + set -e{{verbose("x")}} + {% for var in platform._all_toolchain_env_vars %} + [ -n "${{var}}" ] && . "${{var}}" + {% endfor %} + : ${DB_DIR:=/usr/share/nextpnr/prjxray-db} + : ${CHIPDB_DIR:=/usr/share/nextpnr/xilinx-chipdb} + {{emit_commands("sh")}} + """, + "{{name}}.v": r""" + /* {{autogenerated}} */ + {{emit_verilog()}} + """, + "{{name}}.debug.v": r""" + /* {{autogenerated}} */ + {{emit_debug_verilog()}} + """, + "{{name}}.xdc": r""" + # {{autogenerated}} + {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} + {% for attr_name, attr_value in attrs.items() -%} + set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}}] + set_property LOC {{pin_name}} [get_ports {{port_name|tcl_escape}}] + {% endfor %} + {% endfor %} + """, + } + _xray_command_templates = [ + r""" + {{invoke_tool("yosys")}} + -p "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {{name}}; write_json {{name}}.json" + {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} + {{name}}.v + """, + r""" + {{invoke_tool("nextpnr-xilinx")}} + --chipdb $CHIPDB_DIR/{{platform._xray_device}}.bin + --xdc {{name}}.xdc + --json {{name}}.json + --write {{name}}_routed.json + --fasm {{name}}.fasm + """, + r""" + {{invoke_tool("fasm2frames")}} + --part {{platform._xray_part}} + --db-root $DB_DIR/{{platform._xray_family}} {{name}}.fasm > {{name}}.frames + """, + r""" + {{invoke_tool("xc7frames2bit")}} + --part_file $DB_DIR/{{platform._xray_family}}/{{platform._xray_part}}/part.yaml + --part_name {{platform._xray_part}} + --frm_file {{name}}.frames + --output_file {{name}}.bit + """, + ] + + # Common logic + + def __init__(self, *, toolchain=None): + super().__init__() + + # Determine device family. + device = self.device.lower() + # Remove the prefix. + if device.startswith("xc"): + device = device[2:] + elif device.startswith("xa"): + device = device[2:] + elif device.startswith("xqr"): + device = device[3:] + elif device.startswith("xq"): + device = device[2:] + else: + raise ValueError("Device '{}' is not recognized".format(self.device)) + # Do actual name matching. + if device.startswith("2vp"): + self.family = "virtex2p" + elif device.startswith("2v"): + self.family = "virtex2" + elif device.startswith("3sd"): + self.family = "spartan3adsp" + elif device.startswith("3s"): + if device.endswith("a"): + self.family = "spartan3a" + elif device.endswith("e"): + self.family = "spartan3e" + else: + self.family = "spartan3" + elif device.startswith("4v"): + self.family = "virtex4" + elif device.startswith("5v"): + self.family = "virtex5" + elif device.startswith("6v"): + self.family = "virtex6" + elif device.startswith("6s"): + self.family = "spartan6" + elif device.startswith("7"): + self.family = "series7" + elif device.startswith(("vu", "ku")): + if device.endswith("p"): + self.family = "ultrascaleplus" + else: + self.family = "ultrascale" + elif device.startswith(("zu", "u", "k26", "au")): + self.family = "ultrascaleplus" + elif device.startswith(("v", "2s")): + # Match last to avoid conflict with ultrascale. + # Yes, Spartan 2 is the same thing as Virtex. + if device.endswith("e"): + self.family = "virtexe" + else: + self.family = "virtex" + + + ISE_FAMILIES = { + "virtex", "virtexe", + "virtex2", "virtex2p", + "spartan3", "spartan3e", "spartan3a", "spartan3adsp", + "virtex4", + "virtex5", + "virtex6", + "spartan6", + } + if toolchain is None: + if self.family in ISE_FAMILIES: + toolchain = "ISE" + else: + toolchain = "Vivado" + + assert toolchain in ("Vivado", "ISE", "Symbiflow", "Xray") + if toolchain == "Vivado": + if self.family in ISE_FAMILIES: + raise ValueError("Family '{}' is not supported by the Vivado toolchain, please use ISE instead".format(self.family)) + elif toolchain == "ISE": + if self.family not in ISE_FAMILIES and self.family != "series7": + raise ValueError("Family '{}' is not supported by the ISE toolchain, please use Vivado instead".format(self.family)) + elif toolchain == "Symbiflow": + if self.family != "series7": + raise ValueError("Family '{}' is not supported by the Symbiflow toolchain".format(self.family)) + elif toolchain == "Xray": + if self.family != "series7": + raise ValueError("Family '{}' is not supported by the yosys nextpnr toolchain".format(self.family)) + + self.toolchain = toolchain + + @property + def required_tools(self): + if self.toolchain == "Vivado": + return self._vivado_required_tools + if self.toolchain == "ISE": + return self._ise_required_tools + if self.toolchain == "Symbiflow": + return self._symbiflow_required_tools + if self.toolchain == "Xray": + return self._xray_required_tools + assert False + + @property + def file_templates(self): + if self.toolchain == "Vivado": + return self._vivado_file_templates + if self.toolchain == "ISE": + return self._ise_file_templates + if self.toolchain == "Symbiflow": + return self._symbiflow_file_templates + if self.toolchain == "Xray": + return self._xray_file_templates + assert False + + @property + def command_templates(self): + if self.toolchain == "Vivado": + return self._vivado_command_templates + if self.toolchain == "ISE": + return self._ise_command_templates + if self.toolchain == "Symbiflow": + return self._symbiflow_command_templates + if self.toolchain == "Xray": + return self._xray_command_templates + assert False + + def create_missing_domain(self, name): + # Xilinx devices have a global write enable (GWE) signal that asserted during configuration + # and deasserted once it ends. Because it is an asynchronous signal (GWE is driven by logic + # synchronous to configuration clock, which is not used by most designs), even though it is + # a low-skew global network, its deassertion may violate a setup/hold constraint with + # relation to a user clock. The recommended solution is to use a BUFGCE driven by the EOS + # signal (if available). For details, see: + # * https://www.xilinx.com/support/answers/44174.html + # * https://www.xilinx.com/support/documentation/white_papers/wp272.pdf + + STARTUP_PRIMITIVE = { + "spartan6": "STARTUP_SPARTAN6", + "virtex4": "STARTUP_VIRTEX4", + "virtex5": "STARTUP_VIRTEX5", + "virtex6": "STARTUP_VIRTEX6", + "series7": "STARTUPE2", + "ultrascale": "STARTUPE3", + "ultrascaleplus": "STARTUPE3", + } + + if self.family not in STARTUP_PRIMITIVE or not self.vendor_toolchain: + # Spartan 3 and before lacks a STARTUP primitive with EOS output; use a simple ResetSynchronizer + # in that case, as is the default. + # Symbiflow does not support the STARTUPE2 primitive. + return super().create_missing_domain(name) + + if name == "sync" and self.default_clk is not None: + clk_i = self.request(self.default_clk).i + if self.default_rst is not None: + rst_i = self.request(self.default_rst).i + + m = Module() + ready = Signal() + m.submodules += Instance(STARTUP_PRIMITIVE[self.family], o_EOS=ready) + m.domains += ClockDomain("sync", reset_less=self.default_rst is None) + if self.toolchain != "Vivado": + m.submodules += Instance("BUFGCE", i_CE=ready, i_I=clk_i, o_O=ClockSignal("sync")) + elif self.family == "series7": + # Actually use BUFGCTRL configured as BUFGCE, since using BUFGCE causes + # sim/synth mismatches with Vivado 2019.2, and the suggested workaround + # (SIM_DEVICE parameter) breaks Vivado 2017.4. + m.submodules += Instance("BUFGCTRL", + p_SIM_DEVICE="7SERIES", + i_I0=clk_i, i_S0=C(1, 1), i_CE0=ready, i_IGNORE0=C(0, 1), + i_I1=C(1, 1), i_S1=C(0, 1), i_CE1=C(0, 1), i_IGNORE1=C(1, 1), + o_O=ClockSignal("sync") + ) + else: + m.submodules += Instance("BUFGCE", + p_SIM_DEVICE="ULTRASCALE", + i_CE=ready, + i_I=clk_i, + o_O=ClockSignal("sync") + ) + if self.default_rst is not None: + m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") + return m + + def add_clock_constraint(self, clock, frequency): + super().add_clock_constraint(clock, frequency) + clock.attrs["keep"] = "TRUE" + + def _get_xdr_buffer(self, m, pin, iostd, *, i_invert=False, o_invert=False): + XFDDR_FAMILIES = { + "virtex2", + "virtex2p", + "spartan3", + } + XDDR2_FAMILIES = { + "spartan3e", + "spartan3a", + "spartan3adsp", + "spartan6", + } + XDDR_FAMILIES = { + "virtex4", + "virtex5", + "virtex6", + "series7", + } + XDDRE1_FAMILIES = { + "ultrascale", + "ultrascaleplus", + } + + def get_iob_dff(clk, d, q): + # SDR I/O is performed by packing a flip-flop into the pad IOB. + for bit in range(len(q)): + m.submodules += Instance("FDCE", + a_IOB="TRUE", + i_C=clk, + i_CE=Const(1), + i_CLR=Const(0), + i_D=d[bit], + o_Q=q[bit] + ) + + def get_dff(clk, d, q): + for bit in range(len(q)): + m.submodules += Instance("FDCE", + i_C=clk, + i_CE=Const(1), + i_CLR=Const(0), + i_D=d[bit], + o_Q=q[bit] + ) + + def get_ifddr(clk, io, q0, q1): + assert self.family in XFDDR_FAMILIES + for bit in range(len(q0)): + m.submodules += Instance("IFDDRCPE", + i_C0=clk, i_C1=~clk, + i_CE=Const(1), + i_CLR=Const(0), i_PRE=Const(0), + i_D=io[bit], + o_Q0=q0[bit], o_Q1=q1[bit] + ) + + def get_iddr2(clk, d, q0, q1, alignment): + assert self.family in XDDR2_FAMILIES + for bit in range(len(q0)): + m.submodules += Instance("IDDR2", + p_DDR_ALIGNMENT=alignment, + p_SRTYPE="ASYNC", + p_INIT_Q0=C(0, 1), p_INIT_Q1=C(0, 1), + i_C0=clk, i_C1=~clk, + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D=d[bit], + o_Q0=q0[bit], o_Q1=q1[bit] + ) + + def get_iddr(clk, d, q1, q2): + assert self.family in XDDR_FAMILIES or self.family in XDDRE1_FAMILIES + for bit in range(len(q1)): + if self.family in XDDR_FAMILIES: + m.submodules += Instance("IDDR", + p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", + p_SRTYPE="ASYNC", + p_INIT_Q1=C(0, 1), p_INIT_Q2=C(0, 1), + i_C=clk, + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D=d[bit], + o_Q1=q1[bit], o_Q2=q2[bit] + ) + else: + m.submodules += Instance("IDDRE1", + p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", + p_IS_C_INVERTED=C(0, 1), p_IS_CB_INVERTED=C(1, 1), + i_C=clk, i_CB=clk, + i_R=Const(0), + i_D=d[bit], + o_Q1=q1[bit], o_Q2=q2[bit] + ) + + def get_fddr(clk, d0, d1, q): + for bit in range(len(q)): + if self.family in XFDDR_FAMILIES: + m.submodules += Instance("FDDRCPE", + i_C0=clk, i_C1=~clk, + i_CE=Const(1), + i_PRE=Const(0), i_CLR=Const(0), + i_D0=d0[bit], i_D1=d1[bit], + o_Q=q[bit] + ) + else: + m.submodules += Instance("ODDR2", + p_DDR_ALIGNMENT="NONE", + p_SRTYPE="ASYNC", + p_INIT=C(0, 1), + i_C0=clk, i_C1=~clk, + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D0=d0[bit], i_D1=d1[bit], + o_Q=q[bit] + ) + + def get_oddr(clk, d1, d2, q): + for bit in range(len(q)): + if self.family in XDDR2_FAMILIES: + m.submodules += Instance("ODDR2", + p_DDR_ALIGNMENT="C0", + p_SRTYPE="ASYNC", + p_INIT=C(0, 1), + i_C0=clk, i_C1=~clk, + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D0=d1[bit], i_D1=d2[bit], + o_Q=q[bit] + ) + elif self.family in XDDR_FAMILIES: + m.submodules += Instance("ODDR", + p_DDR_CLK_EDGE="SAME_EDGE", + p_SRTYPE="ASYNC", + p_INIT=C(0, 1), + i_C=clk, + i_CE=Const(1), + i_S=Const(0), i_R=Const(0), + i_D1=d1[bit], i_D2=d2[bit], + o_Q=q[bit] + ) + elif self.family in XDDRE1_FAMILIES: + m.submodules += Instance("ODDRE1", + p_SRVAL=C(0, 1), + i_C=clk, + i_SR=Const(0), + i_D1=d1[bit], i_D2=d2[bit], + o_Q=q[bit] + ) + + def get_ineg(y, invert): + if invert: + a = Signal.like(y, name_suffix="_n") + m.d.comb += y.eq(~a) + return a + else: + return y + + def get_oneg(a, invert): + if invert: + y = Signal.like(a, name_suffix="_n") + m.d.comb += y.eq(~a) + return y + else: + return a + + if "i" in pin.dir: + if pin.xdr < 2: + pin_i = get_ineg(pin.i, i_invert) + elif pin.xdr == 2: + pin_i0 = get_ineg(pin.i0, i_invert) + pin_i1 = get_ineg(pin.i1, i_invert) + if "o" in pin.dir: + if pin.xdr < 2: + pin_o = get_oneg(pin.o, o_invert) + elif pin.xdr == 2: + pin_o0 = get_oneg(pin.o0, o_invert) + pin_o1 = get_oneg(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: + if "i" in pin.dir: + get_iob_dff(pin.i_clk, i, pin_i) + if "o" in pin.dir: + get_iob_dff(pin.o_clk, pin_o, o) + if pin.dir in ("oe", "io"): + get_iob_dff(pin.o_clk, ~pin.oe, t) + elif pin.xdr == 2: + # On Spartan 3E/3A, the situation with DDR registers is messy: while the hardware + # supports same-edge alignment, it does so by borrowing the resources of the other + # pin in the differential pair (if any). Since we cannot be sure if the other pin + # is actually unused (or if the pin is even part of a differential pair in the first + # place), we only use the hardware alignment feature in two cases: + # + # - differential inputs (since the other pin's input registers will be unused) + # - true differential outputs (since they use only one pin's output registers, + # as opposed to pseudo-differential outputs that use both) + TRUE_DIFF_S3EA = { + "LVDS_33", "LVDS_25", + "MINI_LVDS_33", "MINI_LVDS_25", + "RSDS_33", "RSDS_25", + "PPDS_33", "PPDS_25", + "TMDS_33", + } + DIFF_S3EA = TRUE_DIFF_S3EA | { + "DIFF_HSTL_I", + "DIFF_HSTL_III", + "DIFF_HSTL_I_18", + "DIFF_HSTL_II_18", + "DIFF_HSTL_III_18", + "DIFF_SSTL3_I", + "DIFF_SSTL3_II", + "DIFF_SSTL2_I", + "DIFF_SSTL2_II", + "DIFF_SSTL18_I", + "DIFF_SSTL18_II", + "BLVDS_25", + } + if "i" in pin.dir: + if self.family in XFDDR_FAMILIES: + # First-generation input DDR register: basically just two FFs with opposite + # clocks. Add a register on both outputs, so that they enter fabric on + # the same clock edge, adding one cycle of latency. + i0_ff = Signal.like(pin_i0, name_suffix="_ff") + i1_ff = Signal.like(pin_i1, name_suffix="_ff") + get_dff(pin.i_clk, i0_ff, pin_i0) + get_dff(pin.i_clk, i1_ff, pin_i1) + get_iob_dff(pin.i_clk, i, i0_ff) + get_iob_dff(~pin.i_clk, i, i1_ff) + elif self.family in XDDR2_FAMILIES: + if self.family == 'spartan6' or iostd in DIFF_S3EA: + # Second-generation input DDR register: hw realigns i1 to positive clock edge, + # but also misaligns it with i0 input. Re-register first input before it + # enters fabric. This allows both inputs to enter fabric on the same clock + # edge, and adds one cycle of latency. + i0_ff = Signal.like(pin_i0, name_suffix="_ff") + get_dff(pin.i_clk, i0_ff, pin_i0) + get_iddr2(pin.i_clk, i, i0_ff, pin_i1, "C0") + else: + # No extra register available for hw alignment, use extra registers. + i0_ff = Signal.like(pin_i0, name_suffix="_ff") + i1_ff = Signal.like(pin_i1, name_suffix="_ff") + get_dff(pin.i_clk, i0_ff, pin_i0) + get_dff(pin.i_clk, i1_ff, pin_i1) + get_iddr2(pin.i_clk, i, i0_ff, i1_ff, "NONE") + else: + # Third-generation input DDR register: does all of the above on its own. + get_iddr(pin.i_clk, i, pin_i0, pin_i1) + if "o" in pin.dir: + if self.family in XFDDR_FAMILIES or self.family == "spartan3e" or (self.family.startswith("spartan3a") and iostd not in TRUE_DIFF_S3EA): + # For this generation, we need to realign o1 input ourselves. + o1_ff = Signal.like(pin_o1, name_suffix="_ff") + get_dff(pin.o_clk, pin_o1, o1_ff) + get_fddr(pin.o_clk, pin_o0, o1_ff, o) + else: + get_oddr(pin.o_clk, pin_o0, pin_o1, o) + if pin.dir in ("oe", "io"): + if self.family == "spartan6": + get_oddr(pin.o_clk, ~pin.oe, ~pin.oe, t) + else: + get_iob_dff(pin.o_clk, ~pin.oe, t) + else: + assert False + + return (i, o, t) + + def _get_valid_xdrs(self): + if self.family in {"virtex", "virtexe"}: + return (0, 1) + else: + return (0, 1, 2) + + def get_input(self, pin, port, attrs, invert): + self._check_feature("single-ended input", pin, attrs, + valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF", + i_I=port.io[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=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert) + if self.vendor_toolchain: + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF", + i_I=o[bit], + o_O=port.io[bit] + ) + else: + m.d.comb += port.eq(self._invert_if(invert, o)) + return m + + def get_tristate(self, pin, port, attrs, invert): + if not self.vendor_toolchain: + return super().get_tristate(pin, port, attrs, invert) + + self._check_feature("single-ended tristate", pin, attrs, + valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFT", + i_T=t, + i_I=o[bit], + o_O=port.io[bit] + ) + return m + + def get_input_output(self, pin, port, attrs, invert): + if not self.vendor_toolchain: + return super().get_input_output(pin, port, attrs, invert) + + self._check_feature("single-ended input/output", pin, attrs, + valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF", + i_T=t, + i_I=o[bit], + o_O=i[bit], + io_IO=port.io[bit] + ) + return m + + def get_diff_input(self, pin, port, attrs, invert): + if not self.vendor_toolchain: + return super().get_diff_input(pin, port, attrs, invert) + + self._check_feature("differential input", pin, attrs, + valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUFDS", + i_I=port.p[bit], i_IB=port.n[bit], + o_O=i[bit] + ) + return m + + def get_diff_output(self, pin, port, attrs, invert): + if not self.vendor_toolchain: + return super().get_diff_output(pin, port, attrs, invert) + + self._check_feature("differential output", pin, attrs, + valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFDS", + i_I=o[bit], + o_O=port.p[bit], o_OB=port.n[bit] + ) + return m + + def get_diff_tristate(self, pin, port, attrs, invert): + if not self.vendor_toolchain: + return super().get_diff_tristate(pin, port, attrs, invert) + + self._check_feature("differential tristate", pin, attrs, + valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFTDS", + i_T=t, + i_I=o[bit], + o_O=port.p[bit], o_OB=port.n[bit] + ) + return m + + def get_diff_input_output(self, pin, port, attrs, invert): + if not self.vendor_toolchain: + return super().get_diff_input_output(pin, port, attrs, invert) + + self._check_feature("differential input/output", pin, attrs, + valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) + m = Module() + i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert, o_invert=invert) + for bit in range(pin.width): + m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUFDS", + i_T=t, + i_I=o[bit], + o_O=i[bit], + io_IO=port.p[bit], io_IOB=port.n[bit] + ) + return m + + # The synchronizer implementations below apply two separate but related timing constraints. + # + # First, the ASYNC_REG attribute prevents inference of shift registers from synchronizer FFs, + # and constraints the FFs to be placed as close as possible, ideally in one CLB. This attribute + # only affects the synchronizer FFs themselves. + # + # Second, for Vivado only, the amaranth.vivado.false_path or amaranth.vivado.max_delay attribute + # affects the path into the synchronizer. If maximum input delay is specified, a datapath-only + # maximum delay constraint is applied, limiting routing delay (and therefore skew) at + # the synchronizer input. Otherwise, a false path constraint is used to omit the input path + # from the timing analysis. + + def get_ff_sync(self, ff_sync): + m = Module() + flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index), + reset=ff_sync._reset, reset_less=ff_sync._reset_less, + attrs={"ASYNC_REG": "TRUE"}) + for index in range(ff_sync._stages)] + if self.toolchain == "Vivado": + if ff_sync._max_input_delay is None: + flops[0].attrs["amaranth.vivado.false_path"] = "TRUE" + else: + flops[0].attrs["amaranth.vivado.max_delay"] = str(ff_sync._max_input_delay * 1e9) + elif ff_sync._max_input_delay is not None: + raise NotImplementedError("Platform '{}' does not support constraining input delay " + "for FFSynchronizer" + .format(type(self).__name__)) + for i, o in zip((ff_sync.i, *flops), flops): + m.d[ff_sync._o_domain] += o.eq(i) + m.d.comb += ff_sync.o.eq(flops[-1]) + return m + + + def get_async_ff_sync(self, async_ff_sync): + m = Module() + m.domains += ClockDomain("async_ff", async_reset=True, local=True) + flops = [Signal(1, name="stage{}".format(index), reset=1, + attrs={"ASYNC_REG": "TRUE"}) + for index in range(async_ff_sync._stages)] + if self.toolchain == "Vivado": + if async_ff_sync._max_input_delay is None: + flops[0].attrs["amaranth.vivado.false_path"] = "TRUE" + else: + flops[0].attrs["amaranth.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9) + elif async_ff_sync._max_input_delay is not None: + raise NotImplementedError("Platform '{}' does not support constraining input delay " + "for AsyncFFSynchronizer" + .format(type(self).__name__)) + for i, o in zip((0, *flops), flops): + m.d.async_ff += o.eq(i) + + if async_ff_sync._edge == "pos": + m.d.comb += ResetSignal("async_ff").eq(async_ff_sync.i) + else: + m.d.comb += ResetSignal("async_ff").eq(~async_ff_sync.i) + + m.d.comb += [ + ClockSignal("async_ff").eq(ClockSignal(async_ff_sync._o_domain)), + async_ff_sync.o.eq(flops[-1]) + ] + + return m diff --git a/amaranth/vendor/gowin.py b/amaranth/vendor/gowin.py index 02a58d8..b8f86a2 100644 --- a/amaranth/vendor/gowin.py +++ b/amaranth/vendor/gowin.py @@ -1,606 +1,13 @@ -from abc import abstractproperty -from fractions import Fraction -import re +# TODO(amaranth-0.5): remove module -from ..hdl import * -from ..lib.cdc import ResetSynchronizer -from ..build import * +import warnings +import importlib +from .. import vendor -# Acknowledgments: -# Parts of this file originate from https://github.com/tcjie/Gowin - -__all__ = ["GowinPlatform"] - - -class GowinPlatform(TemplatedPlatform): - """ - .. rubric:: Apicula toolchain - - Required tools: - * ``yosys`` - * ``nextpnr-gowin`` - * ``gowin_pack`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_APICULA``, if present. - - Build products: - * ``{{name}}.fs``: binary bitstream. - - .. rubric:: Gowin toolchain - - Required tools: - * ``gw_sh`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_GOWIN``, if present. - - Build products: - * ``{{name}}.fs``: binary bitstream. - """ - - toolchain = None # selected when creating platform - - part = abstractproperty() - family = abstractproperty() - - def parse_part(self): - # These regular expressions match all >900 parts of Gowin device_info.csv - reg_series = r"(GW[12]{1}[AN]{1}[EFNRSZ]{0,3})-" - reg_voltage = r"(ZV|EV|LV|LX|UV|UX)" - reg_size = r"(1|2|4|9|18|55)" - reg_subseries = r"(?:(B|C|S|X|P5)?)" - reg_package = r"((?:PG|UG|EQ|LQ|MG|M|QN|CS|FN)(?:\d+)(?:P?)(?:A|E|M|CF|C|D|G|H|F|S|T|U|X)?)" - reg_speed = r"((?:C\d{1}/I\d{1})|ES|A\d{1}|I\d{1})" - - match = re.match(reg_series+reg_voltage+reg_size+reg_subseries+reg_package+reg_speed+"$", - self.part) - if not match: - raise ValueError("Supplied part name is invalid") - - self.series = match.group(1) - self.voltage = match.group(2) - self.size = match.group(3) - self.subseries = match.group(4) or "" - self.package = match.group(5) - self.speed = match.group(6) - - match = re.match(reg_series+reg_size+reg_subseries+"$", self.family) - if not match: - raise ValueError("Supplied device family name is invalid") - - self.series_f = match.group(1) - self.size_f = match.group(2) - self.subseries_f = match.group(3) or "" - - # subseries_f is usually more reliable than subseries. - - if self.series != self.series_f: - raise ValueError("Series extracted from supplied part name does not match " - "supplied family series") - if self.size != self.size_f: - raise ValueError("Size extracted from supplied part name does not match " - "supplied family size") - - # _chipdb_device is tied to available chipdb-*.bin files of nextpnr-gowin - @property - def _chipdb_device(self): - # GW1NR series does not have its own chipdb file, but works with GW1N - if self.series == "GW1NR": - return "GW1N-{}{}".format(self.size, self.subseries_f) - return self.family - - _dev_osc_mapping = { - "GW1N-1" : "OSCH", - "GW1N-1P5" : "OSCO", - "GW1N-1P5B" : "OSCO", - "GW1N-1S" : "OSCH", - "GW1N-2" : "OSCO", - "GW1N-2B" : "OSCO", - "GW1N-4" : "OSC", - "GW1N-4B" : "OSC", - "GW1N-9" : "OSC", - "GW1N-9C" : "OSC", - "GW1NR-1" : "OSCH", - "GW1NR-2" : "OSCO", - "GW1NR-2B" : "OSCO", - "GW1NR-4" : "OSC", - "GW1NR-4B" : "OSC", - "GW1NR-9" : "OSC", - "GW1NR-9C" : "OSC", - "GW1NRF-4B" : "OSC", - "GW1NS-2" : "OSCF", - "GW1NS-2C" : "OSCF", - "GW1NS-4" : "OSCZ", - "GW1NS-4C" : "OSCZ", - "GW1NSE-2C" : "OSCF", - "GW1NSER-4C" : "OSCZ", - "GW1NSR-2" : "OSCF", - "GW1NSR-2C" : "OSCF", - "GW1NSR-4" : "OSCZ", - "GW1NSR-4C" : "OSCZ", - "GW1NZ-1" : "OSCZ", - "GW1NZ-1C" : "OSCZ", - "GW2A-18" : "OSC", - "GW2A-18C" : "OSC", - "GW2A-55" : "OSC", - "GW2A-55C" : "OSC", - "GW2AN-18X" : "OSCW", - "GW2AN-55C" : "OSC", - "GW2AN-9X" : "OSCW", - "GW2ANR-18C" : "OSC", - "GW2AR-18" : "OSC", - "GW2AR-18C" : "OSC" - } - - @property - def _osc_type(self): - if self.family in self._dev_osc_mapping: - return self._dev_osc_mapping[self.family] - raise NotImplementedError("Device family {} does not have an assigned oscillator type" - .format(self.family)) - - @property - def _osc_base_freq(self): - osc = self._osc_type - if osc == "OSC": - if self.speed == 4 and self.subseries_f in ("B", "D"): - return 210_000_000 - else: - return 250_000_000 - elif osc in ("OSCZ", "OSCO"): - if self.series == "GW1NSR" and self.speed == "C7/I6": - return 260_000_000 - else: - return 250_000_000 - elif osc in ("OSCF", "OSCH"): - return 240_000_000 - elif osc == "OSCW": - return 200_000_000 - else: - assert False - - @property - def _osc_div(self): - div_min = 2 - div_max = 128 - div_step = 2 - div_frac = Fraction(self._osc_base_freq, self.osc_frequency) - - if div_frac.denominator != 1 or div_frac not in range(div_min, div_max, div_step): - raise ValueError( - "On-chip oscillator frequency (platform.osc_frequency) must be chosen such that " - "the oscillator divider, calculated as ({}/{}), is an integer between {} and {} in " - "steps of {}" - .format(div_frac.numerator, div_frac.denominator, div_min, div_max, div_step)) - - return div_frac.numerator - - # Common templates - - _common_file_templates = { - "{{name}}.cst": r""" - // {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - IO_LOC "{{port_name}}" {{pin_name}}; - {% for attr_name, attr_value in attrs.items() -%} - IO_PORT "{{port_name}}" {{attr_name}}={{attr_value}}; - {% endfor %} - {% endfor %} - """, - } - - # Apicula templates - - _apicula_required_tools = [ - "yosys", - "nextpnr-gowin", - "gowin_pack" - ] - _apicula_file_templates = { - **TemplatedPlatform.build_script_templates, - **_common_file_templates, - "{{name}}.il": r""" - # {{autogenerated}} - {{emit_rtlil()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.ys": r""" - # {{autogenerated}} - {% for file in platform.iter_files(".v") -%} - read_verilog {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".sv") -%} - read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".il") -%} - read_ilang {{file}} - {% endfor %} - read_ilang {{name}}.il - delete w:$verilog_initial_trigger - {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} - synth_gowin {{get_override("synth_opts")|options}} -top {{name}} -json {{name}}.syn.json - {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} - """, - } - _apicula_command_templates = [ - r""" - {{invoke_tool("yosys")}} - {{quiet("-q")}} - {{get_override("yosys_opts")|options}} - -l {{name}}.rpt - {{name}}.ys - """, - r""" - {{invoke_tool("nextpnr-gowin")}} - {{quiet("--quiet")}} - {{get_override("nextpnr_opts")|options}} - --log {{name}}.tim - --device {{platform.part}} - --family {{platform._chipdb_device}} - --json {{name}}.syn.json - --cst {{name}}.cst - --write {{name}}.pnr.json - """, - r""" - {{invoke_tool("gowin_pack")}} - -d {{platform._chipdb_device}} - -o {{name}}.fs - {{get_override("gowin_pack_opts")|options}} - {{name}}.pnr.json - """ - ] - - # Vendor toolchain templates - - _gowin_required_tools = ["gw_sh"] - _gowin_file_templates = { - **TemplatedPlatform.build_script_templates, - **_common_file_templates, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.tcl": r""" - # {{autogenerated}} - {% for file in platform.iter_files(".v",".sv",".vhd",".vhdl") -%} - add_file {{file}} - {% endfor %} - add_file -type verilog {{name}}.v - add_file -type cst {{name}}.cst - add_file -type sdc {{name}}.sdc - set_device -name {{platform.family}} {{platform.part}} - set_option -verilog_std v2001 -print_all_synthesis_warning 1 -show_all_warn 1 - {{get_override("add_options")|default("# (add_options placeholder)")}} - run all - file delete -force {{name}}.fs - file copy -force impl/pnr/project.fs {{name}}.fs - """, - "{{name}}.sdc": r""" - // {{autogenerated}} - {% for net_signal,port_signal,frequency in platform.iter_clock_constraints() -%} - create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}] - {% endfor %} - {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} - """, - } - _gowin_command_templates = [ - r""" - {{invoke_tool("gw_sh")}} - {{name}}.tcl - """ - ] - - def __init__(self, *, toolchain="Apicula"): - super().__init__() - - assert toolchain in ("Apicula", "Gowin") - self.toolchain = toolchain - - self.parse_part() - - @property - def required_tools(self): - if self.toolchain == "Apicula": - return self._apicula_required_tools - elif self.toolchain == "Gowin": - return self._gowin_required_tools - assert False - - @property - def file_templates(self): - if self.toolchain == "Apicula": - return self._apicula_file_templates - elif self.toolchain == "Gowin": - return self._gowin_file_templates - assert False - - @property - def command_templates(self): - if self.toolchain == "Apicula": - return self._apicula_command_templates - elif self.toolchain == "Gowin": - return self._gowin_command_templates - assert False - - def add_clock_constraint(self, clock, frequency): - super().add_clock_constraint(clock, frequency) - clock.attrs["keep"] = "true" - - @property - def default_clk_constraint(self): - if self.default_clk == "OSC": - if not hasattr(self, "osc_frequency"): - raise AttributeError( - "Using the on-chip oscillator as the default clock source requires " - "the platform.osc_frequency attribute to be set") - return Clock(self.osc_frequency) - - # Use the defined Clock resource. - return super().default_clk_constraint - - def create_missing_domain(self, name): - if name == "sync" and self.default_clk is not None: - m = Module() - - if self.default_clk == "OSC": - clk_i = Signal() - if self._osc_type == "OSCZ": - m.submodules += Instance(self._osc_type, - p_FREQ_DIV=self._osc_div, - i_OSCEN=Const(1), - o_OSCOUT=clk_i) - elif self._osc_type == "OSCO": - # TODO: Make use of regulator configurable - m.submodules += Instance(self._osc_type, - p_REGULATOR_EN=Const(1), - p_FREQ_DIV=self._osc_div, - i_OSCEN=Const(1), - o_OSCOUT=clk_i) - elif self._osc_type == "OSCF": - m.submodules += Instance(self._osc_type, - p_FREQ_DIV=self._osc_div, - o_OSCOUT30M=None, - o_OSCOUT=clk_i) - else: - m.submodules += Instance(self._osc_type, - p_FREQ_DIV=self._osc_div, - o_OSCOUT=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) - - m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") - m.domains += ClockDomain("sync") - m.d.comb += ClockSignal("sync").eq(clk_i) - - return m - - def _get_xdr_buffer(self, m, pin, i_invert=False, o_invert=False): - - def get_ireg(clk,d,q): - for bit in range(len(q)): - m.submodules += Instance("DFF", - i_CLK=clk, - i_D=d[bit], - o_Q=q[bit], - ) - - def get_oreg(clk,d,q): - for bit in range(len(q)): - m.submodules += Instance("DFF", - i_CLK=clk, - i_D=d[bit], - o_Q=q[bit] - ) - - def get_iddr(clk,d,q0,q1): - for bit in range(len(d)): - m.submodules += Instance("IDDR", - i_CLK=clk, - i_D=d[bit], - o_Q0=q0[bit], - o_Q1=q1[bit] - ) - - def get_oddr(clk,d0,d1,q): - for bit in range(len(q)): - m.submodules += Instance("ODDR", - p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK - i_CLK=clk, - i_D0=d0[bit], - i_D1=d1[bit], - o_Q0=q[bit] - ) - - def get_oeddr(clk,d0,d1,tx,q0,q1): - for bit in range(len(q0)): - m.submodules += Instance("ODDR", - p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK - i_CLK=clk, - i_D0=d0[bit], - i_D1=d1[bit], - i_TX=tx, - o_Q0=q0[bit], - o_Q1=q1 - ) - - def get_ineg(y, invert): - if invert: - a = Signal.like(y, name_suffix="_n") - m.d.comb += y.eq(~a) - return a - else: - return y - - def get_oneg(a, invert): - if invert: - y = Signal.like(a, name_suffix="_n") - m.d.comb += y.eq(~a) - return y - else: - return a - - - if "i" in pin.dir: - if pin.xdr < 2: - pin_i = get_ineg(pin.i, i_invert) - elif pin.xdr == 2: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - if "o" in pin.dir: - if pin.xdr < 2: - pin_o = get_oneg(pin.o, o_invert) - elif pin.xdr == 2: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(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: - 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 pin.dir in ("o",): - get_oddr(pin.o_clk, pin_o0, pin_o1, o) - if pin.dir in ("oe", "io"): - get_oeddr(pin.o_clk, pin_o0, pin_o1, ~pin.oe, o, 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() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF", - i_I=port.io[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, port.io, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF", - i_I=o[bit], - o_O=port.io[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=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("TBUF", - i_OEN=t, - i_I=o[bit], - o_O=port.io[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=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF", - i_OEN=t, - i_I=o[bit], - o_O=i[bit], - io_IO=port.io[bit] - ) - return m - - def get_diff_input(self, pin, 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=invert) - for bit in range(pin.wodth): - m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IBUF", - i_I=port.p[bit], - i_IB=port.n[bit], - o_O=i[bit] - ) - return m - - def get_diff_output(self, pin, 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=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_OBUF", - i_I=o[bit], - o_O=port.p[bit], - o_OB=port.n[bit], - ) - return m - - def get_diff_tristate(self, pin, 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=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_TBUF", - i_OEN=t, - i_I=o[bit], - o_O=port.p[bit], - o_OB=port.n[bit] - ) - return m - - def get_diff_input_output(self, pin, port, atttr, 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=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IOBUF", - i_OEN=t, - i_I=o[bit], - o_O=i[bit], - io_IO=port.p[bit], - io_IOB=port.n[bit] - ) - return m +def __getattr__(name): + if name in ("GowinPlatform",): + warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}", + DeprecationWarning, stacklevel=2) + return getattr(vendor, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/amaranth/vendor/intel.py b/amaranth/vendor/intel.py index 83d127b..617e627 100644 --- a/amaranth/vendor/intel.py +++ b/amaranth/vendor/intel.py @@ -1,571 +1,13 @@ -from abc import abstractmethod +# TODO(amaranth-0.5): remove module -from ..hdl import * -from ..build import * +import warnings +import importlib +from .. import vendor -__all__ = ["IntelPlatform"] - - -class IntelPlatform(TemplatedPlatform): - """ - .. rubric:: Quartus toolchain - - Required tools: - * ``quartus_map`` - * ``quartus_fit`` - * ``quartus_asm`` - * ``quartus_sta`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_QUARTUS``, if present. - - Available overrides: - * ``add_settings``: inserts commands at the end of the QSF file. - * ``add_constraints``: inserts commands at the end of the SDC file. - * ``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. - - - .. rubric:: Mistral toolchain - - Required tools: - * ``yosys`` - * ``nextpnr-mistral`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_MISTRAL``, if present. - - * ``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_intel_alm`` Yosys command. - * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script. - * ``script_after_synth``: inserts commands after ``synth_intel_alm`` in Yosys script. - * ``yosys_opts``: adds extra options for ``yosys``. - * ``nextpnr_opts``: adds extra options for ``nextpnr-mistral``. - """ - - toolchain = None # selected when creating platform - - device = property(abstractmethod(lambda: None)) - package = property(abstractmethod(lambda: None)) - speed = property(abstractmethod(lambda: None)) - suffix = "" - - # Quartus templates - - quartus_suppressed_warnings = [ - 10264, # All case item expressions in this case statement are onehot - 10270, # Incomplete Verilog case statement has no default case item - 10335, # Unrecognized synthesis attribute - 10763, # Verilog case statement has overlapping case item expressions with non-constant or don't care bits - 10935, # Verilog casex/casez overlaps with a previous casex/vasez item expression - 12125, # Using design file which is not specified as a design file for the current project, but contains definitions used in project - 18236, # Number of processors not specified in QSF - 292013, # Feature is only available with a valid subscription license - ] - - quartus_required_tools = [ - "quartus_map", - "quartus_fit", - "quartus_asm", - "quartus_sta", - ] - - quartus_file_templates = { - **TemplatedPlatform.build_script_templates, - "build_{{name}}.sh": r""" - # {{autogenerated}} - {% for var in platform._all_toolchain_env_vars %} - if [ -n "${{var}}" ]; then - QUARTUS_ROOTDIR=$(dirname $(dirname "${{var}}")) - # Quartus' qenv.sh does not work with `set -e`. - . "${{var}}" - fi - {% endfor %} - set -e{{verbose("x")}} - {{emit_commands("sh")}} - """, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.qsf": r""" - # {{autogenerated}} - {% if get_override("nproc") -%} - set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}} - {% endif %} - - {% for file in platform.iter_files(".v") -%} - set_global_assignment -name VERILOG_FILE {{file|tcl_quote}} - {% endfor %} - {% for file in platform.iter_files(".sv") -%} - set_global_assignment -name SYSTEMVERILOG_FILE {{file|tcl_quote}} - {% endfor %} - {% for file in platform.iter_files(".vhd", ".vhdl") -%} - set_global_assignment -name VHDL_FILE {{file|tcl_quote}} - {% 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, attrs in platform.iter_port_constraints_bits() -%} - set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}} - {% for key, value in attrs.items() -%} - set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}} - {% endfor %} - {% endfor %} - - set_global_assignment -name GENERATE_RBF_FILE ON - - {{get_override("add_settings")|default("# (add_settings 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_quote}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_quote}}] - {% else -%} - create_clock -name {{net_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("|")|tcl_quote}}] - {% endif %} - {% endfor %} - {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} - """, - "{{name}}.srf": r""" - {% for warning in platform.quartus_suppressed_warnings %} - { "" "" "" "{{name}}.v" { } { } 0 {{warning}} "" 0 0 "Design Software" 0 -1 0 ""} - {% endfor %} - """, - } - quartus_command_templates = [ - r""" - {{invoke_tool("quartus_map")}} - {{get_override("quartus_map_opts")|options}} - --rev={{name}} {{name}} - """, - r""" - {{invoke_tool("quartus_fit")}} - {{get_override("quartus_fit_opts")|options}} - --rev={{name}} {{name}} - """, - r""" - {{invoke_tool("quartus_asm")}} - {{get_override("quartus_asm_opts")|options}} - --rev={{name}} {{name}} - """, - r""" - {{invoke_tool("quartus_sta")}} - {{get_override("quartus_sta_opts")|options}} - --rev={{name}} {{name}} - """, - ] - - - # Mistral templates - - mistral_required_tools = [ - "yosys", - "nextpnr-mistral" - ] - mistral_file_templates = { - **TemplatedPlatform.build_script_templates, - "{{name}}.il": r""" - # {{autogenerated}} - {{emit_rtlil()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.ys": r""" - # {{autogenerated}} - {% for file in platform.iter_files(".v") -%} - read_verilog {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".sv") -%} - read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".il") -%} - read_ilang {{file}} - {% endfor %} - read_ilang {{name}}.il - delete w:$verilog_initial_trigger - {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} - synth_intel_alm {{get_override("synth_opts")|options}} -top {{name}} - {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} - write_json {{name}}.json - """, - "{{name}}.qsf": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}} - {% for key, value in attrs.items() -%} - set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}} - {% endfor %} - {% endfor %} - """, - - } - mistral_command_templates = [ - r""" - {{invoke_tool("yosys")}} - {{quiet("-q")}} - {{get_override("yosys_opts")|options}} - -l {{name}}.rpt - {{name}}.ys - """, - r""" - {{invoke_tool("nextpnr-mistral")}} - {{quiet("--quiet")}} - {{get_override("nextpnr_opts")|options}} - --log {{name}}.tim - --device {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}} - --json {{name}}.json - --qsf {{name}}.qsf - --rbf {{name}}.rbf - """ - ] - - # Common logic - - def __init__(self, *, toolchain="Quartus"): - super().__init__() - - assert toolchain in ("Quartus", "Mistral") - self.toolchain = toolchain - - @property - def required_tools(self): - if self.toolchain == "Quartus": - return self.quartus_required_tools - if self.toolchain == "Mistral": - return self.mistral_required_tools - assert False - - @property - def file_templates(self): - if self.toolchain == "Quartus": - return self.quartus_file_templates - if self.toolchain == "Mistral": - return self.mistral_file_templates - assert False - - @property - def command_templates(self): - if self.toolchain == "Quartus": - return self.quartus_command_templates - if self.toolchain == "Mistral": - return self.mistral_command_templates - assert False - - def add_clock_constraint(self, clock, frequency): - super().add_clock_constraint(clock, frequency) - clock.attrs["keep"] = "true" - - @property - def default_clk_constraint(self): - # Internal high-speed oscillator on Cyclone V devices. - # It is specified to not be faster than 100MHz, but the actual - # frequency seems to vary a lot between devices. Measurements - # of 78 to 84 MHz have been observed. - if self.default_clk == "cyclonev_oscillator": - assert self.device.startswith("5C") - return Clock(100e6) - # Otherwise, use the defined Clock resource. - return super().default_clk_constraint - - def create_missing_domain(self, name): - if name == "sync" and self.default_clk == "cyclonev_oscillator": - # Use the internal high-speed oscillator for Cyclone V devices - assert self.device.startswith("5C") - m = Module() - m.domains += ClockDomain("sync") - m.submodules += Instance("cyclonev_oscillator", - i_oscena=Const(1), - o_clkout=ClockSignal("sync")) - return m - else: - return super().create_missing_domain(name) - - # 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): - # altiobuf_ requires an output enable signal for each pin, but pin.oe is 1 bit wide. - if pin.xdr == 0: - return pin.oe.replicate(pin.width) - 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.io, - 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.io, - ) - 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.io, - i_oe=self._get_oereg(m, pin) - ) - 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.io, - o_dataout=self._get_ireg(m, pin, invert), - i_oe=self._get_oereg(m, pin), - ) - return m - - def get_diff_input(self, pin, port, attrs, invert): - self._check_feature("differential input", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - if pin.xdr == 1: - port.p.attrs["useioff"] = 1 - port.n.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=port.p, - i_datain_b=port.n, - o_dataout=self._get_ireg(m, pin, invert) - ) - return m - - def get_diff_output(self, pin, port, attrs, invert): - self._check_feature("differential output", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - if pin.xdr == 1: - port.p.attrs["useioff"] = 1 - port.n.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=port.p, - o_dataout_b=port.n, - ) - return m - - def get_diff_tristate(self, pin, port, attrs, invert): - self._check_feature("differential tristate", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - if pin.xdr == 1: - port.p.attrs["useioff"] = 1 - port.n.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=port.p, - o_dataout_b=port.n, - i_oe=self._get_oereg(m, pin), - ) - return m - - def get_diff_input_output(self, pin, port, attrs, invert): - self._check_feature("differential input/output", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - if pin.xdr == 1: - port.p.attrs["useioff"] = 1 - port.n.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=port.p, - io_dataio_b=port.n, - 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_async_ff_sync(self, async_ff_sync): - m = Module() - sync_output = Signal() - if async_ff_sync._edge == "pos": - m.submodules += Instance("altera_std_synchronizer", - p_depth=async_ff_sync._stages, - i_clk=ClockSignal(async_ff_sync._o_domain), - i_reset_n=~async_ff_sync.i, - i_din=Const(1), - o_dout=sync_output, - ) - else: - m.submodules += Instance("altera_std_synchronizer", - p_depth=async_ff_sync._stages, - i_clk=ClockSignal(async_ff_sync._o_domain), - i_reset_n=async_ff_sync.i, - i_din=Const(1), - o_dout=sync_output, - ) - m.d.comb += async_ff_sync.o.eq(~sync_output) - return m +def __getattr__(name): + if name in ("IntelPlatform",): + warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}", + DeprecationWarning, stacklevel=2) + return getattr(vendor, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/amaranth/vendor/lattice_ecp5.py b/amaranth/vendor/lattice_ecp5.py index 55c10e8..8bf1711 100644 --- a/amaranth/vendor/lattice_ecp5.py +++ b/amaranth/vendor/lattice_ecp5.py @@ -1,668 +1,13 @@ -from abc import abstractmethod +# TODO(amaranth-0.5): remove module -from ..hdl import * -from ..build import * +import warnings +import importlib +from .. import vendor -__all__ = ["LatticeECP5Platform"] - - -class LatticeECP5Platform(TemplatedPlatform): - """ - .. rubric:: Trellis toolchain - - Required tools: - * ``yosys`` - * ``nextpnr-ecp5`` - * ``ecppack`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_TRELLIS``, if present. - - 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-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. - * ``{{name}}.json``: synthesized RTL. - * ``{{name}}.tim``: nextpnr log. - * ``{{name}}.config``: ASCII bitstream. - * ``{{name}}.bit``: binary bitstream. - * ``{{name}}.svf``: JTAG programming vector. - - .. rubric:: Diamond toolchain - - Required tools: - * ``pnmainc`` - * ``ddtcmd`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond - itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``, - for instance) is known to work:: - - @echo off - set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH% - - 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 = None # selected when creating platform - - device = property(abstractmethod(lambda: None)) - package = property(abstractmethod(lambda: None)) - speed = property(abstractmethod(lambda: None)) - grade = "C" # [C]ommercial, [I]ndustrial - - # Trellis templates - - _nextpnr_device_options = { - "LFE5U-12F": "--12k", - "LFE5U-25F": "--25k", - "LFE5U-45F": "--45k", - "LFE5U-85F": "--85k", - "LFE5UM-25F": "--um-25k", - "LFE5UM-45F": "--um-45k", - "LFE5UM-85F": "--um-85k", - "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", - } - - _trellis_required_tools = [ - "yosys", - "nextpnr-ecp5", - "ecppack" - ] - _trellis_file_templates = { - **TemplatedPlatform.build_script_templates, - "{{name}}.il": r""" - # {{autogenerated}} - {{emit_rtlil()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.ys": r""" - # {{autogenerated}} - {% for file in platform.iter_files(".v") -%} - read_verilog {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".sv") -%} - read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".il") -%} - read_ilang {{file}} - {% endfor %} - read_ilang {{name}}.il - delete w:$verilog_initial_trigger - {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} - synth_ecp5 {{get_override("synth_opts")|options}} -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, attrs in platform.iter_port_constraints_bits() -%} - LOCATE COMP "{{port_name}}" SITE "{{pin_name}}"; - {% if attrs -%} - IOBUF PORT "{{port_name}}" - {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %}; - {% endif %} - {% endfor %} - {% 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)")}} - """ - } - _trellis_command_templates = [ - r""" - {{invoke_tool("yosys")}} - {{quiet("-q")}} - {{get_override("yosys_opts")|options}} - -l {{name}}.rpt - {{name}}.ys - """, - r""" - {{invoke_tool("nextpnr-ecp5")}} - {{quiet("--quiet")}} - {{get_override("nextpnr_opts")|options}} - --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""" - {{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 - {% for var in platform._all_toolchain_env_vars %} - if [ -n "${{var}}" ]; then - bindir=$(dirname "${{var}}") - . "${{var}}" - fi - {% endfor %} - {{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 {{name}}_impl \ - -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \ - -lpf {{name}}.lpf \ - -synthesis synplify - {% for file in platform.iter_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 - prj_run Translate -impl impl - prj_run Map -impl impl - prj_run PAR -impl impl - prj_run Export -impl impl -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, attrs in platform.iter_port_constraints_bits() -%} - LOCATE COMP "{{port_name}}" SITE "{{pin_name}}"; - {% if attrs -%} - IOBUF PORT "{{port_name}}" - {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %}; - {% endif %} - {% 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): - # 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", - "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_I", "SSTL135D_II", "SSTL15D_I", "SSTL15D_II", "SSTL18D_I", - "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=False, o_invert=False): - 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_oereg(clk, oe, q): - for bit in range(len(q)): - m.submodules += Instance("OFS1P3DX", - i_SCLK=clk, - i_SP=Const(1), - i_CD=Const(0), - i_D=oe, - 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_iddrx2(sclk, eclk, d, q0, q1, q2, q3): - for bit in range(len(d)): - m.submodules += Instance("IDDRX2F", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D=d[bit], - o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit] - ) - - def get_iddr71b(sclk, eclk, d, q0, q1, q2, q3, q4, q5, q6): - for bit in range(len(d)): - m.submodules += Instance("IDDR71B", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D=d[bit], - o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit], - o_Q4=q4[bit], o_Q5=q5[bit], o_Q6=q6[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_oddrx2(sclk, eclk, d0, d1, d2, d3, q): - for bit in range(len(q)): - m.submodules += Instance("ODDRX2F", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit], - o_Q=q[bit] - ) - - def get_oddr71b(sclk, eclk, d0, d1, d2, d3, d4, d5, d6, q): - for bit in range(len(q)): - m.submodules += Instance("ODDR71B", - i_SCLK=sclk, - i_ECLK=eclk, - i_RST=Const(0), - i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit], - i_D4=d4[bit], i_D5=d5[bit], i_D6=d6[bit], - o_Q=q[bit] - ) - - def get_ineg(z, invert): - if invert: - a = Signal.like(z, name_suffix="_n") - m.d.comb += z.eq(~a) - return a - else: - return z - - def get_oneg(a, invert): - if invert: - z = Signal.like(a, name_suffix="_n") - m.d.comb += z.eq(~a) - return z - else: - return a - - if "i" in pin.dir: - if pin.xdr < 2: - pin_i = get_ineg(pin.i, i_invert) - elif pin.xdr == 2: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - elif pin.xdr == 4: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - pin_i2 = get_ineg(pin.i2, i_invert) - pin_i3 = get_ineg(pin.i3, i_invert) - elif pin.xdr == 7: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - pin_i2 = get_ineg(pin.i2, i_invert) - pin_i3 = get_ineg(pin.i3, i_invert) - pin_i4 = get_ineg(pin.i4, i_invert) - pin_i5 = get_ineg(pin.i5, i_invert) - pin_i6 = get_ineg(pin.i6, i_invert) - if "o" in pin.dir: - if pin.xdr < 2: - pin_o = get_oneg(pin.o, o_invert) - elif pin.xdr == 2: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(pin.o1, o_invert) - elif pin.xdr == 4: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(pin.o1, o_invert) - pin_o2 = get_oneg(pin.o2, o_invert) - pin_o3 = get_oneg(pin.o3, o_invert) - elif pin.xdr == 7: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(pin.o1, o_invert) - pin_o2 = get_oneg(pin.o2, o_invert) - pin_o3 = get_oneg(pin.o3, o_invert) - pin_o4 = get_oneg(pin.o4, o_invert) - pin_o5 = get_oneg(pin.o5, o_invert) - pin_o6 = get_oneg(pin.o6, 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(pin.width, 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).replicate(pin.width) - elif pin.xdr == 1: - 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_oereg(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"): - get_oereg(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 4: - if "i" in pin.dir: - get_iddrx2(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3) - if "o" in pin.dir: - get_oddrx2(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, o) - if pin.dir in ("oe", "io"): - get_oereg(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 7: - if "i" in pin.dir: - get_iddr71b(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3, pin_i4, pin_i5, pin_i6) - if "o" in pin.dir: - get_oddr71b(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, pin_o4, pin_o5, pin_o6, o) - if pin.dir in ("oe", "io"): - get_oereg(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, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", - i_I=port.io[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, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", - i_I=o[bit], - o_O=port.io[bit] - ) - return m - - def get_tristate(self, pin, port, attrs, invert): - self._check_feature("single-ended tristate", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", - i_T=t[bit], - i_I=o[bit], - o_O=port.io[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, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", - i_T=t[bit], - i_I=o[bit], - o_O=i[bit], - io_B=port.io[bit] - ) - return m - - def get_diff_input(self, pin, port, attrs, invert): - self._check_feature("differential input", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", - i_I=port.p[bit], - o_O=i[bit] - ) - return m - - def get_diff_output(self, pin, port, attrs, invert): - self._check_feature("differential output", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", - i_I=o[bit], - o_O=port.p[bit], - ) - return m - - def get_diff_tristate(self, pin, port, attrs, invert): - self._check_feature("differential tristate", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", - i_T=t[bit], - i_I=o[bit], - o_O=port.p[bit], - ) - return m - - def get_diff_input_output(self, pin, port, attrs, invert): - self._check_feature("differential input/output", pin, attrs, - valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", - i_T=t[bit], - i_I=o[bit], - o_O=i[bit], - io_B=port.p[bit], - ) - return m - - # CDC primitives are not currently specialized for ECP5. - # While Diamond supports false path constraints; nextpnr-ecp5 does not. +def __getattr__(name): + if name in ("LatticeECP5Platform",): + warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}", + DeprecationWarning, stacklevel=2) + return getattr(vendor, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/amaranth/vendor/lattice_ice40.py b/amaranth/vendor/lattice_ice40.py index 1e2ca1e..dd8348d 100644 --- a/amaranth/vendor/lattice_ice40.py +++ b/amaranth/vendor/lattice_ice40.py @@ -1,627 +1,13 @@ -from abc import abstractmethod +# TODO(amaranth-0.5): remove module -from ..hdl import * -from ..lib.cdc import ResetSynchronizer -from ..build import * +import warnings +import importlib +from .. import vendor -__all__ = ["LatticeICE40Platform"] - - -class LatticeICE40Platform(TemplatedPlatform): - """ - .. rubric:: IceStorm toolchain - - Required tools: - * ``yosys`` - * ``nextpnr-ice40`` - * ``icepack`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_ICESTORM``, if present. - - 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_ice40`` Yosys command. - * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script. - * ``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. - * ``{{name}}.json``: synthesized RTL. - * ``{{name}}.tim``: nextpnr log. - * ``{{name}}.asc``: ASCII bitstream. - * ``{{name}}.bin``: binary bitstream. - - .. rubric:: 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 - ``AMARANTH_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 = None # selected when creating platform - - device = property(abstractmethod(lambda: None)) - package = property(abstractmethod(lambda: None)) - - # IceStorm templates - - _nextpnr_device_options = { - "iCE40LP384": "--lp384", - "iCE40LP1K": "--lp1k", - "iCE40LP4K": "--lp8k", - "iCE40LP8K": "--lp8k", - "iCE40HX1K": "--hx1k", - "iCE40HX4K": "--hx8k", - "iCE40HX8K": "--hx8k", - "iCE40UP5K": "--up5k", - "iCE40UP3K": "--up5k", - "iCE5LP4K": "--u4k", - "iCE5LP2K": "--u4k", - "iCE5LP1K": "--u4k", - } - _nextpnr_package_options = { - "iCE40LP4K": ":4k", - "iCE40HX4K": ":4k", - "iCE40UP3K": "", - "iCE5LP2K": "", - "iCE5LP1K": "", - } - - _icestorm_required_tools = [ - "yosys", - "nextpnr-ice40", - "icepack", - ] - _icestorm_file_templates = { - **TemplatedPlatform.build_script_templates, - "{{name}}.il": r""" - # {{autogenerated}} - {{emit_rtlil()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.ys": r""" - # {{autogenerated}} - {% for file in platform.iter_files(".v") -%} - read_verilog {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".sv") -%} - read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}} - {% endfor %} - {% for file in platform.iter_files(".il") -%} - read_ilang {{file}} - {% endfor %} - read_ilang {{name}}.il - delete w:$verilog_initial_trigger - {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} - synth_ice40 {{get_override("synth_opts")|options}} -top {{name}} - {{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 %} - {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} - set_frequency {{net_signal|hierarchy(".")}} {{frequency/1000000}} - {% endfor%} - {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} - """, - } - _icestorm_command_templates = [ - r""" - {{invoke_tool("yosys")}} - {{quiet("-q")}} - {{get_override("yosys_opts")|options}} - -l {{name}}.rpt - {{name}}.ys - """, - r""" - {{invoke_tool("nextpnr-ice40")}} - {{quiet("--quiet")}} - {{get_override("nextpnr_opts")|options}} - --log {{name}}.tim - {{platform._nextpnr_device_options[platform.device]}} - --package - {{platform.package|lower}}{{platform._nextpnr_package_options[platform.device]| - default("")}} - --json {{name}}.json - --pcf {{name}}.pcf - --asc {{name}}.asc - """, - r""" - {{invoke_tool("icepack")}} - {{verbose("-v")}} - {{name}}.asc - {{name}}.bin - """ - ] - - # iCECube2 templates - - _icecube2_required_tools = [ - "synthesis", - "synpwrap", - "tclsh", - ] - _icecube2_file_templates = { - **TemplatedPlatform.build_script_templates, - "build_{{name}}.sh": r""" - # {{autogenerated}} - set -e{{verbose("x")}} - {% for var in platform._all_toolchain_env_vars %} - if [ -n "${{var}}" ]; then - # LSE environment - export LD_LIBRARY_PATH=${{var}}/LSE/bin/lin64:$LD_LIBRARY_PATH - export PATH=${{var}}/LSE/bin/lin64:$PATH - export FOUNDRY=${{var}}/LSE - # Synplify environment - export LD_LIBRARY_PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH - export PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH - export SYNPLIFY_PATH=${{var}}/synpbase - # Common environment - export SBT_DIR=${{var}}/sbt_backend - else - echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1 - fi - {% endfor %} - {{emit_commands("sh")}} - """, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_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_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_files(".v", ".sv", ".vhd", ".vhdl") -%} - add_file -verilog {{file|tcl_escape}} - {% 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 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)")}} - """, - "{{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"AMARANTH_ENV_{self.toolchain}" - if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"): - return f"AMARANTH_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 - - @property - def default_clk_constraint(self): - # Internal high-speed oscillator: 48 MHz / (2 ^ div) - if self.default_clk == "SB_HFOSC": - return Clock(48e6 / 2 ** self.hfosc_div) - # Internal low-speed oscillator: 10 KHz - elif self.default_clk == "SB_LFOSC": - return Clock(10e3) - # Otherwise, use the defined Clock resource. - return super().default_clk_constraint - - 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 - # global reset. Note that this is a *time-based* delay, generated purely by the internal - # oscillator, which may not be observed nor influenced directly. For details, see links: - # * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411 - # * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673 - # - # To handle this, it is necessary to have a global reset in any iCE40 design that may - # potentially instantiate BRAMs, and assert this reset for >3 us after configuration. - # (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated - # reset line, this line is ORed with the power on reset. - # - # If an internal oscillator is selected as the default clock source, the power-on-reset - # delay is increased to 100 us, since the oscillators are only stable after that long. - # - # The power-on reset timer counts up because the vendor tools do not support initialization - # of flip-flops. - if name == "sync" and self.default_clk is not None: - m = Module() - - # Internal high-speed clock: 6 MHz, 12 MHz, 24 MHz, or 48 MHz depending on the divider. - if self.default_clk == "SB_HFOSC": - if not hasattr(self, "hfosc_div"): - raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer " - "between 0 and 3") - if not isinstance(self.hfosc_div, int) or self.hfosc_div < 0 or self.hfosc_div > 3: - raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer " - "between 0 and 3, not {!r}" - .format(self.hfosc_div)) - clk_i = Signal() - m.submodules += Instance("SB_HFOSC", - i_CLKHFEN=1, - i_CLKHFPU=1, - p_CLKHF_DIV="0b{0:02b}".format(self.hfosc_div), - o_CLKHF=clk_i) - delay = int(100e-6 * self.default_clk_frequency) - # Internal low-speed clock: 10 KHz. - elif self.default_clk == "SB_LFOSC": - clk_i = Signal() - m.submodules += Instance("SB_LFOSC", - i_CLKLFEN=1, - i_CLKLFPU=1, - o_CLKLF=clk_i) - delay = int(100e-6 * self.default_clk_frequency) - # User-defined clock signal. - else: - clk_i = self.request(self.default_clk).i - delay = int(15e-6 * self.default_clk_frequency) - - if self.default_rst is not None: - rst_i = self.request(self.default_rst).i - else: - rst_i = Const(0) - - # Power-on-reset domain - m.domains += ClockDomain("por", reset_less=True, local=True) - timer = Signal(range(delay)) - ready = Signal() - m.d.comb += ClockSignal("por").eq(clk_i) - with m.If(timer == delay): - m.d.por += ready.eq(1) - with m.Else(): - m.d.por += timer.eq(timer + 1) - - # Primary domain - m.domains += ClockDomain("sync") - m.d.comb += ClockSignal("sync").eq(clk_i) - if self.default_rst is not None: - m.submodules.reset_sync = ResetSynchronizer(~ready | rst_i, domain="sync") - else: - m.d.comb += ResetSignal("sync").eq(~ready) - - return m - - def should_skip_port_component(self, port, attrs, component): - # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for - # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs - # between LP/HX and UP series: - # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting) - # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting) - if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n": - return True - return False - - def _get_io_buffer(self, m, pin, port, attrs, *, i_invert=False, o_invert=False, - invert_lut=False): - def get_dff(clk, d, q): - m.submodules += Instance("$dff", - p_CLK_POLARITY=1, - p_WIDTH=len(d), - i_CLK=clk, - i_D=d, - o_Q=q) - - def get_ineg(y, invert): - if invert_lut: - a = Signal.like(y, name_suffix="_x{}".format(1 if invert else 0)) - for bit in range(len(y)): - m.submodules += Instance("SB_LUT4", - p_LUT_INIT=Const(0b01 if invert else 0b10, 16), - i_I0=a[bit], - i_I1=Const(0), - i_I2=Const(0), - i_I3=Const(0), - o_O=y[bit]) - return a - elif invert: - a = Signal.like(y, name_suffix="_n") - m.d.comb += y.eq(~a) - return a - else: - return y - - def get_oneg(a, invert): - if invert_lut: - y = Signal.like(a, name_suffix="_x{}".format(1 if invert else 0)) - for bit in range(len(a)): - m.submodules += Instance("SB_LUT4", - p_LUT_INIT=Const(0b01 if invert else 0b10, 16), - i_I0=a[bit], - i_I1=Const(0), - i_I2=Const(0), - i_I3=Const(0), - o_O=y[bit]) - return y - elif invert: - y = Signal.like(a, name_suffix="_n") - m.d.comb += y.eq(~a) - return y - else: - return a - - if "GLOBAL" in attrs: - is_global_input = bool(attrs["GLOBAL"]) - del attrs["GLOBAL"] - else: - is_global_input = False - assert not (is_global_input and i_invert) - - if "i" in pin.dir: - if pin.xdr < 2: - pin_i = get_ineg(pin.i, i_invert) - elif pin.xdr == 2: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - if "o" in pin.dir: - if pin.xdr < 2: - pin_o = get_oneg(pin.o, o_invert) - elif pin.xdr == 2: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(pin.o1, o_invert) - - if "i" in pin.dir and pin.xdr == 2: - i0_ff = Signal.like(pin_i0, name_suffix="_ff") - i1_ff = Signal.like(pin_i1, name_suffix="_ff") - get_dff(pin.i_clk, i0_ff, pin_i0) - get_dff(pin.i_clk, i1_ff, pin_i1) - if "o" in pin.dir and pin.xdr == 2: - o1_ff = Signal.like(pin_o1, name_suffix="_ff") - get_dff(pin.o_clk, pin_o1, o1_ff) - - for bit in range(len(port)): - io_args = [ - ("io", "PACKAGE_PIN", port[bit]), - *(("p", key, value) for key, value in attrs.items()), - ] - - if "i" not in pin.dir: - # If no input pin is requested, it is important to use a non-registered input pin - # type, because an output-only pin would not have an input clock, and if its input - # is configured as registered, this would prevent a co-located input-capable pin - # from using an input clock. - i_type = 0b01 # PIN_INPUT - elif pin.xdr == 0: - i_type = 0b01 # PIN_INPUT - elif pin.xdr > 0: - i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR - if "o" not in pin.dir: - o_type = 0b0000 # PIN_NO_OUTPUT - elif pin.xdr == 0 and pin.dir == "o": - o_type = 0b0110 # PIN_OUTPUT - elif pin.xdr == 0: - o_type = 0b1010 # PIN_OUTPUT_TRISTATE - elif pin.xdr == 1 and pin.dir == "o": - o_type = 0b0101 # PIN_OUTPUT_REGISTERED - elif pin.xdr == 1: - o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED - elif pin.xdr == 2 and pin.dir == "o": - o_type = 0b0100 # PIN_OUTPUT_DDR - elif pin.xdr == 2: - o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED - 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)) - if hasattr(pin, "o_clk"): - io_args.append(("i", "OUTPUT_CLK", pin.o_clk)) - - if "i" in pin.dir: - if pin.xdr == 0 and is_global_input: - io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit])) - elif pin.xdr < 2: - io_args.append(("o", "D_IN_0", pin_i[bit])) - elif pin.xdr == 2: - # Re-register both inputs before they enter fabric. This increases hold time - # to an entire cycle, and adds one cycle of latency. - io_args.append(("o", "D_IN_0", i0_ff[bit])) - io_args.append(("o", "D_IN_1", i1_ff[bit])) - if "o" in pin.dir: - if pin.xdr < 2: - io_args.append(("i", "D_OUT_0", pin_o[bit])) - elif pin.xdr == 2: - # Re-register negedge output after it leaves fabric. This increases setup time - # to an entire cycle, and doesn't add latency. - io_args.append(("i", "D_OUT_0", pin_o0[bit])) - io_args.append(("i", "D_OUT_1", o1_ff[bit])) - - if pin.dir in ("oe", "io"): - io_args.append(("i", "OUTPUT_ENABLE", pin.oe)) - - if is_global_input: - m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_GB_IO", *io_args) - else: - m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_IO", *io_args) - - 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() - self._get_io_buffer(m, pin, port.io, attrs, i_invert=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) - m = Module() - self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) - 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() - self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) - 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() - self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert) - return m - - def get_diff_input(self, pin, port, attrs, invert): - self._check_feature("differential input", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - m = Module() - # See comment in should_skip_port_component above. - self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert) - return m - - def get_diff_output(self, pin, port, attrs, invert): - self._check_feature("differential output", pin, attrs, - valid_xdrs=(0, 1, 2), valid_attrs=True) - m = Module() - # Note that the non-inverting output pin is not driven the same way as a regular - # output pin. The inverter introduces a delay, so for a non-inverting output pin, - # an identical delay is introduced by instantiating a LUT. This makes the waveform - # perfectly symmetric in the xdr=0 case. - self._get_io_buffer(m, pin, port.p, attrs, o_invert= invert, invert_lut=True) - self._get_io_buffer(m, pin, port.n, attrs, o_invert=not invert, invert_lut=True) - return m - - # Tristate bidirectional buffers are not supported on iCE40 because it requires external - # termination, which is different for differential pins configured as inputs and outputs. - - # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports - # the necessary attributes; nextpnr-ice40 does not. +def __getattr__(name): + if name in ("LatticeICE40Platform",): + warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}", + DeprecationWarning, stacklevel=2) + return getattr(vendor, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/amaranth/vendor/lattice_machxo_2_3l.py b/amaranth/vendor/lattice_machxo_2_3l.py index 36fa4c8..9ccc16b 100644 --- a/amaranth/vendor/lattice_machxo_2_3l.py +++ b/amaranth/vendor/lattice_machxo_2_3l.py @@ -1,456 +1,13 @@ -from abc import abstractmethod +# TODO(amaranth-0.5): remove module -from ..hdl import * -from ..build import * +import warnings +import importlib +from .. import vendor -__all__ = ["LatticeMachXO2Platform", "LatticeMachXO3LPlatform"] - - -# MachXO2 and MachXO3L primitives are the same. Handle both using -# one class and expose user-aliases for convenience. -class LatticeMachXO2Or3LPlatform(TemplatedPlatform): - """ - Required tools: - * ``pnmainc`` - * ``ddtcmd`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond - itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``, - for instance) is known to work:: - - @echo off - set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH% - - 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}}.jed``: JEDEC fuse file. - * ``{{name}}.bit``: binary bitstream. - * ``{{name}}.svf``: JTAG programming vector for FLASH programming. - * ``{{name}}_flash.svf``: JTAG programming vector for FLASH programming. - * ``{{name}}_sram.svf``: JTAG programming vector for SRAM programming. - """ - - toolchain = "Diamond" - - device = property(abstractmethod(lambda: None)) - package = property(abstractmethod(lambda: None)) - speed = property(abstractmethod(lambda: None)) - grade = "C" # [C]ommercial, [I]ndustrial - - required_tools = [ - "pnmainc", - "ddtcmd" - ] - file_templates = { - **TemplatedPlatform.build_script_templates, - "build_{{name}}.sh": r""" - # {{autogenerated}} - set -e{{verbose("x")}} - if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi - {% for var in platform._all_toolchain_env_vars %} - if [ -n "${{var}}" ]; then - bindir=$(dirname "${{var}}") - . "${{var}}" - fi - {% endfor %} - {{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 {{name}}_impl \ - -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \ - -lpf {{name}}.lpf \ - -synthesis synplify - {% for file in platform.iter_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 - prj_run Translate -impl impl - prj_run Map -impl impl - prj_run PAR -impl impl - prj_run Export -impl impl -task Bitgen - prj_run Export -impl impl -task Jedecgen - {{get_override("script_after_export")|default("# (script_after_export placeholder)")}} - """, - "{{name}}.lpf": r""" - # {{autogenerated}} - BLOCK ASYNCPATHS; - BLOCK RESETPATHS; - {% 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 attrs.items() %} {{key}}={{value}}{% endfor %}; - {% endif %} - {% 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)")}} - """, - } - 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 -jed - -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} - -if {{name}}_impl/{{name}}_impl.jed -of {{name}}.jed - """, - r""" - {{invoke_tool("ddtcmd")}} - -oft -svfsingle -revd -op "FLASH Erase,Program,Verify" - -if {{name}}_impl/{{name}}_impl.jed -of {{name}}_flash.svf - """, - # TODO(amaranth-0.4): remove - r""" - {% if syntax == "bat" -%} - copy {{name}}_flash.svf {{name}}.svf - {% else -%} - cp {{name}}_flash.svf {{name}}.svf - {% endif %} - """, - r""" - {{invoke_tool("ddtcmd")}} - -oft -svfsingle -revd -op "SRAM Fast Program" - -if {{name}}_impl/{{name}}_impl.bit -of {{name}}_sram.svf - """, - ] - # These numbers were extracted from - # "MachXO2 sysCLOCK PLL Design and Usage Guide" - _supported_osch_freqs = [ - 2.08, 2.15, 2.22, 2.29, 2.38, 2.46, 2.56, 2.66, 2.77, 2.89, - 3.02, 3.17, 3.33, 3.50, 3.69, 3.91, 4.16, 4.29, 4.43, 4.59, - 4.75, 4.93, 5.12, 5.32, 5.54, 5.78, 6.05, 6.33, 6.65, 7.00, - 7.39, 7.82, 8.31, 8.58, 8.87, 9.17, 9.50, 9.85, 10.23, 10.64, - 11.08, 11.57, 12.09, 12.67, 13.30, 14.00, 14.78, 15.65, 15.65, 16.63, - 17.73, 19.00, 20.46, 22.17, 24.18, 26.60, 29.56, 33.25, 38.00, 44.33, - 53.20, 66.50, 88.67, 133.00 - ] - - @property - def default_clk_constraint(self): - # Internal high-speed oscillator on MachXO2/MachXO3L devices. - # It can have a range of frequencies. - if self.default_clk == "OSCH": - assert self.osch_frequency in self._supported_osch_freqs - return Clock(int(self.osch_frequency * 1e6)) - # Otherwise, use the defined Clock resource. - return super().default_clk_constraint - - def create_missing_domain(self, name): - # Lattice MachXO2/MachXO3L 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: - using_osch = False - if self.default_clk == "OSCH": - using_osch = True - clk_i = Signal() - 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() - m = Module() - # There is no end-of-startup signal on MachXO2/MachXO3L, 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), - ] - if using_osch: - osch_freq = self.osch_frequency - if osch_freq not in self._supported_osch_freqs: - raise ValueError("Frequency {!r} is not valid for OSCH clock. Valid frequencies are {!r}" - .format(osch_freq, self._supported_osch_freqs)) - osch_freq_param = "{:.2f}".format(float(osch_freq)) - m.submodules += [ Instance("OSCH", p_NOM_FREQ=osch_freq_param, i_STDBY=Const(0), o_OSC=clk_i, o_SEDSTDBY=Signal()) ] - # 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 = [ - "PCI33", "LVTTL33", "LVCMOS33", "LVCMOS25", "LVCMOS18", "LVCMOS15", "LVCMOS12", - "LVCMOS25R33", "LVCMOS18R33", "LVCMOS18R25", "LVCMOS15R33", "LVCMOS15R25", "LVCMOS12R33", - "LVCMOS12R25", "LVCMOS10R33", "LVCMOS10R25", "SSTL25_I", "SSTL25_II", "SSTL18_I", - "SSTL18_II", "HSTL18_I", "HSTL18_II", - ] - _differential_io_types = [ - "LVDS25", "LVDS25E", "RSDS25", "RSDS25E", "BLVDS25", "BLVDS25E", "MLVDS25", "MLVDS25E", - "LVPECL33", "LVPECL33E", "SSTL25D_I", "SSTL25D_II", "SSTL18D_I", "SSTL18D_II", - "HSTL18D_I", "HSTL18D_II", "LVTTL33D", "LVCMOS33D", "LVCMOS25D", "LVCMOS18D", "LVCMOS15D", - "LVCMOS12D", "MIPI", - ] - - 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=False, o_invert=False): - 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("IDDRXE", - 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("ODDRXE", - i_SCLK=sclk, - i_RST=Const(0), - i_D0=d0[bit], i_D1=d1[bit], - o_Q=q[bit] - ) - - def get_ineg(z, invert): - if invert: - a = Signal.like(z, name_suffix="_n") - m.d.comb += z.eq(~a) - return a - else: - return z - - def get_oneg(a, invert): - if invert: - z = Signal.like(a, name_suffix="_n") - m.d.comb += z.eq(~a) - return z - else: - return a - - if "i" in pin.dir: - if pin.xdr < 2: - pin_i = get_ineg(pin.i, i_invert) - elif pin.xdr == 2: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - if "o" in pin.dir: - if pin.xdr < 2: - pin_o = get_oneg(pin.o, o_invert) - elif pin.xdr == 2: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(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() - i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert) - for bit in range(len(port)): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", - i_I=port.io[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=invert) - for bit in range(len(port)): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", - i_I=o[bit], - o_O=port.io[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=invert) - for bit in range(len(port)): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", - i_T=t, - i_I=o[bit], - o_O=port.io[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=invert, o_invert=invert) - for bit in range(len(port)): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", - i_T=t, - i_I=o[bit], - o_O=i[bit], - io_B=port.io[bit] - ) - return m - - def get_diff_input(self, pin, 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=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB", - i_I=port.p[bit], - o_O=i[bit] - ) - return m - - def get_diff_output(self, pin, 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=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB", - i_I=o[bit], - o_O=port.p[bit], - ) - return m - - def get_diff_tristate(self, pin, 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=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ", - i_T=t, - i_I=o[bit], - o_O=port.p[bit], - ) - return m - - def get_diff_input_output(self, pin, 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=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB", - i_T=t, - i_I=o[bit], - o_O=i[bit], - io_B=port.p[bit], - ) - return m - - # CDC primitives are not currently specialized for MachXO2/MachXO3L. - - -LatticeMachXO2Platform = LatticeMachXO2Or3LPlatform -LatticeMachXO3LPlatform = LatticeMachXO2Or3LPlatform +def __getattr__(name): + if name in ("LatticeMachXO2Platform", "LatticeMachXO3LPlatform"): + warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}", + DeprecationWarning, stacklevel=2) + return getattr(vendor, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/amaranth/vendor/quicklogic.py b/amaranth/vendor/quicklogic.py index 697ec4c..24d6b05 100644 --- a/amaranth/vendor/quicklogic.py +++ b/amaranth/vendor/quicklogic.py @@ -1,184 +1,13 @@ -from abc import abstractmethod +# TODO(amaranth-0.5): remove module -from ..hdl import * -from ..lib.cdc import ResetSynchronizer -from ..build import * +import warnings +import importlib +from .. import vendor -__all__ = ["QuicklogicPlatform"] - - -class QuicklogicPlatform(TemplatedPlatform): - """ - .. rubric:: Symbiflow toolchain - - Required tools: - * ``symbiflow_synth`` - * ``symbiflow_pack`` - * ``symbiflow_place`` - * ``symbiflow_route`` - * ``symbiflow_write_fasm`` - * ``symbiflow_write_bitstream`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_QLSYMBIFLOW``, if present. - - Available overrides: - * ``add_constraints``: inserts commands in XDC file. - """ - - device = property(abstractmethod(lambda: None)) - package = property(abstractmethod(lambda: None)) - - # Since the QuickLogic version of SymbiFlow toolchain is not upstreamed yet - # we should distinguish the QuickLogic version from mainline one. - # QuickLogic toolchain: https://github.com/QuickLogic-Corp/quicklogic-fpga-toolchain/releases - toolchain = "QLSymbiflow" - - required_tools = [ - "symbiflow_synth", - "symbiflow_pack", - "symbiflow_place", - "symbiflow_route", - "symbiflow_write_fasm", - "symbiflow_write_bitstream", - "symbiflow_write_openocd", - ] - file_templates = { - **TemplatedPlatform.build_script_templates, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.pcf": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - set_io {{port_name}} {{pin_name}} - {% endfor %} - """, - "{{name}}.xdc": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - {% for attr_name, attr_value in attrs.items() -%} - set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }] - {% endfor %} - {% endfor %} - {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} - """, - "{{name}}.sdc": r""" - # {{autogenerated}} - {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} - {% if port_signal is not none -%} - create_clock -period {{100000000/frequency}} {{port_signal.name|ascii_escape}} - {% endif %} - {% endfor %} - """ - } - command_templates = [ - r""" - {{invoke_tool("symbiflow_synth")}} - -t {{name}} - -v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v - -d {{platform.device}} - -p {{name}}.pcf - -P {{platform.package}} - -x {{name}}.xdc - """, - r""" - {{invoke_tool("symbiflow_pack")}} - -e {{name}}.eblif - -d {{platform.device}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_place")}} - -e {{name}}.eblif - -d {{platform.device}} - -p {{name}}.pcf - -n {{name}}.net - -P {{platform.package}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_route")}} - -e {{name}}.eblif - -d {{platform.device}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_write_fasm")}} - -e {{name}}.eblif - -d {{platform.device}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_write_bitstream")}} - -f {{name}}.fasm - -d {{platform.device}} - -P {{platform.package}} - -b {{name}}.bit - """, - # This should be `invoke_tool("symbiflow_write_openocd")`, but isn't because of a bug in - # the QLSymbiflow v1.3.0 toolchain release. - r""" - python3 -m quicklogic_fasm.bitstream_to_openocd - {{name}}.bit - {{name}}.openocd - --osc-freq {{platform.osc_freq}} - --fpga-clk-divider {{platform.osc_div}} - """, - ] - - # Common logic - - @property - def default_clk_constraint(self): - if self.default_clk == "sys_clk0": - return Clock(self.osc_freq / self.osc_div) - return super().default_clk_constraint - - def add_clock_constraint(self, clock, frequency): - super().add_clock_constraint(clock, frequency) - clock.attrs["keep"] = "TRUE" - - def create_missing_domain(self, name): - if name == "sync" and self.default_clk is not None: - m = Module() - if self.default_clk == "sys_clk0": - if not hasattr(self, "osc_div"): - raise ValueError("OSC divider (osc_div) must be an integer between 2 " - "and 512") - if not isinstance(self.osc_div, int) or self.osc_div < 2 or self.osc_div > 512: - raise ValueError("OSC divider (osc_div) must be an integer between 2 " - "and 512, not {!r}" - .format(self.osc_div)) - if not hasattr(self, "osc_freq"): - raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 " - "and 80000000") - if not isinstance(self.osc_freq, int) or self.osc_freq < 2100000 or self.osc_freq > 80000000: - raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 " - "and 80000000, not {!r}" - .format(self.osc_freq)) - clk_i = Signal() - sys_clk0 = Signal() - m.submodules += Instance("qlal4s3b_cell_macro", - o_Sys_Clk0=sys_clk0) - m.submodules += Instance("gclkbuff", - o_A=sys_clk0, - o_Z=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) - - m.domains += ClockDomain("sync") - m.d.comb += ClockSignal("sync").eq(clk_i) - m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") - return m +def __getattr__(name): + if name in ("QuicklogicPlatform",): + warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}", + DeprecationWarning, stacklevel=2) + return getattr(vendor, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/amaranth/vendor/xilinx.py b/amaranth/vendor/xilinx.py index 47699e3..d975344 100644 --- a/amaranth/vendor/xilinx.py +++ b/amaranth/vendor/xilinx.py @@ -1,1218 +1,13 @@ -import re -from abc import abstractmethod +# TODO(amaranth-0.5): remove module -from ..hdl import * -from ..lib.cdc import ResetSynchronizer -from ..build import * +import warnings +import importlib +from .. import vendor -__all__ = ["XilinxPlatform"] - - -class XilinxPlatform(TemplatedPlatform): - """ - .. rubric:: Vivado toolchain - - Required tools: - * ``vivado`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_VIVADO``, if present. - - Available overrides: - * ``script_after_read``: inserts commands after ``read_xdc`` in Tcl script. - * ``synth_design_opts``: sets options for ``synth_design``. - * ``script_after_synth``: inserts commands after ``synth_design`` in Tcl script. - * ``script_after_place``: inserts commands after ``place_design`` in Tcl script. - * ``script_after_route``: inserts commands after ``route_design`` in Tcl script. - * ``script_before_bitstream``: inserts commands before ``write_bitstream`` in Tcl script. - * ``script_after_bitstream``: inserts commands after ``write_bitstream`` in Tcl script. - * ``add_constraints``: inserts commands in XDC file. - * ``vivado_opts``: adds extra options for ``vivado``. - - Build products: - * ``{{name}}.log``: Vivado log. - * ``{{name}}_timing_synth.rpt``: Vivado report. - * ``{{name}}_utilization_hierarchical_synth.rpt``: Vivado report. - * ``{{name}}_utilization_synth.rpt``: Vivado report. - * ``{{name}}_utilization_hierarchical_place.rpt``: Vivado report. - * ``{{name}}_utilization_place.rpt``: Vivado report. - * ``{{name}}_io.rpt``: Vivado report. - * ``{{name}}_control_sets.rpt``: Vivado report. - * ``{{name}}_clock_utilization.rpt``: Vivado report. - * ``{{name}}_route_status.rpt``: Vivado report. - * ``{{name}}_drc.rpt``: Vivado report. - * ``{{name}}_methodology.rpt``: Vivado report. - * ``{{name}}_timing.rpt``: Vivado report. - * ``{{name}}_power.rpt``: Vivado report. - * ``{{name}}_route.dcp``: Vivado design checkpoint. - * ``{{name}}.bit``: binary bitstream with metadata. - * ``{{name}}.bin``: binary bitstream. - - .. rubric:: ISE toolchain - - Required tools: - * ``xst`` - * ``ngdbuild`` - * ``map`` - * ``par`` - * ``bitgen`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_ISE``, if present. - - Available overrides: - * ``script_after_run``: inserts commands after ``run`` in XST script. - * ``add_constraints``: inserts commands in UCF file. - * ``xst_opts``: adds extra options for ``xst``. - * ``ngdbuild_opts``: adds extra options for ``ngdbuild``. - * ``map_opts``: adds extra options for ``map``. - * ``par_opts``: adds extra options for ``par``. - * ``bitgen_opts``: adds extra and overrides default options for ``bitgen``; - default options: ``-g Compress``. - - Build products: - * ``{{name}}.srp``: synthesis report. - * ``{{name}}.ngc``: synthesized RTL. - * ``{{name}}.bld``: NGDBuild log. - * ``{{name}}.ngd``: design database. - * ``{{name}}_map.map``: MAP log. - * ``{{name}}_map.mrp``: mapping report. - * ``{{name}}_map.ncd``: mapped netlist. - * ``{{name}}.pcf``: physical constraints. - * ``{{name}}_par.par``: PAR log. - * ``{{name}}_par_pad.txt``: I/O usage report. - * ``{{name}}_par.ncd``: place and routed netlist. - * ``{{name}}.drc``: DRC report. - * ``{{name}}.bgn``: BitGen log. - * ``{{name}}.bit``: binary bitstream with metadata. - * ``{{name}}.bin``: raw binary bitstream. - - .. rubric:: Symbiflow toolchain - - Required tools: - * ``symbiflow_synth`` - * ``symbiflow_pack`` - * ``symbiflow_place`` - * ``symbiflow_route`` - * ``symbiflow_write_fasm`` - * ``symbiflow_write_bitstream`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_SYMBIFLOW``, if present. - - Available overrides: - * ``add_constraints``: inserts commands in XDC file. - - .. rubric:: Xray toolchain - - Required tools: - * ``yosys`` - * ``nextpnr-xilinx`` - * ``fasm2frames`` - * ``xc7frames2bit`` - - The environment is populated by running the script specified in the environment variable - ``AMARANTH_ENV_XRAY``, if present. - """ - - toolchain = None # selected when creating platform - - device = property(abstractmethod(lambda: None)) - package = property(abstractmethod(lambda: None)) - speed = property(abstractmethod(lambda: None)) - - @property - def _part(self): - if self.family in {"ultrascale", "ultrascaleplus"}: - return "{}-{}-{}".format(self.device, self.package, self.speed) - else: - return "{}{}-{}".format(self.device, self.package, self.speed) - - @property - def vendor_toolchain(self): - return self.toolchain in ["Vivado", "ISE"] - - # Vivado templates - - _vivado_required_tools = ["vivado"] - _vivado_file_templates = { - **TemplatedPlatform.build_script_templates, - "build_{{name}}.sh": r""" - # {{autogenerated}} - set -e{{verbose("x")}} - if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi - {% for var in platform._all_toolchain_env_vars %} - [ -n "${{var}}" ] && . "${{var}}" - {% endfor %} - {{emit_commands("sh")}} - """, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.tcl": r""" - # {{autogenerated}} - create_project -force -name {{name}} -part {{platform._part}} - {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} - add_files {{file|tcl_escape}} - {% endfor %} - add_files {{name}}.v - read_xdc {{name}}.xdc - {% for file in platform.iter_files(".xdc") -%} - read_xdc {{file|tcl_escape}} - {% endfor %} - {{get_override("script_after_read")|default("# (script_after_read placeholder)")}} - synth_design -top {{name}} {{get_override("synth_design_opts")}} - foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.false_path == "TRUE"}] { - set_false_path -to $cell - } - foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay != ""}] { - set clock [get_clocks -of_objects \ - [all_fanin -flat -startpoints_only [get_pin $cell/D]]] - if {[llength $clock] != 0} { - set_max_delay -datapath_only -from $clock \ - -to [get_cells $cell] [get_property amaranth.vivado.max_delay $cell] - } - } - {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} - report_timing_summary -file {{name}}_timing_synth.rpt - report_utilization -hierarchical -file {{name}}_utilization_hierarchical_synth.rpt - report_utilization -file {{name}}_utilization_synth.rpt - opt_design - place_design - {{get_override("script_after_place")|default("# (script_after_place placeholder)")}} - report_utilization -hierarchical -file {{name}}_utilization_hierarchical_place.rpt - report_utilization -file {{name}}_utilization_place.rpt - report_io -file {{name}}_io.rpt - report_control_sets -verbose -file {{name}}_control_sets.rpt - report_clock_utilization -file {{name}}_clock_utilization.rpt - route_design - {{get_override("script_after_route")|default("# (script_after_route placeholder)")}} - phys_opt_design - report_timing_summary -no_header -no_detailed_paths - write_checkpoint -force {{name}}_route.dcp - report_route_status -file {{name}}_route_status.rpt - report_drc -file {{name}}_drc.rpt - report_methodology -file {{name}}_methodology.rpt - report_timing_summary -datasheet -max_paths 10 -file {{name}}_timing.rpt - report_power -file {{name}}_power.rpt - {{get_override("script_before_bitstream")|default("# (script_before_bitstream placeholder)")}} - write_bitstream -force -bin_file {{name}}.bit - {{get_override("script_after_bitstream")|default("# (script_after_bitstream placeholder)")}} - quit - """, - "{{name}}.xdc": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - set_property LOC {{pin_name}} [get_ports {{port_name|tcl_escape}}] - {% for attr_name, attr_value in attrs.items() -%} - set_property {{attr_name}} {{attr_value|tcl_escape}} [get_ports {{port_name|tcl_escape}}] - {% endfor %} - {% endfor %} - {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} - {% if port_signal is not none -%} - create_clock -name {{port_signal.name|ascii_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}] - {% else -%} - create_clock -name {{net_signal.name|ascii_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}] - {% endif %} - {% endfor %} - {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} - """ - } - _vivado_command_templates = [ - r""" - {{invoke_tool("vivado")}} - {{verbose("-verbose")}} - {{get_override("vivado_opts")|options}} - -mode batch - -log {{name}}.log - -source {{name}}.tcl - """ - ] - - # ISE toolchain - - _ise_required_tools = [ - "xst", - "ngdbuild", - "map", - "par", - "bitgen", - ] - _ise_file_templates = { - **TemplatedPlatform.build_script_templates, - "build_{{name}}.sh": r""" - # {{autogenerated}} - set -e{{verbose("x")}} - if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi - {% for var in platform._all_toolchain_env_vars %} - [ -n "${{var}}" ] && . "${{var}}" - {% endfor %} - {{emit_commands("sh")}} - """, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.prj": r""" - # {{autogenerated}} - {% for file in platform.iter_files(".vhd", ".vhdl") -%} - vhdl work {{file}} - {% endfor %} - {% for file in platform.iter_files(".v") -%} - verilog work {{file}} - {% endfor %} - verilog work {{name}}.v - """, - "{{name}}.xst": r""" - # {{autogenerated}} - run - -ifn {{name}}.prj - -ofn {{name}}.ngc - -top {{name}} - -use_new_parser yes - -p {{platform.device}}{{platform.package}}-{{platform.speed}} - {{get_override("script_after_run")|default("# (script_after_run placeholder)")}} - """, - "{{name}}.ucf": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - {% set port_name = port_name|replace("[", "<")|replace("]", ">") -%} - NET "{{port_name}}" LOC={{pin_name}}; - {% for attr_name, attr_value in attrs.items() -%} - NET "{{port_name}}" {{attr_name}}={{attr_value}}; - {% endfor %} - {% endfor %} - {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} - NET "{{net_signal|hierarchy("/")}}" TNM_NET="PRD{{net_signal|hierarchy("/")}}"; - TIMESPEC "TS{{net_signal|hierarchy("__")}}"=PERIOD "PRD{{net_signal|hierarchy("/")}}" {{1000000000/frequency}} ns HIGH 50%; - {% endfor %} - {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} - """ - } - _ise_command_templates = [ - r""" - {{invoke_tool("xst")}} - {{get_override("xst_opts")|options}} - -ifn {{name}}.xst - """, - r""" - {{invoke_tool("ngdbuild")}} - {{quiet("-quiet")}} - {{verbose("-verbose")}} - {{get_override("ngdbuild_opts")|options}} - -uc {{name}}.ucf - {{name}}.ngc - """, - r""" - {{invoke_tool("map")}} - {{verbose("-detail")}} - {{get_override("map_opts")|default([])|options}} - -w - -o {{name}}_map.ncd - {{name}}.ngd - {{name}}.pcf - """, - r""" - {{invoke_tool("par")}} - {{get_override("par_opts")|default([])|options}} - -w - {{name}}_map.ncd - {{name}}_par.ncd - {{name}}.pcf - """, - r""" - {{invoke_tool("bitgen")}} - {{get_override("bitgen_opts")|default(["-g Compress"])|options}} - -w - -g Binary:Yes - {{name}}_par.ncd - {{name}}.bit - """ - ] - - # Symbiflow templates - - # symbiflow does not distinguish between speed grades - # TODO: join with _xray_part - @property - def _symbiflow_part(self): - # drop the trailing speed grade letter(s), if any - part = re.sub(r"[^\d]+$", "", self._part) - # drop temp/speed grade letters after family name, if any - part = re.sub(r"(.{4}\d+t)[il]", r"\1", part) - return part - - # bitstream device name according to prjxray-db path - # TODO: join with _xray_family - @property - def _symbiflow_bitstream_device(self): - if self._part.startswith("xc7a"): - return "artix7" - elif self._part.startswith("xc7k"): - return "kintex7" - elif self._part.startswith("xc7z"): - return "zynq7" - elif self._part.startswith("xc7s"): - return "spartan7" - else: - print("Unknown bitstream device for part {}".format(self._part)) - raise ValueError - - # device naming according to part_db.yml of f4pga project - @property - def _symbiflow_device(self): - if self._part.startswith("xc7a35") or self._part.startswith("xc7a50"): - return "xc7a50t_test" - elif self._part.startswith("xc7a100"): - return "xc7a100t_test" - elif self._part.startswith("xc7a200"): - return "xc7a200t_test" - else: - print("Unknown symbiflow device for part {}".format(self._part)) - raise ValueError - - - _symbiflow_required_tools = [ - "symbiflow_synth", - "symbiflow_pack", - "symbiflow_place", - "symbiflow_route", - "symbiflow_write_fasm", - "symbiflow_write_bitstream" - ] - _symbiflow_file_templates = { - **TemplatedPlatform.build_script_templates, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.pcf": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - set_io {{port_name}} {{pin_name}} - {% endfor %} - """, - "{{name}}.xdc": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - {% for attr_name, attr_value in attrs.items() -%} - set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }] - {% endfor %} - {% endfor %} - {{get_override("add_constraints")|default("# (add_constraints placeholder)")}} - """, - "{{name}}.sdc": r""" - # {{autogenerated}} - {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%} - {% if port_signal is none -%} - create_clock -period {{1000000000/frequency}} {{net_signal.name|ascii_escape}} - {% endif %} - {% endfor %} - """ - } - _symbiflow_command_templates = [ - r""" - {{invoke_tool("symbiflow_synth")}} - -t {{name}} - -v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v - -p {{platform._symbiflow_part}} - -d {{platform._symbiflow_bitstream_device}} - -x {{name}}.xdc - """, - r""" - {{invoke_tool("symbiflow_pack")}} - -e {{name}}.eblif - -d {{platform._symbiflow_device}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_place")}} - -e {{name}}.eblif - -p {{name}}.pcf - -n {{name}}.net - -P {{platform._symbiflow_part}} - -d {{platform._symbiflow_device}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_route")}} - -e {{name}}.eblif - -P {{platform._symbiflow_part}} - -d {{platform._symbiflow_device}} - -s {{name}}.sdc - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_write_fasm")}} - -e {{name}}.eblif - -P {{platform._symbiflow_part}} - -d {{platform._symbiflow_device}} - """, - r""" - {{invoke_tool("symbiflow_write_bitstream")}} - -f {{name}}.fasm - -p {{platform._symbiflow_part}} - -d {{platform._symbiflow_bitstream_device}} - -b {{name}}.bit - """ - ] - - # Yosys NextPNR prjxray templates - - @property - def _xray_part(self): - return { - "xc7a35ticsg324-1L": "xc7a35tcsg324-1", # Arty-A7 35t - "xc7a100ticsg324-1L": "xc7a100tcsg324-1", # Arty-A7 100t - }.get(self._part, self._part) - - @property - def _xray_device(self): - return { - "xc7a35ti": "xc7a35t", - "xc7a100ti": "xc7a100t", - }.get(self.device, self.device) - - @property - def _xray_family(self): - return { - "xc7a": "artix7", - "xc7z": "zynq7", - }[self.device[:4]] - - _xray_required_tools = [ - "yosys", - "nextpnr-xilinx", - "fasm2frames", - "xc7frames2bit" - ] - _xray_file_templates = { - **TemplatedPlatform.build_script_templates, - "build_{{name}}.sh": r""" - # {{autogenerated}} - set -e{{verbose("x")}} - {% for var in platform._all_toolchain_env_vars %} - [ -n "${{var}}" ] && . "${{var}}" - {% endfor %} - : ${DB_DIR:=/usr/share/nextpnr/prjxray-db} - : ${CHIPDB_DIR:=/usr/share/nextpnr/xilinx-chipdb} - {{emit_commands("sh")}} - """, - "{{name}}.v": r""" - /* {{autogenerated}} */ - {{emit_verilog()}} - """, - "{{name}}.debug.v": r""" - /* {{autogenerated}} */ - {{emit_debug_verilog()}} - """, - "{{name}}.xdc": r""" - # {{autogenerated}} - {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%} - {% for attr_name, attr_value in attrs.items() -%} - set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}}] - set_property LOC {{pin_name}} [get_ports {{port_name|tcl_escape}}] - {% endfor %} - {% endfor %} - """, - } - _xray_command_templates = [ - r""" - {{invoke_tool("yosys")}} - -p "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {{name}}; write_json {{name}}.json" - {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} - {{name}}.v - """, - r""" - {{invoke_tool("nextpnr-xilinx")}} - --chipdb $CHIPDB_DIR/{{platform._xray_device}}.bin - --xdc {{name}}.xdc - --json {{name}}.json - --write {{name}}_routed.json - --fasm {{name}}.fasm - """, - r""" - {{invoke_tool("fasm2frames")}} - --part {{platform._xray_part}} - --db-root $DB_DIR/{{platform._xray_family}} {{name}}.fasm > {{name}}.frames - """, - r""" - {{invoke_tool("xc7frames2bit")}} - --part_file $DB_DIR/{{platform._xray_family}}/{{platform._xray_part}}/part.yaml - --part_name {{platform._xray_part}} - --frm_file {{name}}.frames - --output_file {{name}}.bit - """, - ] - - # Common logic - - def __init__(self, *, toolchain=None): - super().__init__() - - # Determine device family. - device = self.device.lower() - # Remove the prefix. - if device.startswith("xc"): - device = device[2:] - elif device.startswith("xa"): - device = device[2:] - elif device.startswith("xqr"): - device = device[3:] - elif device.startswith("xq"): - device = device[2:] - else: - raise ValueError("Device '{}' is not recognized".format(self.device)) - # Do actual name matching. - if device.startswith("2vp"): - self.family = "virtex2p" - elif device.startswith("2v"): - self.family = "virtex2" - elif device.startswith("3sd"): - self.family = "spartan3adsp" - elif device.startswith("3s"): - if device.endswith("a"): - self.family = "spartan3a" - elif device.endswith("e"): - self.family = "spartan3e" - else: - self.family = "spartan3" - elif device.startswith("4v"): - self.family = "virtex4" - elif device.startswith("5v"): - self.family = "virtex5" - elif device.startswith("6v"): - self.family = "virtex6" - elif device.startswith("6s"): - self.family = "spartan6" - elif device.startswith("7"): - self.family = "series7" - elif device.startswith(("vu", "ku")): - if device.endswith("p"): - self.family = "ultrascaleplus" - else: - self.family = "ultrascale" - elif device.startswith(("zu", "u", "k26", "au")): - self.family = "ultrascaleplus" - elif device.startswith(("v", "2s")): - # Match last to avoid conflict with ultrascale. - # Yes, Spartan 2 is the same thing as Virtex. - if device.endswith("e"): - self.family = "virtexe" - else: - self.family = "virtex" - - - ISE_FAMILIES = { - "virtex", "virtexe", - "virtex2", "virtex2p", - "spartan3", "spartan3e", "spartan3a", "spartan3adsp", - "virtex4", - "virtex5", - "virtex6", - "spartan6", - } - if toolchain is None: - if self.family in ISE_FAMILIES: - toolchain = "ISE" - else: - toolchain = "Vivado" - - assert toolchain in ("Vivado", "ISE", "Symbiflow", "Xray") - if toolchain == "Vivado": - if self.family in ISE_FAMILIES: - raise ValueError("Family '{}' is not supported by the Vivado toolchain, please use ISE instead".format(self.family)) - elif toolchain == "ISE": - if self.family not in ISE_FAMILIES and self.family != "series7": - raise ValueError("Family '{}' is not supported by the ISE toolchain, please use Vivado instead".format(self.family)) - elif toolchain == "Symbiflow": - if self.family != "series7": - raise ValueError("Family '{}' is not supported by the Symbiflow toolchain".format(self.family)) - elif toolchain == "Xray": - if self.family != "series7": - raise ValueError("Family '{}' is not supported by the yosys nextpnr toolchain".format(self.family)) - - self.toolchain = toolchain - - @property - def required_tools(self): - if self.toolchain == "Vivado": - return self._vivado_required_tools - if self.toolchain == "ISE": - return self._ise_required_tools - if self.toolchain == "Symbiflow": - return self._symbiflow_required_tools - if self.toolchain == "Xray": - return self._xray_required_tools - assert False - - @property - def file_templates(self): - if self.toolchain == "Vivado": - return self._vivado_file_templates - if self.toolchain == "ISE": - return self._ise_file_templates - if self.toolchain == "Symbiflow": - return self._symbiflow_file_templates - if self.toolchain == "Xray": - return self._xray_file_templates - assert False - - @property - def command_templates(self): - if self.toolchain == "Vivado": - return self._vivado_command_templates - if self.toolchain == "ISE": - return self._ise_command_templates - if self.toolchain == "Symbiflow": - return self._symbiflow_command_templates - if self.toolchain == "Xray": - return self._xray_command_templates - assert False - - def create_missing_domain(self, name): - # Xilinx devices have a global write enable (GWE) signal that asserted during configuration - # and deasserted once it ends. Because it is an asynchronous signal (GWE is driven by logic - # synchronous to configuration clock, which is not used by most designs), even though it is - # a low-skew global network, its deassertion may violate a setup/hold constraint with - # relation to a user clock. The recommended solution is to use a BUFGCE driven by the EOS - # signal (if available). For details, see: - # * https://www.xilinx.com/support/answers/44174.html - # * https://www.xilinx.com/support/documentation/white_papers/wp272.pdf - - STARTUP_PRIMITIVE = { - "spartan6": "STARTUP_SPARTAN6", - "virtex4": "STARTUP_VIRTEX4", - "virtex5": "STARTUP_VIRTEX5", - "virtex6": "STARTUP_VIRTEX6", - "series7": "STARTUPE2", - "ultrascale": "STARTUPE3", - "ultrascaleplus": "STARTUPE3", - } - - if self.family not in STARTUP_PRIMITIVE or not self.vendor_toolchain: - # Spartan 3 and before lacks a STARTUP primitive with EOS output; use a simple ResetSynchronizer - # in that case, as is the default. - # Symbiflow does not support the STARTUPE2 primitive. - return super().create_missing_domain(name) - - if name == "sync" and self.default_clk is not None: - clk_i = self.request(self.default_clk).i - if self.default_rst is not None: - rst_i = self.request(self.default_rst).i - - m = Module() - ready = Signal() - m.submodules += Instance(STARTUP_PRIMITIVE[self.family], o_EOS=ready) - m.domains += ClockDomain("sync", reset_less=self.default_rst is None) - if self.toolchain != "Vivado": - m.submodules += Instance("BUFGCE", i_CE=ready, i_I=clk_i, o_O=ClockSignal("sync")) - elif self.family == "series7": - # Actually use BUFGCTRL configured as BUFGCE, since using BUFGCE causes - # sim/synth mismatches with Vivado 2019.2, and the suggested workaround - # (SIM_DEVICE parameter) breaks Vivado 2017.4. - m.submodules += Instance("BUFGCTRL", - p_SIM_DEVICE="7SERIES", - i_I0=clk_i, i_S0=C(1, 1), i_CE0=ready, i_IGNORE0=C(0, 1), - i_I1=C(1, 1), i_S1=C(0, 1), i_CE1=C(0, 1), i_IGNORE1=C(1, 1), - o_O=ClockSignal("sync") - ) - else: - m.submodules += Instance("BUFGCE", - p_SIM_DEVICE="ULTRASCALE", - i_CE=ready, - i_I=clk_i, - o_O=ClockSignal("sync") - ) - if self.default_rst is not None: - m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync") - return m - - def add_clock_constraint(self, clock, frequency): - super().add_clock_constraint(clock, frequency) - clock.attrs["keep"] = "TRUE" - - def _get_xdr_buffer(self, m, pin, iostd, *, i_invert=False, o_invert=False): - XFDDR_FAMILIES = { - "virtex2", - "virtex2p", - "spartan3", - } - XDDR2_FAMILIES = { - "spartan3e", - "spartan3a", - "spartan3adsp", - "spartan6", - } - XDDR_FAMILIES = { - "virtex4", - "virtex5", - "virtex6", - "series7", - } - XDDRE1_FAMILIES = { - "ultrascale", - "ultrascaleplus", - } - - def get_iob_dff(clk, d, q): - # SDR I/O is performed by packing a flip-flop into the pad IOB. - for bit in range(len(q)): - m.submodules += Instance("FDCE", - a_IOB="TRUE", - i_C=clk, - i_CE=Const(1), - i_CLR=Const(0), - i_D=d[bit], - o_Q=q[bit] - ) - - def get_dff(clk, d, q): - for bit in range(len(q)): - m.submodules += Instance("FDCE", - i_C=clk, - i_CE=Const(1), - i_CLR=Const(0), - i_D=d[bit], - o_Q=q[bit] - ) - - def get_ifddr(clk, io, q0, q1): - assert self.family in XFDDR_FAMILIES - for bit in range(len(q0)): - m.submodules += Instance("IFDDRCPE", - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_CLR=Const(0), i_PRE=Const(0), - i_D=io[bit], - o_Q0=q0[bit], o_Q1=q1[bit] - ) - - def get_iddr2(clk, d, q0, q1, alignment): - assert self.family in XDDR2_FAMILIES - for bit in range(len(q0)): - m.submodules += Instance("IDDR2", - p_DDR_ALIGNMENT=alignment, - p_SRTYPE="ASYNC", - p_INIT_Q0=C(0, 1), p_INIT_Q1=C(0, 1), - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D=d[bit], - o_Q0=q0[bit], o_Q1=q1[bit] - ) - - def get_iddr(clk, d, q1, q2): - assert self.family in XDDR_FAMILIES or self.family in XDDRE1_FAMILIES - for bit in range(len(q1)): - if self.family in XDDR_FAMILIES: - m.submodules += Instance("IDDR", - p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", - p_SRTYPE="ASYNC", - p_INIT_Q1=C(0, 1), p_INIT_Q2=C(0, 1), - i_C=clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D=d[bit], - o_Q1=q1[bit], o_Q2=q2[bit] - ) - else: - m.submodules += Instance("IDDRE1", - p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", - p_IS_C_INVERTED=C(0, 1), p_IS_CB_INVERTED=C(1, 1), - i_C=clk, i_CB=clk, - i_R=Const(0), - i_D=d[bit], - o_Q1=q1[bit], o_Q2=q2[bit] - ) - - def get_fddr(clk, d0, d1, q): - for bit in range(len(q)): - if self.family in XFDDR_FAMILIES: - m.submodules += Instance("FDDRCPE", - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_PRE=Const(0), i_CLR=Const(0), - i_D0=d0[bit], i_D1=d1[bit], - o_Q=q[bit] - ) - else: - m.submodules += Instance("ODDR2", - p_DDR_ALIGNMENT="NONE", - p_SRTYPE="ASYNC", - p_INIT=C(0, 1), - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D0=d0[bit], i_D1=d1[bit], - o_Q=q[bit] - ) - - def get_oddr(clk, d1, d2, q): - for bit in range(len(q)): - if self.family in XDDR2_FAMILIES: - m.submodules += Instance("ODDR2", - p_DDR_ALIGNMENT="C0", - p_SRTYPE="ASYNC", - p_INIT=C(0, 1), - i_C0=clk, i_C1=~clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D0=d1[bit], i_D1=d2[bit], - o_Q=q[bit] - ) - elif self.family in XDDR_FAMILIES: - m.submodules += Instance("ODDR", - p_DDR_CLK_EDGE="SAME_EDGE", - p_SRTYPE="ASYNC", - p_INIT=C(0, 1), - i_C=clk, - i_CE=Const(1), - i_S=Const(0), i_R=Const(0), - i_D1=d1[bit], i_D2=d2[bit], - o_Q=q[bit] - ) - elif self.family in XDDRE1_FAMILIES: - m.submodules += Instance("ODDRE1", - p_SRVAL=C(0, 1), - i_C=clk, - i_SR=Const(0), - i_D1=d1[bit], i_D2=d2[bit], - o_Q=q[bit] - ) - - def get_ineg(y, invert): - if invert: - a = Signal.like(y, name_suffix="_n") - m.d.comb += y.eq(~a) - return a - else: - return y - - def get_oneg(a, invert): - if invert: - y = Signal.like(a, name_suffix="_n") - m.d.comb += y.eq(~a) - return y - else: - return a - - if "i" in pin.dir: - if pin.xdr < 2: - pin_i = get_ineg(pin.i, i_invert) - elif pin.xdr == 2: - pin_i0 = get_ineg(pin.i0, i_invert) - pin_i1 = get_ineg(pin.i1, i_invert) - if "o" in pin.dir: - if pin.xdr < 2: - pin_o = get_oneg(pin.o, o_invert) - elif pin.xdr == 2: - pin_o0 = get_oneg(pin.o0, o_invert) - pin_o1 = get_oneg(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: - if "i" in pin.dir: - get_iob_dff(pin.i_clk, i, pin_i) - if "o" in pin.dir: - get_iob_dff(pin.o_clk, pin_o, o) - if pin.dir in ("oe", "io"): - get_iob_dff(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 2: - # On Spartan 3E/3A, the situation with DDR registers is messy: while the hardware - # supports same-edge alignment, it does so by borrowing the resources of the other - # pin in the differential pair (if any). Since we cannot be sure if the other pin - # is actually unused (or if the pin is even part of a differential pair in the first - # place), we only use the hardware alignment feature in two cases: - # - # - differential inputs (since the other pin's input registers will be unused) - # - true differential outputs (since they use only one pin's output registers, - # as opposed to pseudo-differential outputs that use both) - TRUE_DIFF_S3EA = { - "LVDS_33", "LVDS_25", - "MINI_LVDS_33", "MINI_LVDS_25", - "RSDS_33", "RSDS_25", - "PPDS_33", "PPDS_25", - "TMDS_33", - } - DIFF_S3EA = TRUE_DIFF_S3EA | { - "DIFF_HSTL_I", - "DIFF_HSTL_III", - "DIFF_HSTL_I_18", - "DIFF_HSTL_II_18", - "DIFF_HSTL_III_18", - "DIFF_SSTL3_I", - "DIFF_SSTL3_II", - "DIFF_SSTL2_I", - "DIFF_SSTL2_II", - "DIFF_SSTL18_I", - "DIFF_SSTL18_II", - "BLVDS_25", - } - if "i" in pin.dir: - if self.family in XFDDR_FAMILIES: - # First-generation input DDR register: basically just two FFs with opposite - # clocks. Add a register on both outputs, so that they enter fabric on - # the same clock edge, adding one cycle of latency. - i0_ff = Signal.like(pin_i0, name_suffix="_ff") - i1_ff = Signal.like(pin_i1, name_suffix="_ff") - get_dff(pin.i_clk, i0_ff, pin_i0) - get_dff(pin.i_clk, i1_ff, pin_i1) - get_iob_dff(pin.i_clk, i, i0_ff) - get_iob_dff(~pin.i_clk, i, i1_ff) - elif self.family in XDDR2_FAMILIES: - if self.family == 'spartan6' or iostd in DIFF_S3EA: - # Second-generation input DDR register: hw realigns i1 to positive clock edge, - # but also misaligns it with i0 input. Re-register first input before it - # enters fabric. This allows both inputs to enter fabric on the same clock - # edge, and adds one cycle of latency. - i0_ff = Signal.like(pin_i0, name_suffix="_ff") - get_dff(pin.i_clk, i0_ff, pin_i0) - get_iddr2(pin.i_clk, i, i0_ff, pin_i1, "C0") - else: - # No extra register available for hw alignment, use extra registers. - i0_ff = Signal.like(pin_i0, name_suffix="_ff") - i1_ff = Signal.like(pin_i1, name_suffix="_ff") - get_dff(pin.i_clk, i0_ff, pin_i0) - get_dff(pin.i_clk, i1_ff, pin_i1) - get_iddr2(pin.i_clk, i, i0_ff, i1_ff, "NONE") - else: - # Third-generation input DDR register: does all of the above on its own. - get_iddr(pin.i_clk, i, pin_i0, pin_i1) - if "o" in pin.dir: - if self.family in XFDDR_FAMILIES or self.family == "spartan3e" or (self.family.startswith("spartan3a") and iostd not in TRUE_DIFF_S3EA): - # For this generation, we need to realign o1 input ourselves. - o1_ff = Signal.like(pin_o1, name_suffix="_ff") - get_dff(pin.o_clk, pin_o1, o1_ff) - get_fddr(pin.o_clk, pin_o0, o1_ff, o) - else: - get_oddr(pin.o_clk, pin_o0, pin_o1, o) - if pin.dir in ("oe", "io"): - if self.family == "spartan6": - get_oddr(pin.o_clk, ~pin.oe, ~pin.oe, t) - else: - get_iob_dff(pin.o_clk, ~pin.oe, t) - else: - assert False - - return (i, o, t) - - def _get_valid_xdrs(self): - if self.family in {"virtex", "virtexe"}: - return (0, 1) - else: - return (0, 1, 2) - - def get_input(self, pin, port, attrs, invert): - self._check_feature("single-ended input", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF", - i_I=port.io[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=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert) - if self.vendor_toolchain: - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF", - i_I=o[bit], - o_O=port.io[bit] - ) - else: - m.d.comb += port.eq(self._invert_if(invert, o)) - return m - - def get_tristate(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_tristate(pin, port, attrs, invert) - - self._check_feature("single-ended tristate", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFT", - i_T=t, - i_I=o[bit], - o_O=port.io[bit] - ) - return m - - def get_input_output(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_input_output(pin, port, attrs, invert) - - self._check_feature("single-ended input/output", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF", - i_T=t, - i_I=o[bit], - o_O=i[bit], - io_IO=port.io[bit] - ) - return m - - def get_diff_input(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_input(pin, port, attrs, invert) - - self._check_feature("differential input", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUFDS", - i_I=port.p[bit], i_IB=port.n[bit], - o_O=i[bit] - ) - return m - - def get_diff_output(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_output(pin, port, attrs, invert) - - self._check_feature("differential output", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFDS", - i_I=o[bit], - o_O=port.p[bit], o_OB=port.n[bit] - ) - return m - - def get_diff_tristate(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_tristate(pin, port, attrs, invert) - - self._check_feature("differential tristate", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFTDS", - i_T=t, - i_I=o[bit], - o_O=port.p[bit], o_OB=port.n[bit] - ) - return m - - def get_diff_input_output(self, pin, port, attrs, invert): - if not self.vendor_toolchain: - return super().get_diff_input_output(pin, port, attrs, invert) - - self._check_feature("differential input/output", pin, attrs, - valid_xdrs=self._get_valid_xdrs(), valid_attrs=True) - m = Module() - i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert, o_invert=invert) - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUFDS", - i_T=t, - i_I=o[bit], - o_O=i[bit], - io_IO=port.p[bit], io_IOB=port.n[bit] - ) - return m - - # The synchronizer implementations below apply two separate but related timing constraints. - # - # First, the ASYNC_REG attribute prevents inference of shift registers from synchronizer FFs, - # and constraints the FFs to be placed as close as possible, ideally in one CLB. This attribute - # only affects the synchronizer FFs themselves. - # - # Second, for Vivado only, the amaranth.vivado.false_path or amaranth.vivado.max_delay attribute - # affects the path into the synchronizer. If maximum input delay is specified, a datapath-only - # maximum delay constraint is applied, limiting routing delay (and therefore skew) at - # the synchronizer input. Otherwise, a false path constraint is used to omit the input path - # from the timing analysis. - - def get_ff_sync(self, ff_sync): - m = Module() - flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index), - reset=ff_sync._reset, reset_less=ff_sync._reset_less, - attrs={"ASYNC_REG": "TRUE"}) - for index in range(ff_sync._stages)] - if self.toolchain == "Vivado": - if ff_sync._max_input_delay is None: - flops[0].attrs["amaranth.vivado.false_path"] = "TRUE" - else: - flops[0].attrs["amaranth.vivado.max_delay"] = str(ff_sync._max_input_delay * 1e9) - elif ff_sync._max_input_delay is not None: - raise NotImplementedError("Platform '{}' does not support constraining input delay " - "for FFSynchronizer" - .format(type(self).__name__)) - for i, o in zip((ff_sync.i, *flops), flops): - m.d[ff_sync._o_domain] += o.eq(i) - m.d.comb += ff_sync.o.eq(flops[-1]) - return m - - - def get_async_ff_sync(self, async_ff_sync): - m = Module() - m.domains += ClockDomain("async_ff", async_reset=True, local=True) - flops = [Signal(1, name="stage{}".format(index), reset=1, - attrs={"ASYNC_REG": "TRUE"}) - for index in range(async_ff_sync._stages)] - if self.toolchain == "Vivado": - if async_ff_sync._max_input_delay is None: - flops[0].attrs["amaranth.vivado.false_path"] = "TRUE" - else: - flops[0].attrs["amaranth.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9) - elif async_ff_sync._max_input_delay is not None: - raise NotImplementedError("Platform '{}' does not support constraining input delay " - "for AsyncFFSynchronizer" - .format(type(self).__name__)) - for i, o in zip((0, *flops), flops): - m.d.async_ff += o.eq(i) - - if async_ff_sync._edge == "pos": - m.d.comb += ResetSignal("async_ff").eq(async_ff_sync.i) - else: - m.d.comb += ResetSignal("async_ff").eq(~async_ff_sync.i) - - m.d.comb += [ - ClockSignal("async_ff").eq(ClockSignal(async_ff_sync._o_domain)), - async_ff_sync.o.eq(flops[-1]) - ] - - return m +def __getattr__(name): + if name in ("XilinxPlatform",): + warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}", + DeprecationWarning, stacklevel=2) + return getattr(vendor, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/docs/changes.rst b/docs/changes.rst index 66e278e..02078b7 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -25,6 +25,7 @@ Apply the following changes to code written against Amaranth 0.3 to migrate it t * Update shell environment to use ``AMARANTH_*`` environment variables instead of ``NMIGEN_*`` environment variables. * Update shell environment to use ``AMARANTH_ENV_`` (with all-uppercase ```` name) environment variable names instead of ``AMARANTH_ENV_`` or ``NMIGEN_ENV_`` (with mixed-case ```` name). +* Update imports of the form ``from amaranth.vendor.some_vendor import SomeVendorPlatform`` to ``from amaranth.vendor import SomeVendorPlatform``. This change will reduce future churn. While code that uses the features listed as deprecated below will work in Amaranth 0.4, they will be removed in the next version. @@ -42,6 +43,7 @@ Implemented RFCs .. _RFC 9: https://amaranth-lang.org/rfcs/0009-const-init-shape-castable.html .. _RFC 10: https://amaranth-lang.org/rfcs/0010-move-repl-to-value.html .. _RFC 15: https://amaranth-lang.org/rfcs/0015-lifting-shape-castables.html +.. _RFC 18: https://amaranth-lang.org/rfcs/0018-reorganize-vendor-platforms.html .. _RFC 22: https://amaranth-lang.org/rfcs/0022-valuecastable-shape.html * `RFC 1`_: Aggregate data structure library @@ -54,6 +56,7 @@ Implemented RFCs * `RFC 8`_: Aggregate extensibility * `RFC 9`_: Constant initialization for shape-castable objects * `RFC 10`_: Move ``Repl`` to ``Value.replicate`` +* `RFC 18`_: Reorganize vendor platforms * `RFC 15`_: Lifting shape-castable objects * `RFC 22`_: Define ``ValueCastable.shape()`` @@ -110,9 +113,10 @@ Platform integration changes .. currentmodule:: amaranth.vendor -* Added: ``OSCH`` as ``default_clk`` clock source in :class:`vendor.lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform`. -* Added: Xray toolchain support in :class:`vendor.xilinx.XilinxPlatform`. -* Added: :class:`vendor.gowin.GowinPlatform`. +* Added: ``OSCH`` as ``default_clk`` clock source in :class:`vendor.LatticeMachXO2Platform`, :class:`vendor.LatticeMachXO3LPlatform`. +* Added: Xray toolchain support in :class:`vendor.XilinxPlatform`. +* Added: :class:`vendor.GowinPlatform`. +* Deprecated: :mod:`vendor.intel`, :mod:`vendor.lattice_ecp5`, :mod:`vendor.lattice_ice40`, :mod:`vendor.lattice_machxo2_3l`, :mod:`vendor.quicklogic`, :mod:`vendor.xilinx`; import platforms directly from :mod:`vendor` instead. (`RFC 18`_) * Removed: (deprecated in 0.3) :mod:`lattice_machxo2` * Removed: (deprecated in 0.3) :class:`lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform` SVF programming vector ``{{name}}.svf``. * Removed: (deprecated in 0.3) :class:`xilinx_spartan_3_6.XilinxSpartan3APlatform`, :class:`xilinx_spartan_3_6.XilinxSpartan6Platform`, :class:`xilinx_7series.Xilinx7SeriesPlatform`, :class:`xilinx_ultrascale.XilinxUltrascalePlatform`. diff --git a/docs/platform/gowin.rst b/docs/platform/gowin.rst index 7abeee4..19da49d 100644 --- a/docs/platform/gowin.rst +++ b/docs/platform/gowin.rst @@ -1,9 +1,9 @@ Gowin ##### -.. py:module:: amaranth.vendor.gowin +.. currentmodule:: amaranth.vendor -The :mod:`amaranth.vendor.gowin` module provides a base platform to support Gowin toolchains. +The :class:`GowinPlatform` class provides a base platform to support Gowin toolchains. The Apicula and Gowin toolchains are supported. diff --git a/docs/platform/intel.rst b/docs/platform/intel.rst index 2b419e0..940cfa1 100644 --- a/docs/platform/intel.rst +++ b/docs/platform/intel.rst @@ -1,9 +1,9 @@ Intel ##### -.. py:module:: amaranth.vendor.intel +.. currentmodule:: amaranth.vendor -The :mod:`amaranth.vendor.intel` module provides a base platform to support Intel toolchains. +The :class:`IntelPlatform` class provides a base platform to support Intel toolchains. The Quartus and Mistral toolchains are supported. diff --git a/docs/platform/lattice-ecp5.rst b/docs/platform/lattice-ecp5.rst index 5f10e4a..38dc10c 100644 --- a/docs/platform/lattice-ecp5.rst +++ b/docs/platform/lattice-ecp5.rst @@ -1,9 +1,9 @@ Lattice ECP5 ############ -.. py:module:: amaranth.vendor.lattice_ecp5 +.. currentmodule:: amaranth.vendor -The :mod:`amaranth.vendor.lattice_ecp5` module provides a base platform to support Lattice ECP5 devices. +The :class:`LatticeECP5Platform` class provides a base platform to support Lattice ECP5 devices. The Trellis and Diamond toolchains are supported. diff --git a/docs/platform/lattice-ice40.rst b/docs/platform/lattice-ice40.rst index 6f9ba17..9c7d07f 100644 --- a/docs/platform/lattice-ice40.rst +++ b/docs/platform/lattice-ice40.rst @@ -1,9 +1,9 @@ Lattice iCE40 ############# -.. py:module:: amaranth.vendor.lattice_ice40 +.. currentmodule:: amaranth.vendor -The :mod:`amaranth.vendor.lattice_ice40` module provides a base platform to support Lattice iCE40 devices. +The :class:`LatticeICE40Platform` class provides a base platform to support Lattice iCE40 devices. The IceStorm and iCECube2 toolchains are supported. diff --git a/docs/platform/lattice-machxo-2-3l.rst b/docs/platform/lattice-machxo-2-3l.rst index da23e1d..790138d 100644 --- a/docs/platform/lattice-machxo-2-3l.rst +++ b/docs/platform/lattice-machxo-2-3l.rst @@ -1,19 +1,12 @@ Lattice MachXO2 and MachXO3L ############################ -.. py:module:: amaranth.vendor.lattice_machxo_2_3l +.. currentmodule:: amaranth.vendor -The :mod:`amaranth.vendor.lattice_machxo_2_3l` module provides a base platform to support Lattice -MachXO2 and MachXO3L devices. +The :class:`LatticeMachXO2Platform` and :class:`LatticeMachXO3LPlatform` classes provide base platforms to support Lattice MachXO2 and MachXO3L devices. The Diamond toolchain is supported. - - +.. autoclass:: amaranth.vendor._lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform .. autoclass:: LatticeMachXO2Platform .. autoclass:: LatticeMachXO3LPlatform - -.. note:: Both of the above are aliases for the actual platform below, however only the aliased - definitions are actually exported from the module for use. - -.. autoclass:: LatticeMachXO2Or3LPlatform diff --git a/docs/platform/quicklogic.rst b/docs/platform/quicklogic.rst index 94c9f5c..700f2a2 100644 --- a/docs/platform/quicklogic.rst +++ b/docs/platform/quicklogic.rst @@ -1,9 +1,9 @@ Quicklogic ########## -.. py:module:: amaranth.vendor.quicklogic +.. currentmodule:: amaranth.vendor -The :mod:`amaranth.vendor.quicklogic` module provides a base platform to support Quicklogic toolchains. +The :class:`QuicklogicPlatform` class provides a base platform to support Quicklogic toolchains. The Symbiflow toolchain is supported. diff --git a/docs/platform/xilinx.rst b/docs/platform/xilinx.rst index 2c6d384..ac0468a 100644 --- a/docs/platform/xilinx.rst +++ b/docs/platform/xilinx.rst @@ -1,9 +1,9 @@ Xilinx ###### -.. py:module:: amaranth.vendor.xilinx +.. currentmodule:: amaranth.vendor -The :mod:`amaranth.vendor.xilinx` module provides a base platform to support Xilinx toolchains. +The :class:`XilinxPlatform` class provides a base platform to support Xilinx toolchains. The ISE, Vivado, and Symbiflow toolchains are supported.