build/lattice/trellis: fix spimode typo.
[litex.git] / litex / build / lattice / trellis.py
index 2790b28d74fc63726704c18b5adb19ffa38b4624..df7559f2ec029940db9e0971c3744c7262a7ee11 100644 (file)
@@ -1,4 +1,5 @@
-# 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
 
@@ -12,33 +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 "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):
@@ -51,50 +26,129 @@ 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 ------------------------------------------------------------------
 
+_yosys_template = [
+    "verilog_defaults -push",
+    "verilog_defaults -add -defer",
+    "{read_files}",
+    "verilog_defaults -pop",
+    "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 _build_script(source, build_template, build_name, architecture,
-                  package, freq_constraint):
+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))
+
+def nextpnr_ecp5_parse_device(device):
+    device      = device.lower()
+    family      = device.split("-")[0]
+    size        = device.split("-")[1]
+    speed_grade = device.split("-")[2][0]
+    if speed_grade not in ["6", "7", "8"]:
+       raise ValueError("Invalid speed grade {}".format(speed_grade))
+    package     = device.split("-")[2][1:]
+    if "256" in package:
+        package = "CABGA256"
+    elif "285" in package:
+        package = "CSFBGA285"
+    elif "381" in package:
+        package = "CABGA381"
+    elif "554" in package:
+        package = "CABGA554"
+    elif "756" in package:
+        package = "CABGA756"
+    else:
+       raise ValueError("Invalid package {}".format(package))
+    return (family, size, speed_grade, package)
+
+nextpnr_ecp5_architectures = {
+    "lfe5u-12f"   : "12k",
+    "lfe5u-25f"   : "25k",
+    "lfe5u-45f"   : "45k",
+    "lfe5u-85f"   : "85k",
+    "lfe5um-25f"  : "um-25k",
+    "lfe5um-45f"  : "um-45k",
+    "lfe5um-85f"  : "um-85k",
+    "lfe5um5g-25f": "um5g-25k",
+    "lfe5um5g-45f": "um5g-45k",
+    "lfe5um5g-85f": "um5g-85k",
+}
+
+# Script -------------------------------------------------------------------------------------------
+
+_build_template = [
+    "yosys -l {build_name}.rpt {build_name}.ys",
+    "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config  \
+    --{architecture} --package {package} --speed {speed_grade} {timefailarg} {ignoreloops} --seed {seed}",
+    "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit --bootaddr {bootaddr} --spimode {spimode}"
+]
+
+def _build_script(source, build_template, build_name, architecture, package, speed_grade, timingstrict, ignoreloops, bootaddr, seed, spimode):
     if sys.platform in ("win32", "cygwin"):
         script_ext = ".bat"
-        build_script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.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.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,
-                                               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_contents += s_fail.format(
+            build_name      = build_name,
+            architecture    = architecture,
+            package         = package,
+            speed_grade     = speed_grade,
+            timefailarg     = "--timing-allow-fail" if not timingstrict else "",
+            ignoreloops     = "--ignore-loops" if ignoreloops else "",
+            bootaddr        = bootaddr,
+            fail_stmt       = fail_stmt,
+            seed            = seed,
+            spimode         = spimode)
+
+    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"):
@@ -105,99 +159,109 @@ 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
+    special_overrides = common.lattice_ecp5_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 -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}",
-            "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit"
-        ]
-
-        self.freq_constraints = dict()
-
-    def build(self, platform, fragment, build_dir="build", build_name="top",
-              toolchain_path=None, run=True, **kwargs):
-        if toolchain_path is None:
-            toolchain_path = "/usr/share/trellis/"
+        self.yosys_template   = _yosys_template
+        self.build_template   = _build_template
+        self.false_paths = set() # FIXME: use it
+
+    def build(self, platform, fragment,
+        build_dir      = "build",
+        build_name     = "top",
+        run            = True,
+        nowidelut      = False,
+        timingstrict   = False,
+        ignoreloops    = False,
+        bootaddr       = 0,
+        seed           = 1,
+        spimode        = "fast-read",
+        **kwargs):
+
+        # 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 yosys script
-        yosys_script_file = build_name + ".ys"
-        yosys_script_contents = "\n".join(_.format(build_name=build_name,
-                                                   read_files=yosys_import_sources(platform))
-                                          for _ in self.yosys_template)
-        tools.write_to_file(yosys_script_file, yosys_script_contents)
+        # Generate design constraints file (.lpf)
+        _build_lpf(named_sc, named_pc, build_name)
 
-        # transform platform.device to nextpnr's architecture
-        (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))
+        # Generate Yosys script
+        _build_yosys(self.yosys_template, platform, nowidelut, build_name)
 
-        script = _build_script(False, self.build_template, build_name,
-                               architecture, package, freq_constraint)
+        # Translate device to Nextpnr architecture/package/speed_grade
+        (family, size, speed_grade, package) = nextpnr_ecp5_parse_device(platform.device)
+        architecture = nextpnr_ecp5_architectures[(family + "-" + size)]
 
-        # run scripts
+        # Generate build script
+        script = _build_script(False, self.build_template, build_name, architecture, package,
+                               speed_grade, timingstrict, ignoreloops, bootaddr, seed, spimode)
+        # 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 add_false_path_constraint(self, platform, from_, to):
+        from_.attr.add("keep")
+        to.attr.add("keep")
+        if (to, from_) not in self.false_paths:
+            self.false_paths.add((from_, to))
+
+def trellis_args(parser):
+    parser.add_argument("--yosys-nowidelut", action="store_true",
+                        help="pass '-nowidelut' to yosys synth_ecp5")
+    parser.add_argument("--nextpnr-timingstrict", action="store_true",
+                        help="fail if timing not met, i.e., do NOT pass '--timing-allow-fail' to nextpnr")
+    parser.add_argument("--nextpnr-ignoreloops", action="store_true",
+                        help="ignore combinational loops in timing analysis, i.e. pass '--ignore-loops' to nextpnr")
+    parser.add_argument("--ecppack-bootaddr", default=0,
+                        help="Set boot address for next image, i.e. pass '--bootaddr xxx' to ecppack")
+    parser.add_argument("--ecppack-spimode", default="fast-read",
+                        help="Set slave SPI programming mode")
+    parser.add_argument("--nextpnr-seed", default=1, type=int,
+                        help="seed to pass to nextpnr")
+
+def trellis_argdict(args):
+    return {
+        "nowidelut":    args.yosys_nowidelut,
+        "timingstrict": args.nextpnr_timingstrict,
+        "ignoreloops":  args.nextpnr_ignoreloops,
+        "bootaddr":     args.ecppack_bootaddr,
+        "spimode":      args.ecppack_spimode,
+        "seed":         args.nextpnr_seed,
+    }