build/xilinx/vivado: cleanup/simplify
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Sun, 8 Dec 2019 11:08:17 +0000 (12:08 +0100)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Sun, 8 Dec 2019 11:08:17 +0000 (12:08 +0100)
litex/build/xilinx/vivado.py

index fb87bccbe58fb7d1e7a719dcf29014ab69e3fe70..5331d44733d1d7c7f4483d6d0a45e9b1200fa64d 100644 (file)
@@ -13,8 +13,9 @@ from litex.build.generic_platform import *
 from litex.build import tools
 from litex.build.xilinx import common
 
+# Constraints (.xdc) -------------------------------------------------------------------------------
 
-def _format_constraint(c):
+def _format_xdc_constraint(c):
     if isinstance(c, Pins):
         return "set_property LOC " + c.identifiers[0]
     elif isinstance(c, IOStandard):
@@ -30,7 +31,7 @@ def _format_constraint(c):
 
 
 def _format_xdc(signame, resname, *constraints):
-    fmt_c = [_format_constraint(c) for c in constraints]
+    fmt_c = [_format_xdc_constraint(c) for c in constraints]
     fmt_r = resname[0] + ":" + str(resname[1])
     if resname[2] is not None:
         fmt_r += "." + resname[2]
@@ -55,16 +56,16 @@ def _build_xdc(named_sc, named_pc):
         r += "\n" + "\n\n".join(named_pc)
     return r
 
+# Script -------------------------------------------------------------------------------------------
 
-def _run_vivado(build_name, vivado_path, source, ver=None):
-    if sys.platform == "win32" or sys.platform == "cygwin":
-        build_script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
-        build_script_contents += "vivado -mode batch -source " + build_name + ".tcl\n"
-        build_script_file = "build_" + build_name + ".bat"
-        tools.write_to_file(build_script_file, build_script_contents)
-        command = build_script_file
+def _build_script(build_name, vivado_path, source, ver=None):
+    if sys.platform in ["win32", "cygwin"]:
+        script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
+        script_contents += "vivado -mode batch -source " + build_name + ".tcl\n"
+        script_file = "build_" + build_name + ".bat"
+        tools.write_to_file(script_file, script_contents)
     else:
-        build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
+        script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
         # Only source Vivado settings if not already in our $PATH
         if not find_executable("vivado"):
             # For backwards compatibility with ISE paths, also
@@ -79,52 +80,65 @@ def _run_vivado(build_name, vivado_path, source, ver=None):
                 break
             else:
                 raise OSError("Unable to locate Vivado directory or settings.")
-            build_script_contents += "source " + settings + "\n"
-
-        build_script_contents += "vivado -mode batch -source " + build_name + ".tcl\n"
-        build_script_file = "build_" + build_name + ".sh"
-        tools.write_to_file(build_script_file, build_script_contents)
-        command = ["bash", build_script_file]
-    r = tools.subprocess_call_filtered(command, common.colors)
-    if r != 0:
+            script_contents += "source " + settings + "\n"
+
+        script_contents += "vivado -mode batch -source " + build_name + ".tcl\n"
+        script_file = "build_" + build_name + ".sh"
+        tools.write_to_file(script_file, script_contents)
+    return script_file
+
+def _run_script(script):
+    if sys.platform in ["win32", "cygwin"]:
+        shell = ["cmd", "/c"]
+    else:
+        shell = ["bash"]
+
+    if tools.subprocess_call_filtered(shell + [script], common.colors) != 0:
         raise OSError("Subprocess failed")
 
+# XilinxVivadoToolchain ----------------------------------------------------------------------------
 
 class XilinxVivadoToolchain:
     attr_translate = {
-        "keep": ("dont_touch", "true"),
-        "no_retiming": ("dont_touch", "true"),
-        "async_reg": ("async_reg", "true"),
-        "mr_ff": ("mr_ff", "true"),  # user-defined attribute
-        "ars_ff1": ("ars_ff1", "true"),  # user-defined attribute
-        "ars_ff2": ("ars_ff2", "true"),  # user-defined attribute
+        "keep":            ("dont_touch", "true"),
+        "no_retiming":     ("dont_touch", "true"),
+        "async_reg":       ("async_reg",  "true"),
+        "mr_ff":           ("mr_ff",      "true"), # user-defined attribute
+        "ars_ff1":         ("ars_ff1",    "true"), # user-defined attribute
+        "ars_ff2":         ("ars_ff2",    "true"), # user-defined attribute
         "no_shreg_extract": None
     }
 
     def __init__(self):
-        self.bitstream_commands = []
-        self.additional_commands = []
-        self.pre_synthesis_commands = []
-        self.incremental_implementation = False
-        self.vivado_synth_directive = "default"
-        self.opt_directive = "default"
-        self.vivado_place_directive = "default"
+        self.bitstream_commands                   = []
+        self.additional_commands                  = []
+        self.pre_synthesis_commands               = []
+        self.incremental_implementation           = False
+        self.vivado_synth_directive               = "default"
+        self.opt_directive                        = "default"
+        self.vivado_place_directive               = "default"
         self.vivado_post_place_phys_opt_directive = None
-        self.vivado_route_directive = "default"
+        self.vivado_route_directive               = "default"
         self.vivado_post_route_phys_opt_directive = "default"
-        self.clocks = dict()
+        self.clocks      = dict()
         self.false_paths = set()
 
-    def _build_batch(self, platform, sources, edifs, ips, build_name, synth_mode, enable_xpm):
+    def _build_tcl(self, platform, build_name, synth_mode, enable_xpm):
         assert synth_mode in ["vivado", "yosys"]
         tcl = []
+
+        # Create project
         tcl.append("create_project -force -name {} -part {}".format(build_name, platform.device))
         tcl.append("set_msg_config -id {Common 17-55} -new_severity {Warning}")
+
+        # Enable Xilinx Parameterized Macros
         if enable_xpm:
             tcl.append("set_property XPM_LIBRARIES {XPM_CDC XPM_MEMORY} [current_project]")
+
+        # Add sources (when Vivado used for synthesis)
         if synth_mode == "vivado":
             # "-include_dirs {}" crashes Vivado 2016.4
-            for filename, language, library in sources:
+            for filename, language, library in platform.sources:
                 filename_tcl = "{" + filename + "}"
                 if (language == "systemverilog"):
                     tcl.append("read_verilog -sv " + filename_tcl)
@@ -136,11 +150,14 @@ class XilinxVivadoToolchain:
                                .format(library, filename_tcl))
                 else:
                     tcl.append("add_files " + filename_tcl)
-        for filename in edifs:
+
+        # Add EDIFs
+        for filename in platform.edifs:
             filename_tcl = "{" + filename + "}"
             tcl.append("read_edif " + filename_tcl)
 
-        for filename in ips:
+        # Add Ips
+        for filename in platform.ips:
             filename_tcl = "{" + filename + "}"
             ip = os.path.splitext(os.path.basename(filename))[0]
             tcl.append("read_ip " + filename_tcl)
@@ -149,9 +166,13 @@ class XilinxVivadoToolchain:
             tcl.append("synth_ip [get_ips {}] -force".format(ip))
             tcl.append("get_files -all -of_objects [get_files {}]".format(filename_tcl))
 
+        # Add constraints
         tcl.append("read_xdc {}.xdc".format(build_name))
+
+        # Add pre-synthesis commands
         tcl.extend(c.format(build_name=build_name) for c in self.pre_synthesis_commands)
 
+        # Design flow
         if synth_mode == "vivado":
             synth_cmd = "synth_design -directive {} -top {} -part {}".format(self.vivado_synth_directive,
                                                                              build_name, platform.device)
@@ -194,7 +215,7 @@ class XilinxVivadoToolchain:
         tcl.append("quit")
         tools.write_to_file(build_name + ".tcl", "\n".join(tcl))
 
-    def _convert_clocks(self, platform):
+    def _build_clock_constraints(self, platform):
         for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid):
             platform.add_platform_command(
                 "create_clock -name {clk} -period " + str(period) +
@@ -207,26 +228,23 @@ class XilinxVivadoToolchain:
                 "-group [get_clocks -include_generated_clocks -of [get_nets {to}]] "
                 "-asynchronous",
                 from_=from_, to=to)
-
-        # make sure add_*_constraint cannot be used again
+        # Make sure add_*_constraint cannot be used again
         del self.clocks
         del self.false_paths
 
-    def _constrain(self, platform):
+    def _build_false_path_constraints(self, platform):
         # The asynchronous input to a MultiReg is a false path
         platform.add_platform_command(
             "set_false_path -quiet "
             "-to [get_nets -quiet -filter {{mr_ff == TRUE}}]"
         )
-        # The asychronous reset input to the AsyncResetSynchronizer is a false
-        # path
+        # The asychronous reset input to the AsyncResetSynchronizer is a false path
         platform.add_platform_command(
             "set_false_path -quiet "
             "-to [get_pins -quiet -filter {{REF_PIN_NAME == PRE}} "
                 "-of [get_cells -quiet -filter {{ars_ff1 == TRUE || ars_ff2 == TRUE}}]]"
         )
-        # clock_period-2ns to resolve metastability on the wire between the
-        # AsyncResetSynchronizer FFs
+        # clock_period-2ns to resolve metastability on the wire between the AsyncResetSynchronizer FFs
         platform.add_platform_command(
             "set_max_delay 2 -quiet "
             "-from [get_pins -quiet -filter {{REF_PIN_NAME == Q}} "
@@ -235,34 +253,59 @@ class XilinxVivadoToolchain:
                 "-of [get_cells -quiet -filter {{ars_ff2 == TRUE}}]]"
         )
 
-    def build(self, platform, fragment, build_dir="build", build_name="top",
-            toolchain_path="/opt/Xilinx/Vivado", source=True, run=True,
-            synth_mode="vivado", enable_xpm=False, **kwargs):
+    def build(self, platform, fragment,
+        build_dir      = "build",
+        build_name     = "top",
+        toolchain_path = None,
+        source         = True, run=True,
+        synth_mode     = "vivado",
+        enable_xpm     = False,
+        **kwargs):
+
+        # Get default toolchain path (if not specified)
         if toolchain_path is None:
             toolchain_path = "/opt/Xilinx/Vivado"
+
+        # Create build directory
         os.makedirs(build_dir, exist_ok=True)
         cwd = os.getcwd()
         os.chdir(build_dir)
 
+        # Finalize design
         if not isinstance(fragment, _Fragment):
             fragment = fragment.get_fragment()
         platform.finalize(fragment)
-        self._convert_clocks(platform)
-        self._constrain(platform)
+
+        # Generate timing constraints
+        self._build_clock_constraints(platform)
+        self._build_false_path_constraints(platform)
+
+        # Generate verilog
         v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
         named_sc, named_pc = platform.resolve_signals(v_output.ns)
         v_file = build_name + ".v"
         v_output.write(v_file)
         platform.add_source(v_file)
-        sources = platform.sources
-        edifs = platform.edifs
-        ips = platform.ips
-        self._build_batch(platform, sources, edifs, ips, build_name, synth_mode, enable_xpm)
+
+        # Generate design project (.tcl)
+        self._build_tcl(
+            platform   = platform,
+            build_name = build_name,
+            synth_mode = synth_mode,
+            enable_xpm = enable_xpm
+        )
+
+        # Generate design constraints (.xdc)
         tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc))
+
+        # Generate build script
+        script = _build_script(build_name, toolchain_path, source)
+
+        # Run
         if run:
             if synth_mode == "yosys":
-                common._run_yosys(platform.device, sources, platform.verilog_include_paths, build_name)
-            _run_vivado(build_name, toolchain_path, source)
+                common._run_yosys(platform.device, platform.sources, platform.verilog_include_paths, build_name)
+            _run_script(script)
 
         os.chdir(cwd)