From: Marcelina Koƛcielnicka Date: Wed, 16 Dec 2020 15:35:57 +0000 (+0100) Subject: Unify Xilinx platforms into a single class, support more devices X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=bdbe8bff271e65bf87b3cb5442b762798c254066;p=nmigen.git Unify Xilinx platforms into a single class, support more devices This merges existing code, and also adds support for: - Virtex, Virtex E (also known as Spartan 2, Spartan 2E) - Virtex 2, Virtex 2 Pro - Spartan 3, Spartan 3E (in addition to existing Spartan 3A, Spartan 3A DSP support) - Virtex 4 - Virtex 5 - Virtex 6 - ISE synthesis for Series 7 Fixes #552. --- diff --git a/nmigen/vendor/xilinx.py b/nmigen/vendor/xilinx.py new file mode 100644 index 0000000..be8d377 --- /dev/null +++ b/nmigen/vendor/xilinx.py @@ -0,0 +1,1060 @@ +from abc import abstractproperty + +from ..hdl import * +from ..lib.cdc import ResetSynchronizer +from ..build import * + + +__all__ = ["XilinxPlatform"] + + +class XilinxPlatform(TemplatedPlatform): + """ + Vivado toolchain + ---------------- + + Required tools: + * ``vivado`` + + The environment is populated by running the script specified in the environment variable + ``NMIGEN_ENV_Vivado``, if present. + + Available overrides: + * ``script_after_read``: inserts commands after ``read_xdc`` in Tcl script. + * ``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. + + ISE toolchain + ------------- + + Required tools: + * ``xst`` + * ``ngdbuild`` + * ``map`` + * ``par`` + * ``bitgen`` + + The environment is populated by running the script specified in the environment variable + ``NMIGEN_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. + + 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 + ``NMIGEN_ENV_Symbiflow``, if present. + + Available overrides: + * ``add_constraints``: inserts commands in XDC file. + """ + + toolchain = None # selected when creating platform + + device = abstractproperty() + package = abstractproperty() + speed = abstractproperty() + + @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) + + # 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 + [ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}" + {{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}} + foreach cell [get_cells -quiet -hier -filter {nmigen.vivado.false_path == "TRUE"}] { + set_false_path -to $cell + } + foreach cell [get_cells -quiet -hier -filter {nmigen.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 nmigen.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 + [ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}" + {{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_part_map = { + "xc7a35ticsg324-1L": "xc7a35tcsg324-1", # Arty-A7 + } + + _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_map.get(platform._part, platform._part)}} + -x {{name}}.xdc + """, + r""" + {{invoke_tool("symbiflow_pack")}} + -e {{name}}.eblif + -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_place")}} + -e {{name}}.eblif + -p {{name}}.pcf + -n {{name}}.net + -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_route")}} + -e {{name}}.eblif + -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} + -s {{name}}.sdc + """, + r""" + {{invoke_tool("symbiflow_write_fasm")}} + -e {{name}}.eblif + -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} + """, + r""" + {{invoke_tool("symbiflow_write_bitstream")}} + -f {{name}}.fasm + -p {{platform._symbiflow_part_map.get(platform._part, platform._part)}} + -b {{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")): + 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") + 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)) + 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 + 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 + 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 + assert False + + def create_missing_domain(self, name): + # Xilinx devices have a global write enable (GWE) signal that asserted during configuraiton + # and deasserted once it ends. Because it is an asynchronous signal (GWE is driven by logic + # syncronous 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 self.toolchain == "Symbiflow": + # 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.toolchain != "Symbiflow": + 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 self.toolchain == "Symbiflow": + 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 self.toolchain == "Symbiflow": + 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 self.toolchain == "Symbiflow": + 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 self.toolchain == "Symbiflow": + 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 self.toolchain == "Symbiflow": + 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 self.toolchain == "Symbiflow": + 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 nmigen.vivado.false_path or nmigen.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["nmigen.vivado.false_path"] = "TRUE" + else: + flops[0].attrs["nmigen.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["nmigen.vivado.false_path"] = "TRUE" + else: + flops[0].attrs["nmigen.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/nmigen/vendor/xilinx_7series.py b/nmigen/vendor/xilinx_7series.py index cfb1b44..85a3650 100644 --- a/nmigen/vendor/xilinx_7series.py +++ b/nmigen/vendor/xilinx_7series.py @@ -1,621 +1,5 @@ -from abc import abstractproperty - -from ..hdl import * -from ..lib.cdc import ResetSynchronizer -from ..build import * - +from .xilinx import XilinxPlatform __all__ = ["Xilinx7SeriesPlatform"] - -class Xilinx7SeriesPlatform(TemplatedPlatform): - """ - Vivado toolchain - ---------------- - - Required tools: - * ``vivado`` - - The environment is populated by running the script specified in the environment variable - ``NMIGEN_ENV_Vivado``, if present. - - Available overrides: - * ``script_after_read``: inserts commands after ``read_xdc`` in Tcl script. - * ``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. - - 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 - ``NMIGEN_ENV_Symbiflow``, if present. - - Available overrides: - * ``add_constraints``: inserts commands in XDC file. - """ - - toolchain = None # selected when creating platform - - device = abstractproperty() - package = abstractproperty() - speed = abstractproperty() - - @property - def _part(self): - return "{}{}-{}".format(self.device, self.package, self.speed) - - # 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 - [ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}" - {{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}} - foreach cell [get_cells -quiet -hier -filter {nmigen.vivado.false_path == "TRUE"}] { - set_false_path -to $cell - } - foreach cell [get_cells -quiet -hier -filter {nmigen.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 nmigen.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 - """ - ] - - # Symbiflow templates - - _symbiflow_part_map = { - "xc7a35ticsg324-1L": "xc7a35tcsg324-1", # Arty-A7 - } - - _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_map.get(platform._part, platform._part)}} - -x {{name}}.xdc - """, - r""" - {{invoke_tool("symbiflow_pack")}} - -e {{name}}.eblif - -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_place")}} - -e {{name}}.eblif - -p {{name}}.pcf - -n {{name}}.net - -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_route")}} - -e {{name}}.eblif - -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} - -s {{name}}.sdc - """, - r""" - {{invoke_tool("symbiflow_write_fasm")}} - -e {{name}}.eblif - -P {{platform._symbiflow_part_map.get(platform._part, platform._part)}} - """, - r""" - {{invoke_tool("symbiflow_write_bitstream")}} - -f {{name}}.fasm - -p {{platform._symbiflow_part_map.get(platform._part, platform._part)}} - -b {{name}}.bit - """ - ] - - # Common logic - - def __init__(self, *, toolchain="Vivado"): - super().__init__() - - assert toolchain in ("Vivado", "Symbiflow") - self.toolchain = toolchain - - @property - def required_tools(self): - if self.toolchain == "Vivado": - return self._vivado_required_tools - if self.toolchain == "Symbiflow": - return self._symbiflow_required_tools - assert False - - @property - def file_templates(self): - if self.toolchain == "Vivado": - return self._vivado_file_templates - if self.toolchain == "Symbiflow": - return self._symbiflow_file_templates - assert False - - @property - def command_templates(self): - if self.toolchain == "Vivado": - return self._vivado_command_templates - if self.toolchain == "Symbiflow": - return self._symbiflow_command_templates - assert False - - def create_missing_domain(self, name): - # Xilinx devices have a global write enable (GWE) signal that asserted during configuraiton - # and deasserted once it ends. Because it is an asynchronous signal (GWE is driven by logic - # syncronous 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. For details, see: - # * https://www.xilinx.com/support/answers/44174.html - # * https://www.xilinx.com/support/documentation/white_papers/wp272.pdf - 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() - - if self.toolchain == "Vivado": - ready = Signal() - m.submodules += Instance("STARTUPE2", o_EOS=ready) - m.domains += ClockDomain("sync", reset_less=self.default_rst is None) - # 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") - ) - elif self.toolchain == "Symbiflow": - cd_sync = ClockDomain("sync", reset_less=self.default_rst is None) - m.domains += cd_sync - m.submodules += Instance("BUFG", i_I=clk_i, o_O=cd_sync.clk) - self.add_clock_constraint(cd_sync.clk, self.default_clk_frequency) - else: - assert False - - 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, *, i_invert=False, o_invert=False): - def get_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_iddr(clk, d, q1, q2): - for bit in range(len(q1)): - m.submodules += Instance("IDDR", - p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", - p_SRTYPE="ASYNC", - p_INIT_Q1=0, p_INIT_Q2=0, - 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] - ) - - def get_oddr(clk, d1, d2, q): - for bit in range(len(q)): - m.submodules += Instance("ODDR", - p_DDR_CLK_EDGE="SAME_EDGE", - p_SRTYPE="ASYNC", - p_INIT=0, - 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] - ) - - 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_dff(pin.i_clk, i, pin_i) - if "o" in pin.dir: - get_dff(pin.o_clk, pin_o, o) - if pin.dir in ("oe", "io"): - get_dff(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_dff(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(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, o_invert=invert) - if self.toolchain == "Vivado": - for bit in range(pin.width): - m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF", - i_I=o[bit], - o_O=port.io[bit] - ) - elif self.toolchain == "Symbiflow": - m.d.comb += port.eq(self._invert_if(invert, o)) - else: - assert False - return m - - def get_tristate(self, pin, port, attrs, invert): - if self.toolchain == "Symbiflow": - return super().get_tristate(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("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 self.toolchain == "Symbiflow": - return super().get_input_output(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_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 self.toolchain == "Symbiflow": - return super().get_diff_input(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("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 self.toolchain == "Symbiflow": - return super().get_diff_output(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("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 self.toolchain == "Symbiflow": - return super().get_diff_tristate(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("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 self.toolchain == "Symbiflow": - return super().get_diff_input_output(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("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, the nmigen.vivado.false_path or nmigen.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 ff_sync._max_input_delay is None: - flops[0].attrs["nmigen.vivado.false_path"] = "TRUE" - else: - flops[0].attrs["nmigen.vivado.max_delay"] = str(ff_sync._max_input_delay * 1e9) - 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 async_ff_sync._max_input_delay is None: - flops[0].attrs["nmigen.vivado.false_path"] = "TRUE" - else: - flops[0].attrs["nmigen.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9) - 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 +Xilinx7SeriesPlatform = XilinxPlatform diff --git a/nmigen/vendor/xilinx_spartan_3_6.py b/nmigen/vendor/xilinx_spartan_3_6.py index 21cbda0..ced94ed 100644 --- a/nmigen/vendor/xilinx_spartan_3_6.py +++ b/nmigen/vendor/xilinx_spartan_3_6.py @@ -1,466 +1,6 @@ -from abc import abstractproperty - -from ..hdl import * -from ..lib.cdc import ResetSynchronizer -from ..build import * - +from .xilinx import XilinxPlatform __all__ = ["XilinxSpartan3APlatform", "XilinxSpartan6Platform"] - -# The interface to Spartan 3 and 6 are substantially the same. Handle -# differences internally using one class and expose user-aliases for -# convenience. -class XilinxSpartan3Or6Platform(TemplatedPlatform): - """ - Required tools: - * ISE toolchain: - * ``xst`` - * ``ngdbuild`` - * ``map`` - * ``par`` - * ``bitgen`` - - The environment is populated by running the script specified in the environment variable - ``NMIGEN_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. - """ - - toolchain = "ISE" - - device = abstractproperty() - package = abstractproperty() - speed = abstractproperty() - - required_tools = [ - "xst", - "ngdbuild", - "map", - "par", - "bitgen", - ] - - @property - def family(self): - device = self.device.upper() - if device.startswith("XC3S"): - if device.endswith("A"): - return "3A" - elif device.endswith("E"): - raise NotImplementedError("""Spartan 3E family is not supported - as a nMigen platform.""") - else: - raise NotImplementedError("""Spartan 3 family is not supported - as a nMigen platform.""") - elif device.startswith("XC6S"): - return "6" - else: - assert False - - file_templates = { - **TemplatedPlatform.build_script_templates, - "build_{{name}}.sh": r""" - # {{autogenerated}} - set -e{{verbose("x")}} - if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi - [ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}" - {{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}} - {% if platform.family in ["3", "3E", "3A"] %} - -use_new_parser yes - {% endif %} - -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)")}} - """ - } - 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 - """ - ] - - def create_missing_domain(self, name): - # Xilinx devices have a global write enable (GWE) signal that asserted during configuraiton - # and deasserted once it ends. Because it is an asynchronous signal (GWE is driven by logic - # syncronous 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 - if self.family != "6": - # Spartan 3 lacks a STARTUP primitive with EOS output; use a simple ResetSynchronizer - # in that case, as is the default. - 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() - eos = Signal() - m.submodules += Instance("STARTUP_SPARTAN6", o_EOS=eos) - m.domains += ClockDomain("sync", reset_less=self.default_rst is None) - m.submodules += Instance("BUFGCE", i_CE=eos, 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 _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False): - def get_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_iddr(clk, d, q0, q1): - for bit in range(len(q0)): - m.submodules += Instance("IDDR2", - p_DDR_ALIGNMENT="C0", - p_SRTYPE="ASYNC", - p_INIT_Q0=0, p_INIT_Q1=0, - 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_oddr(clk, d0, d1, q): - for bit in range(len(q)): - m.submodules += Instance("ODDR2", - p_DDR_ALIGNMENT="C0", - p_SRTYPE="ASYNC", - p_INIT=0, - 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_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_dff(pin.i_clk, i, pin_i) - if "o" in pin.dir: - get_dff(pin.o_clk, pin_o, o) - if pin.dir in ("oe", "io"): - get_dff(pin.o_clk, ~pin.oe, t) - elif pin.xdr == 2: - if "i" in pin.dir: - # 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_iddr(pin.i_clk, i, i0_ff, pin_i1) - if "o" in pin.dir: - get_oddr(pin.o_clk, pin_o0, pin_o1, o) - if pin.dir in ("oe", "io"): - get_dff(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(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, 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("OBUFT", - 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(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): - 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("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): - 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("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): - 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("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): - 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("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 the ASYNC_REG attribute. This 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. - - def get_ff_sync(self, ff_sync): - if ff_sync._max_input_delay is not None: - raise NotImplementedError("Platform '{}' does not support constraining input delay " - "for FFSynchronizer" - .format(type(self).__name__)) - - 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)] - 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): - if async_ff_sync._max_input_delay is not None: - raise NotImplementedError("Platform '{}' does not support constraining input delay " - "for AsyncFFSynchronizer" - .format(type(self).__name__)) - - 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)] - 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 - -XilinxSpartan3APlatform = XilinxSpartan3Or6Platform -XilinxSpartan6Platform = XilinxSpartan3Or6Platform +XilinxSpartan3APlatform = XilinxPlatform +XilinxSpartan6Platform = XilinxPlatform diff --git a/nmigen/vendor/xilinx_ultrascale.py b/nmigen/vendor/xilinx_ultrascale.py index 1a2d391..92aace0 100644 --- a/nmigen/vendor/xilinx_ultrascale.py +++ b/nmigen/vendor/xilinx_ultrascale.py @@ -1,434 +1,5 @@ -from abc import abstractproperty - -from ..hdl import * -from ..lib.cdc import ResetSynchronizer -from ..build import * - +from .xilinx import XilinxPlatform __all__ = ["XilinxUltraScalePlatform"] - -class XilinxUltraScalePlatform(TemplatedPlatform): - """ - Required tools: - * ``vivado`` - - The environment is populated by running the script specified in the environment variable - ``NMIGEN_ENV_Vivado``, if present. - - Available overrides: - * ``script_after_read``: inserts commands after ``read_xdc`` in Tcl script. - * ``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. - """ - - toolchain = "Vivado" - - device = abstractproperty() - package = abstractproperty() - speed = abstractproperty() - - required_tools = ["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 - [ -n "${{platform._toolchain_env_var}}" ] && . "${{platform._toolchain_env_var}}" - {{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.device}}-{{platform.package}}-{{platform.speed}} - {% 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}} - foreach cell [get_cells -quiet -hier -filter {nmigen.vivado.false_path == "TRUE"}] { - set_false_path -to $cell - } - foreach cell [get_cells -quiet -hier -filter {nmigen.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 nmigen.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)")}} - """ - } - command_templates = [ - r""" - {{invoke_tool("vivado")}} - {{verbose("-verbose")}} - {{get_override("vivado_opts")|options}} - -mode batch - -log {{name}}.log - -source {{name}}.tcl - """ - ] - - def create_missing_domain(self, name): - # Xilinx devices have a global write enable (GWE) signal that asserted during configuraiton - # and deasserted once it ends. Because it is an asynchronous signal (GWE is driven by logic - # syncronous 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. For details, see: - # * https://www.xilinx.com/support/answers/44174.html - # * https://www.xilinx.com/support/documentation/white_papers/wp272.pdf - 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("STARTUPE3", o_EOS=ready) - m.domains += ClockDomain("sync", reset_less=self.default_rst is None) - 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, *, i_invert=False, o_invert=False): - def get_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_iddr(clk, d, q1, q2): - for bit in range(len(q1)): - m.submodules += Instance("IDDRE1", - p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED", - p_IS_C_INVERTED=0, p_IS_CB_INVERTED=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_oddr(clk, d1, d2, q): - for bit in range(len(q)): - m.submodules += Instance("ODDRE1", - p_DDR_CLK_EDGE="SAME_EDGE", - p_INIT=0, - 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_dff(pin.i_clk, i, pin_i) - if "o" in pin.dir: - get_dff(pin.o_clk, pin_o, o) - if pin.dir in ("oe", "io"): - get_dff(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_dff(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(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, 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("OBUFT", - 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(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): - 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("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): - 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("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): - 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("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): - 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("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, the nmigen.vivado.false_path or nmigen.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 ff_sync._max_input_delay is None: - flops[0].attrs["nmigen.vivado.false_path"] = "TRUE" - else: - flops[0].attrs["nmigen.vivado.max_delay"] = str(ff_sync._max_input_delay * 1e9) - 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 async_ff_sync._max_input_delay is None: - flops[0].attrs["nmigen.vivado.false_path"] = "TRUE" - else: - flops[0].attrs["nmigen.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9) - 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 +XilinxUltraScalePlatform = XilinxPlatform