build/altera: cleanup/simplify (no functional change)
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 6 Dec 2019 08:29:48 +0000 (09:29 +0100)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 6 Dec 2019 10:08:46 +0000 (11:08 +0100)
Altera build backend was a bit messy and needed some cleanup to ease future maintenance and new features.

litex/build/altera/common.py
litex/build/altera/platform.py
litex/build/altera/programmer.py
litex/build/altera/quartus.py

index d2d56d88e1cb9511bb10eb246650823395ff8e92..45770984b846372592146551190f12d5776e1354 100644 (file)
@@ -9,15 +9,16 @@ from migen.genlib.resetsync import AsyncResetSynchronizer
 
 from migen.fhdl.structure import *
 
+# DifferentialInput --------------------------------------------------------------------------------
 
 class AlteraDifferentialInputImpl(Module):
     def __init__(self, i_p, i_n, o):
         self.specials += [
             Instance("ALT_INBUF_DIFF",
-                name="ibuf_diff",
-                i_i=i_p,
-                i_ibar=i_n,
-                o_o=o
+                name   = "ibuf_diff",
+                i_i    = i_p,
+                i_ibar = i_n,
+                o_o    = o
             )
         ]
 
@@ -27,15 +28,16 @@ class AlteraDifferentialInput:
     def lower(dr):
         return AlteraDifferentialInputImpl(dr.i_p, dr.i_n, dr.o)
 
+# DifferentialOutput -------------------------------------------------------------------------------
 
 class AlteraDifferentialOutputImpl(Module):
     def __init__(self, i, o_p, o_n):
         self.specials += [
             Instance("ALT_OUTBUF_DIFF",
-                name="obuf_diff",
-                i_i=i,
-                o_o=o_p,
-                o_obar=o_n
+                name   = "obuf_diff",
+                i_i    = i,
+                o_o    = o_p,
+                o_obar = o_n
             )
         ]
 
@@ -45,20 +47,25 @@ class AlteraDifferentialOutput:
     def lower(dr):
         return AlteraDifferentialOutputImpl(dr.i, dr.o_p, dr.o_n)
 
+# AsyncResetSynchronizer ---------------------------------------------------------------------------
 
 class AlteraAsyncResetSynchronizerImpl(Module):
     def __init__(self, cd, async_reset):
         rst_meta = Signal()
         self.specials += [
             Instance("DFF",
-                i_d=0, i_clk=cd.clk,
-                i_clrn=1, i_prn=~async_reset,
-                o_q=rst_meta
+                i_d    = 0,
+                i_clk  = cd.clk,
+                i_clrn = 1,
+                i_prn  = ~async_reset,
+                o_q    = rst_meta
             ),
             Instance("DFF",
-                i_d=rst_meta, i_clk=cd.clk,
-                i_clrn=1, i_prn=~async_reset,
-                o_q=cd.rst
+                i_d    = rst_meta,
+                i_clk  = cd.clk,
+                i_clrn = 1,
+                i_prn  = ~async_reset,
+                o_q    = cd.rst
             )
         ]
 
@@ -68,9 +75,10 @@ class AlteraAsyncResetSynchronizer:
     def lower(dr):
         return AlteraAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
 
+# Special Overrides --------------------------------------------------------------------------------
 
 altera_special_overrides = {
-    DifferentialInput: AlteraDifferentialInput,
-    DifferentialOutput: AlteraDifferentialOutput,
-    AsyncResetSynchronizer: AlteraAsyncResetSynchronizer
+    DifferentialInput:      AlteraDifferentialInput,
+    DifferentialOutput:     AlteraDifferentialOutput,
+    AsyncResetSynchronizer: AlteraAsyncResetSynchronizer,
 }
index ef85f47a6f7d7b7674ae9d1832156fc3734388cb..a95dcf4b9a04da9072e455c01418582f1fcff7a3 100644 (file)
@@ -7,10 +7,11 @@ import os
 from litex.build.generic_platform import GenericPlatform
 from litex.build.altera import common, quartus
 
+# AlteraPlatform -----------------------------------------------------------------------------------
 
 class AlteraPlatform(GenericPlatform):
     bitstream_ext = ".sof"
-    create_rbf = True
+    create_rbf    = True
 
     def __init__(self, *args, toolchain="quartus", **kwargs):
         GenericPlatform.__init__(self, *args, **kwargs)
@@ -26,8 +27,7 @@ class AlteraPlatform(GenericPlatform):
     def get_verilog(self, *args, special_overrides=dict(), **kwargs):
         so = dict(common.altera_special_overrides)
         so.update(special_overrides)
-        return GenericPlatform.get_verilog(self, *args, special_overrides=so,
-                                           **kwargs)
+        return GenericPlatform.get_verilog(self, *args, special_overrides=so, **kwargs)
 
     def build(self, *args, **kwargs):
         return self.toolchain.build(self, *args, **kwargs)
index 6ced88481f557d0ce8221999f38a1567eb964aee..c389635b67336d0f9f62bad9c14132ed1e2e205b 100644 (file)
@@ -5,15 +5,18 @@ import subprocess
 
 from litex.build.generic_programmer import GenericProgrammer
 
+# USBBlaster ---------------------------------------------------------------------------------------
 
 class USBBlaster(GenericProgrammer):
     needs_bitreverse = False
 
     def __init__(self, cable_name="USB-Blaster", device_id=1):
         self.cable_name = cable_name
-        self.device_id = device_id
+        self.device_id  = device_id
 
     def load_bitstream(self, bitstream_file, cable_suffix=""):
-        subprocess.call(["quartus_pgm", "-m", "jtag", "-c",
-                         "{}{}".format(self.cable_name, cable_suffix), "-o",
-                         "p;{}@{}".format(bitstream_file, self.device_id)])
+        subprocess.call(["quartus_pgm",
+           "-m", "jtag",
+           "-c", "{}{}".format(self.cable_name, cable_suffix),
+           "-o", "p;{}@{}".format(bitstream_file, self.device_id)
+        ])
index 40f0681a7699cd789ce83f302f145e73065e4ccc..48a061e2310935bba5a35a3f2c1c6f0dd20d350e 100644 (file)
@@ -13,175 +13,189 @@ from migen.fhdl.structure import _Fragment
 from litex.build.generic_platform import Pins, IOStandard, Misc
 from litex.build import tools
 
+# IO/Placement Constraints (.qsf) ------------------------------------------------------------------
 
 def _format_constraint(c, signame, fmt_r):
+    # IO location constraints
     if isinstance(c, Pins):
-        return "set_location_assignment -comment \"{name}\" " \
-            "-to {signame} Pin_{pin}".format(
-                signame=signame,
-                name=fmt_r,
-                pin=c.identifiers[0])
+        tpl = "set_location_assignment -comment \"{name}\" -to {signame} Pin_{pin}"
+        return tpl.format(signame=signame, name=fmt_r, pin=c.identifiers[0])
+
+    # IO standard constraints
     elif isinstance(c, IOStandard):
-        return "set_instance_assignment -name io_standard " \
-            "-comment \"{name}\" \"{std}\" -to {signame}".format(
-                signame=signame,
-                name=fmt_r,
-                std=c.name)
+        tpl = "set_instance_assignment -name io_standard -comment \"{name}\" \"{std}\" -to {signame}"
+        return tpl.format(signame=signame, name=fmt_r, std=c.name)
+
+    # Others constraints
     elif isinstance(c, Misc):
         if not isinstance(c.misc, str) and len(c.misc) == 2:
-            return "set_instance_assignment -comment \"{name}\" " \
-                "-name {misc[0]} \"{misc[1]}\" -to {signame}".format(
-                    signame=signame,
-                    name=fmt_r,
-                    misc=c.misc)
+            tpl = "set_instance_assignment -comment \"{name}\" -name {misc[0]} \"{misc[1]}\" -to {signame}"
+            return tpl.format(signame=signame, name=fmt_r, misc=c.misc)
         else:
-            return "set_instance_assignment -comment \"{name}\" " \
-                "-name {misc} " \
-                "-to {signame}".format(
-                    signame=signame,
-                    name=fmt_r,
-                    misc=c.misc)
+            tpl = "set_instance_assignment -comment \"{name}\"  -name {misc} -to {signame}"
+            return tpl.format(signame=signame, name=fmt.r, misc=c.misc)
 
-
-def _format_qsf(signame, pin, others, resname):
+def _format_qsf_constraint(signame, pin, others, resname):
     fmt_r = "{}:{}".format(*resname[:2])
     if resname[2] is not None:
         fmt_r += "." + resname[2]
-
-    fmt_c = [_format_constraint(c, signame, fmt_r) for c in
-             ([Pins(pin)] + others)]
-
+    fmt_c = [_format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)]
     return '\n'.join(fmt_c)
 
-
-def _build_qsf(named_sc, named_pc, build_name):
-    lines = []
+def _build_qsf_constraints(named_sc, named_pc):
+    qsf = []
     for sig, pins, others, resname in named_sc:
         if len(pins) > 1:
             for i, p in enumerate(pins):
-                lines.append(
-                    _format_qsf("{}[{}]".format(sig, i), p, others, resname))
+                qsf.append(_format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname))
         else:
-            lines.append(_format_qsf(sig, pins[0], others, resname))
-
+            qsf.append(_format_qsf_constraint(sig, pins[0], others, resname))
     if named_pc:
-        lines.append("")
-        lines.append("\n\n".join(named_pc))
-
-    # Set top level name to "build_name" in .qsf file instead always use "top" name
-    lines.append("set_global_assignment -name top_level_entity " + build_name)
-    return "\n".join(lines)
+        qsf.append("\n\n".join(named_pc))
+    return "\n".join(qsf)
 
+# Timing Constraints (.sdc) ------------------------------------------------------------------------
 
 def _build_sdc(clocks, false_paths, vns, build_name):
-    lines = []
+    sdc = []
+    # Clock constraints
     for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
-        lines.append(
-            "create_clock -name {clk} -period ".format(clk=vns.get_name(clk)) + str(period) +
-            " [get_ports {{{clk}}}]".format(clk=vns.get_name(clk)))
-    for from_, to in sorted(false_paths,
-                            key=lambda x: (x[0].duid, x[1].duid)):
-        lines.append(
-            "set_false_path "
-            "-from [get_clocks {{{from_}}}] "
-            "-to [get_clocks {{{to}}}]".format(
-            from_=vns.get_name(from_), to=vns.get_name(to)))
-    tools.write_to_file("{}.sdc".format(build_name), "\n".join(lines))
-
-
-def _build_files(device, ips, sources, vincpaths, named_sc, named_pc, build_name):
-    lines = []
-    for filename in ips:
-           lines.append("set_global_assignment -name QSYS_FILE {path} ".format(
-                path=filename.replace("\\", "/")))
+        tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]"
+        sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period)))
+    # False path constraints
+    for from_, to in sorted(false_paths, key=lambda x: (x[0].duid, x[1].duid)):
+        tpl = "set_false_path -from [get_clocks {{{from_}}}] -to [get_clocks {{{to}}}]"
+        sdc.append(tpl.format(from_=vns.get_name(from_), to=vns.get_name(to)))
+    tools.write_to_file("{}.sdc".format(build_name), "\n".join(sdc))
+
+# Project (.qsf) -----------------------------------------------------------------------------------
+
+def _build_qsf(device, ips, sources, vincpaths, named_sc, named_pc, build_name):
+    qsf = []
+
+    # Set device
+    qsf.append("set_global_assignment -name DEVICE {}".format(device))
+
+    # Add sources
     for filename, language, library in sources:
-        # Enforce use of SystemVerilog since Quartus does not support global parameters in Verilog
-        if language == "verilog":
-            language = "systemverilog"
-        lines.append(
-            "set_global_assignment -name {lang}_FILE {path} "
-            "-library {lib}".format(
-                lang=language.upper(),
-                path=filename.replace("\\", "/"),
-                lib=library))
-    lines.append("set_global_assignment -name SDC_FILE {}.sdc".format(build_name))
+        if language == "verilog": language = "systemverilog" # Enforce use of SystemVerilog
+        tpl = "set_global_assignment -name {lang}_FILE {path} -library {lib}"
+        qsf.append(tpl.format(lang=language.upper(), path=filename.replace("\\", "/"), lib=library))
 
+    # Add ips
+    for filename in ips:
+        tpl = "set_global_assignment -name QSYS_FILE {filename}"
+        qsf.append(tpl.replace(filename=filename.replace("\\", "/")))
+
+    # Add include paths
     for path in vincpaths:
-        lines.append("set_global_assignment -name SEARCH_PATH {}".format(
-            path.replace("\\", "/")))
+        qsf.append("set_global_assignment -name SEARCH_PATH {}".format(path.replace("\\", "/")))
 
-    lines.append(_build_qsf(named_sc, named_pc, build_name))
-    lines.append("set_global_assignment -name DEVICE {}".format(device))
-    tools.write_to_file("{}.qsf".format(build_name), "\n".join(lines))
+    # Set top level
+    qsf.append("set_global_assignment -name top_level_entity " + build_name)
 
+    # Add io, placement constraints
+    qsf.append(_build_qsf_constraints(named_sc, named_pc))
 
-def _run_quartus(build_name, quartus_path, create_rbf):
-    if sys.platform == "win32" or sys.platform == "cygwin":
-        build_script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n"
-        build_script_file = "build_" + build_name + ".bat"
-        command = build_script_file
-    else:
-        build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n"
-        build_script_file = "build_" + build_name + ".sh"
-        command = ["bash", build_script_file]
-    build_script_contents += """
+    # Set timing constraints
+    qsf.append("set_global_assignment -name SDC_FILE {}.sdc".format(build_name))
 
-set -e
+    # Generate qsf
+    tools.write_to_file("{}.qsf".format(build_name), "\n".join(qsf))
 
-quartus_map --read_settings_files=on --write_settings_files=off {build_name} -c {build_name}
+# Script -------------------------------------------------------------------------------------------
+
+def _build_script(build_name, quartus_path, create_rbf):
+    if sys.platform in ["win32", "cygwin"]:
+        script_contents = "REM Autogenerated by LiteX / git: " + tools.get_litex_git_revision()
+        script_file = "build_" + build_name + ".bat"
+    else:
+        script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision()
+        script_file = "build_" + build_name + ".sh"
+    script_contents += """
+quartus_map --read_settings_files=on  --write_settings_files=off {build_name} -c {build_name}
 quartus_fit --read_settings_files=off --write_settings_files=off {build_name} -c {build_name}
 quartus_asm --read_settings_files=off --write_settings_files=off {build_name} -c {build_name}
 quartus_sta {build_name} -c {build_name}"""
     if create_rbf:
-        build_script_contents +="""
+        script_contents += """
 if [ -f "{build_name}.sof" ]
 then
     quartus_cpf -c {build_name}.sof {build_name}.rbf
 fi
-
 """
-    build_script_contents = build_script_contents.format(build_name=build_name)  # noqa
-    tools.write_to_file(build_script_file,
-                        build_script_contents,
-                        force_unix=True)
+    script_contents = script_contents.format(build_name=build_name)
+    tools.write_to_file(script_file, script_contents, force_unix=True)
 
-    if subprocess.call(command):
+    return script_file
+
+def _run_script(script):
+    if sys.platform in ["win32", "cygwin"]:
+        shell = ["cmd", "/c"]
+    else:
+        shell = ["bash"]
+
+    if subprocess.call(shell + [script]) != 0:
         raise OSError("Subprocess failed")
 
+# AlteraQuartusToolchain ---------------------------------------------------------------------------
 
 class AlteraQuartusToolchain:
     def __init__(self):
-        self.clocks = dict()
+        self.clocks      = dict()
         self.false_paths = set()
 
-    def build(self, platform, fragment, build_dir="build", build_name="top",
-              toolchain_path=None, run=True, **kwargs):
+    def build(self, platform, fragment,
+        build_dir      = "build",
+        build_name     = "top",
+        toolchain_path = None,
+        run            = True,
+        **kwargs):
+
+        # Get default toolchain path (if not specified)
         if toolchain_path is None:
-            toolchain_path="/opt/Altera"
+            toolchain_path = "/opt/Altera"
+
+        # Create build directory
         cwd = os.getcwd()
         os.makedirs(build_dir, exist_ok=True)
         os.chdir(build_dir)
 
+        # Finalize design
         if not isinstance(fragment, _Fragment):
             fragment = fragment.get_fragment()
         platform.finalize(fragment)
 
+        # 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)
-        _build_files(platform.device,
-                     platform.ips,
-                     platform.sources,
-                     platform.verilog_include_paths,
-                     named_sc,
-                     named_pc,
-                     build_name)
-
-        _build_sdc(self.clocks, self.false_paths, v_output.ns, build_name)
+
+        # Generate design timing constraints file (.sdc)
+        _build_sdc(
+            clocks      = self.clocks,
+            false_paths = self.false_paths,
+            vns         = v_output.ns,
+            build_name  = build_name)
+
+        # Generate design project and location constraints file (.qsf)
+        _build_qsf(
+            device     = platform.device,
+            ips        = platform.ips,
+            sources    = platform.sources,
+            vincpaths  = platform.verilog_include_paths,
+            named_sc   = named_sc,
+            named_pc   = named_pc,
+            build_name = build_name)
+
+        # Generate build script
+        script = _build_script(build_name, toolchain_path, platform.create_rbf)
+
+        # Run
         if run:
-            _run_quartus(build_name, toolchain_path, platform.create_rbf)
+            _run_script(script)
 
         os.chdir(cwd)
 
@@ -190,7 +204,7 @@ class AlteraQuartusToolchain:
     def add_period_constraint(self, platform, clk, period):
         if clk in self.clocks:
             raise ValueError("A period constraint already exists")
-        period = math.floor(period*1e3)/1e3 # round to lowest picosecond
+        period = math.floor(period*1e3)/1e3 # Round to lowest picosecond
         self.clocks[clk] = period
 
     def add_false_path_constraint(self, platform, from_, to):