build/lattice: cleanup/simplify (no functional changes)
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 6 Dec 2019 11:51:50 +0000 (12:51 +0100)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 6 Dec 2019 11:54:52 +0000 (12:54 +0100)
icestorm still need to be cleaned up

litex/build/lattice/diamond.py
litex/build/lattice/programmer.py
litex/build/lattice/trellis.py

index c3b17662f49668d0060bddaa151c632d797239c1..b8aefc80eafa205b4b639f0bfc1248deaaa2233a 100644 (file)
@@ -19,7 +19,7 @@ from litex.build.lattice import common
 def _produces_jedec(device):
     return device.startswith("LCMX")
 
-# IO Constraints (.lpf) ----------------------------------------------------------------------------
+# Constraints (.lpf) -------------------------------------------------------------------------------
 
 def _format_constraint(c):
     if isinstance(c, Pins):
@@ -176,12 +176,12 @@ class LatticeDiamondToolchain:
         v_output.write(v_file)
         platform.add_source(v_file)
 
+        # Generate design constraints file (.lpf)
+        _build_lpf(named_sc, named_pc, build_name)
+
         # Generate design script file (.tcl)
         _build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name)
 
-        # Generate design timing constraints file (.lpf)
-        _build_lpf(named_sc, named_pc, build_name)
-
         # Generate build script
         script = _build_script(build_name, platform.device, toolchain_path)
 
index 0c9d5d0e1289e23c377f4f0a62fe25aae97abbb8..c8cae7944e048643944f52b73e55c96d97a9d49d 100644 (file)
@@ -1,4 +1,4 @@
-# This file is Copyright (c) 2015-2018 Florent Kermarrec <florent@enjoy-digital.fr>
+# This file is Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
 # This file is Copyright (c) 2017-2018 William D. Jones <thor0505@comcast.net>
 # License: BSD
 
@@ -8,6 +8,7 @@ import subprocess
 from litex.build.generic_programmer import GenericProgrammer
 from litex.build import tools
 
+# LatticeProgrammer --------------------------------------------------------------------------------
 
 class LatticeProgrammer(GenericProgrammer):
     needs_bitreverse = False
@@ -21,6 +22,7 @@ class LatticeProgrammer(GenericProgrammer):
         tools.write_to_file(xcf_file, xcf_content)
         subprocess.call(["pgrcmd", "-infile", xcf_file])
 
+# IceStormProgrammer -------------------------------------------------------------------------------
 
 class IceStormProgrammer(GenericProgrammer):
     needs_bitreverse = False
@@ -31,6 +33,7 @@ class IceStormProgrammer(GenericProgrammer):
     def load_bitstream(self, bitstream_file):
         subprocess.call(["iceprog", "-S", bitstream_file])
 
+# IceBurnProgrammer --------------------------------------------------------------------------------
 
 class IceBurnProgrammer(GenericProgrammer):
     def __init__(self, iceburn_path):
@@ -42,6 +45,7 @@ class IceBurnProgrammer(GenericProgrammer):
     def load_bitstream(self, bitstream_file):
         subprocess.call([self.iceburn, "-evw", bitstream_file])
 
+# TinyFpgaBProgrammer ------------------------------------------------------------------------------
 
 class TinyFpgaBProgrammer(GenericProgrammer):
     needs_bitreverse = False
@@ -57,6 +61,7 @@ class TinyFpgaBProgrammer(GenericProgrammer):
     def boot(self):
         subprocess.call(["tinyfpgab", "-b"])
 
+# TinyProgProgrammer -------------------------------------------------------------------------------
 
 # Different bootloader protocol requires different application. In the basic
 # case, command-line arguments are the same. Note that this programmer can
@@ -85,6 +90,7 @@ class TinyProgProgrammer(GenericProgrammer):
     def boot(self):
         subprocess.call(["tinyprog", "-b"])
 
+# MyStormProgrammer --------------------------------------------------------------------------------
 
 class MyStormProgrammer(GenericProgrammer):
     def __init__(self, serial_port):
index 8ccd05695f07c6664a339895907ba13263811ae2..a02e49a9a3aab296253d1ed0805d2d414d8e45af 100644 (file)
@@ -1,4 +1,4 @@
-# This file is Copyright (c) 2018 Florent Kermarrec <florent@enjoy-digital.fr>
+# This file is Copyright (c) 2018-2019 Florent Kermarrec <florent@enjoy-digital.fr>
 # This file is Copyright (c) 2018-2019 David Shah <dave@ds0.me>
 # This file is Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
 # License: BSD
@@ -13,35 +13,7 @@ from litex.build.generic_platform import *
 from litex.build import tools
 from litex.build.lattice import common
 
-# TODO:
-# - check/document attr_translate.
-
-nextpnr_ecp5_architectures = {
-    "lfe5u-25f": "25k",
-    "lfe5u-45f": "45k",
-    "lfe5u-85f": "85k",
-    "lfe5um-25f": "um-25k",
-    "lfe5um-45f": "um-45k",
-    "lfe5um-85f": "um-85k",
-    "lfe5um5g-25f": "um5g-25k",
-    "lfe5um5g-45f": "um5g-45k",
-    "lfe5um5g-85f": "um5g-85k",
-}
-
-
-def nextpnr_ecp5_package(package):
-    if "285" in package:
-        return "CSFBGA285"
-    elif "256" in package:
-        return "CABGA256"
-    elif "381" in package:
-        return "CABGA381"
-    elif "554" in package:
-        return "CABGA554"
-    elif "756" in package:
-        return "CABGA756"
-    raise ValueError("Unknown package")
-
+# IO Constraints (.lpf) ----------------------------------------------------------------------------
 
 def _format_constraint(c):
     if isinstance(c, Pins):
@@ -54,51 +26,112 @@ def _format_constraint(c):
 
 def _format_lpf(signame, pin, others, resname):
     fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)]
-    r = ""
+    lpf = []
     for pre, suf in fmt_c:
-        r += pre + "\"" + signame + "\"" + suf + ";\n"
-    return r
+        lpf.append(pre + "\"" + signame + "\"" + suf + ";")
+    return "\n".join(lpf)
 
 
-def _build_lpf(named_sc, named_pc):
-    r = "BLOCK RESETPATHS;\n"
-    r += "BLOCK ASYNCPATHS;\n"
+def _build_lpf(named_sc, named_pc, build_name):
+    lpf = []
+    lpf.append("BLOCK RESETPATHS;")
+    lpf.append("BLOCK ASYNCPATHS;")
     for sig, pins, others, resname in named_sc:
         if len(pins) > 1:
             for i, p in enumerate(pins):
-                r += _format_lpf(sig + "[" + str(i) + "]", p, others, resname)
+                lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname))
         else:
-            r += _format_lpf(sig, pins[0], others, resname)
+            lpf.append(_format_lpf(sig, pins[0], others, resname))
     if named_pc:
-        r += "\n" + "\n\n".join(named_pc)
-    return r
+        lpf.append("\n\n".join(named_pc))
+    tools.write_to_file(build_name + ".lpf", "\n".join(lpf))
 
+# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
 
-def _build_script(source, build_template, build_name, architecture,
-                  package, freq_constraint, timingstrict):
+_yosys_template = [
+    "{read_files}",
+    "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
+    "synth_ecp5 -abc9 {nwl} -json {build_name}.json -top {build_name}",
+]
+
+def _yosys_import_sources(platform):
+    includes = ""
+    reads = []
+    for path in platform.verilog_include_paths:
+        includes += " -I" + path
+    for filename, language, library in platform.sources:
+        reads.append("read_{}{} {}".format(
+            language, includes, filename))
+    return "\n".join(reads)
+
+def _build_yosys(template, platform, nowidelut, build_name):
+    ys = []
+    for l in template:
+        ys.append(l.format(
+            build_name = build_name,
+            nwl        = "-nowidelut" if nowidelut else "",
+            read_files = _yosys_import_sources(platform)
+        ))
+    tools.write_to_file(build_name + ".ys", "\n".join(ys))
+
+nextpnr_ecp5_architectures = {
+    "lfe5u-25f"   : "25k",
+    "lfe5u-45f"   : "45k",
+    "lfe5u-85f"   : "85k",
+    "lfe5um-25f"  : "um-25k",
+    "lfe5um-45f"  : "um-45k",
+    "lfe5um-85f"  : "um-85k",
+    "lfe5um5g-25f": "um5g-25k",
+    "lfe5um5g-45f": "um5g-45k",
+    "lfe5um5g-85f": "um5g-85k",
+}
+
+def nextpnr_ecp5_package(package):
+    if "256" in package:
+        return "CABGA256"
+    elif "285" in package:
+        return "CSFBGA285"
+    elif "381" in package:
+        return "CABGA381"
+    elif "554" in package:
+        return "CABGA554"
+    elif "756" in package:
+        return "CABGA756"
+    raise ValueError("Unknown package {}".format(package))
+
+# Script -------------------------------------------------------------------------------------------
+
+_build_template = [
+    "yosys -q -l {build_name}.rpt {build_name}.ys",
+    "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config  \
+    --{architecture} --package {package} --freq {freq_constraint} {timefailarg}",
+    "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
+]
+
+def _build_script(source, build_template, build_name, architecture, package, freq_constraint, timingstrict):
     if sys.platform in ("win32", "cygwin"):
         script_ext = ".bat"
-        build_script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
+        script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n"
         fail_stmt = " || exit /b"
     else:
         script_ext = ".sh"
-        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"
         fail_stmt = ""
 
     for s in build_template:
         s_fail = s + "{fail_stmt}\n"  # Required so Windows scripts fail early.
-        build_script_contents += s_fail.format(build_name=build_name,
-                                               architecture=architecture,
-                                               package=package,
-                                               freq_constraint=freq_constraint,
-                                               timefailarg="--timing-allow-fail" if not timingstrict else "",
-                                               fail_stmt=fail_stmt)
+        script_contents += s_fail.format(
+            build_name      = build_name,
+            architecture    = architecture,
+            package         = package,
+            freq_constraint = freq_constraint,
+            timefailarg     = "--timing-allow-fail" if not timingstrict else "",
+            fail_stmt       = fail_stmt)
 
-    build_script_file = "build_" + build_name + script_ext
-    tools.write_to_file(build_script_file, build_script_contents,
-                        force_unix=False)
-    return build_script_file
+    script_file = "build_" + build_name + script_ext
+    tools.write_to_file(script_file, script_contents, force_unix=False)
 
+    return script_file
 
 def _run_script(script):
     if sys.platform in ("win32", "cygwin"):
@@ -109,106 +142,90 @@ def _run_script(script):
     if subprocess.call(shell + [script]) != 0:
         raise OSError("Subprocess failed")
 
-
-def yosys_import_sources(platform):
-    includes = ""
-    reads = []
-    for path in platform.verilog_include_paths:
-        includes += " -I" + path
-    for filename, language, library in platform.sources:
-        reads.append("read_{}{} {}".format(
-            language, includes, filename))
-    return "\n".join(reads)
-
+# LatticeTrellisToolchain --------------------------------------------------------------------------
 
 class LatticeTrellisToolchain:
     attr_translate = {
         # FIXME: document
         "keep": ("keep", "true"),
-        "no_retiming": None,
-        "async_reg": None,
-        "mr_ff": None,
-        "mr_false_path": None,
-        "ars_ff1": None,
-        "ars_ff2": None,
-        "ars_false_path": None,
+        "no_retiming":      None,
+        "async_reg":        None,
+        "mr_ff":            None,
+        "mr_false_path":    None,
+        "ars_ff1":          None,
+        "ars_ff2":          None,
+        "ars_false_path":   None,
         "no_shreg_extract": None
     }
 
     special_overrides = common.lattice_ecpx_trellis_special_overrides
 
     def __init__(self):
-        self.yosys_template = [
-            "{read_files}",
-            "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
-            "synth_ecp5 -abc9 {nwl} -json {build_name}.json -top {build_name}",
-        ]
-
-        self.build_template = [
-            "yosys -q -l {build_name}.rpt {build_name}.ys",
-            "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config --{architecture} --package {package} --freq {freq_constraint} {timefailarg}",
-            "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
-        ]
-
+        self.yosys_template   = _yosys_template
+        self.build_template   = _build_template
         self.freq_constraints = dict()
 
-    def build(self, platform, fragment, build_dir="build", build_name="top",
-              toolchain_path=None, run=True,
-              nowidelut=False, timingstrict=False,
-              **kwargs):
+    def build(self, platform, fragment,
+        build_dir      = "build",
+        build_name     = "top",
+        toolchain_path = None,
+        run            = True,
+        nowidelut      = False,
+        timingstrict   = False,
+        **kwargs):
+
+        # Get default toolchain path (if not specified)
         if toolchain_path is None:
             toolchain_path = "/usr/share/trellis/"
+
+        # Create build directory
         os.makedirs(build_dir, exist_ok=True)
         cwd = os.getcwd()
         os.chdir(build_dir)
 
-        # generate verilog
+        # Finalize design
         if not isinstance(fragment, _Fragment):
             fragment = fragment.get_fragment()
         platform.finalize(fragment)
 
-        top_output = platform.get_verilog(fragment, name=build_name, **kwargs)
-        named_sc, named_pc = platform.resolve_signals(top_output.ns)
+         # Generate verilog
+        v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
+        named_sc, named_pc = platform.resolve_signals(v_output.ns)
         top_file = build_name + ".v"
-        top_output.write(top_file)
+        v_output.write(top_file)
         platform.add_source(top_file)
 
-        # generate constraints
-        tools.write_to_file(build_name + ".lpf",
-                            _build_lpf(named_sc, named_pc))
+        # Generate design constraints file (.lpf)
+        _build_lpf(named_sc, named_pc, build_name)
 
-        # generate yosys script
-        yosys_script_file = build_name + ".ys"
-        yosys_script_contents = "\n".join(_.format(build_name=build_name,
-                                                   nwl="-nowidelut" if nowidelut else "",
-                                                   read_files=yosys_import_sources(platform))
-                                          for _ in self.yosys_template)
-        tools.write_to_file(yosys_script_file, yosys_script_contents)
+        # Generate Yosys script
+        _build_yosys(self.yosys_template, platform, nowidelut, build_name)
 
-        # transform platform.device to nextpnr's architecture
+        # Translate device to Nextpnr architecture/package
         (family, size, package) = platform.device.split("-")
         architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()]
-        package = nextpnr_ecp5_package(package)
-        freq_constraint = str(max(self.freq_constraints.values(),
-                                  default=0.0))
+        package      = nextpnr_ecp5_package(package)
+
+        freq_constraint = str(max(self.freq_constraints.values(), default=0.0))
 
-        script = _build_script(False, self.build_template, build_name,
-                               architecture, package, freq_constraint,
-                               timingstrict)
+        # Generate build script
+        script = _build_script(False, self.build_template, build_name, architecture, package,
+            freq_constraint, timingstrict)
 
-        # run scripts
+        # Run
         if run:
             _run_script(script)
 
         os.chdir(cwd)
 
-        return top_output.ns
+        return v_output.ns
 
     # Until nextpnr-ecp5 can handle multiple clock domains, use the same
     # approach as the icestorm and use the fastest clock for timing
     # constraints.
     def add_period_constraint(self, platform, clk, period):
-        platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)
+        platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(
+            freq=str(float(1/period)*1000), clk="{clk}"), clk=clk)
 
 def trellis_args(parser):
     parser.add_argument("--yosys-nowidelut", action="store_true",
@@ -218,6 +235,6 @@ def trellis_args(parser):
 
 def trellis_argdict(args):
     return {
-        "nowidelut": args.yosys_nowidelut,
+        "nowidelut":    args.yosys_nowidelut,
         "timingstrict": args.nextpnr_timingstrict,
     }