build/lattice/icestorm: cleanup/simplify (and remove arachne-pnr support)
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Sat, 7 Dec 2019 20:43:15 +0000 (21:43 +0100)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Sat, 7 Dec 2019 21:11:17 +0000 (22:11 +0100)
litex/build/lattice/icestorm.py
litex/build/lattice/trellis.py

index e590785089d98caa70fb5ee22a53f7047e062766..b999e483bfa8402a165249db35783200010c977d 100644 (file)
@@ -1,4 +1,5 @@
 # This file is Copyright (c) 2017-2018 William D. Jones <thor0505@comcast.net>
+# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
 # License: BSD
 
 
@@ -12,6 +13,7 @@ from litex.build.generic_platform import *
 from litex.build import tools
 from litex.build.lattice import common
 
+# IO Constraints (.pcf) ----------------------------------------------------------------------------
 
 def _build_pcf(named_sc, named_pc):
     r = ""
@@ -25,6 +27,7 @@ def _build_pcf(named_sc, named_pc):
         r += "\n" + "\n\n".join(named_pc)
     return r
 
+# Timing Constraints (in pre_pack file) ------------------------------------------------------------
 
 def _build_pre_pack(vns, freq_cstrs):
     r = ""
@@ -32,8 +35,65 @@ def _build_pre_pack(vns, freq_cstrs):
         r += """ctx.addClock("{}", {})\n""".format(vns.get_name(sig), freq_cstrs[sig])
     return r
 
+# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------
+
+yosys_template = [
+    "{read_files}",
+    "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
+    "synth_ice40 {synth_opts} -top {build_name} -json {build_name}.json",
+]
+
+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, build_name, synth_opts):
+    ys = []
+    for l in template:
+        ys.append(l.format(
+            build_name = build_name,
+            read_files = _yosys_import_sources(platform),
+            synth_opts = synth_opts
+        ))
+    tools.write_to_file(build_name + ".ys", "\n".join(ys))
+
+def parse_device(device):
+    packages = {
+        "lp384": ["qn32", "cm36", "cm49"],
+        "lp1k": ["swg16tr", "cm36", "cm49", "cm81", "cb81", "qn84", "cm121", "cb121"],
+        "hx1k": ["vq100", "cb132", "tq144"],
+        "lp8k": ["cm81", "cm81:4k", "cm121", "cm121:4k", "cm225", "cm225:4k"],
+        "hx8k": ["bg121", "bg121:4k", "cb132", "cb132:4k", "cm121",
+                 "cm121:4k", "cm225", "cm225:4k", "cm81", "cm81:4k",
+                 "ct256", "tq144:4k"],
+        "up3k": ["sg48", "uwg30"],
+        "up5k": ["sg48", "uwg30"],
+    }
+
+    (family, serie, package) = device.split("-")
+    if family not in ["ice40"]:
+        raise ValueError("Unknown device family {}".format(family))
+    if serie not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k", "up5k"]:
+        raise ValueError("Invalid device serie {}".format(serie))
+    if package not in packages[serie]:
+        raise ValueError("Invalid device package {}".format(package))
+    return (family, serie, package)
+
+# Script -------------------------------------------------------------------------------------------
+
+build_template = [
+    "yosys -q -l {build_name}.rpt {build_name}.ys",
+    "nextpnr-ice40 {pnr_pkg_opts} --pcf {build_name}.pcf --json {build_name}.json --asc {build_name}.txt --pre-pack {build_name}_pre_pack.py",
+    "icepack {build_name}.txt {build_name}.bin"
+]
 
-def _build_script(source, build_template, build_name, **kwargs):
+def _build_script(build_template, build_name, **kwargs):
     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"
@@ -45,16 +105,12 @@ def _build_script(source, build_template, build_name, **kwargs):
 
     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,
-                                               fail_stmt=fail_stmt,
-                                               **kwargs)
+        build_script_contents += s_fail.format(build_name=build_name, fail_stmt=fail_stmt, **kwargs)
 
     build_script_file = "build_" + build_name + script_ext
-    tools.write_to_file(build_script_file, build_script_contents,
-                        force_unix=False)
+    tools.write_to_file(build_script_file, build_script_contents,force_unix=False)
     return build_script_file
 
-
 def _run_script(script):
     if sys.platform in ("win32", "cygwin"):
         shell = ["cmd", "/c"]
@@ -64,123 +120,71 @@ def _run_script(script):
     if subprocess.call(shell + [script]) != 0:
         raise OSError("Subprocess failed")
 
+# LatticeIceStormToolchain -------------------------------------------------------------------------
 
 class LatticeIceStormToolchain:
     attr_translate = {
+        # FIXME: document
         "keep": ("keep", "true"),
-        "no_retiming": None,  # yosys can do retiming via the (non-default)
-                              # "-retime" option to "synth_ice40", but
-                              # yosys does not check for an equivalent
-                              # constraint to prevent retiming on signals.
-        "async_reg": None,  # yosys has no equivalent, and arachne-pnr
-                            # wouldn't take advantage of it anyway.
-
-        # While custom attributes are supported in yosys, neither
-        # arachne-pnr nor icetime currently can take advantage of them
-        # to add fine-grained timing constraints.
-        "mr_ff": None,  # user-defined attribute
-        "mr_false_path": None,  # user-defined attribute
-        "ars_ff1": None,  # user-defined attribute
-        "ars_ff2": None,  # user-defined attribute
-        "ars_false_path": None,  # user-defined attribute
-
-        # ice40 does not have a shift register primitive.
+        "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_ice40_special_overrides
 
     def __init__(self):
-        # Variables within replacement fields should be backend-aware and
-        # update their syntax accordingly. Currently, only {pnr_pkg_opts}
-        # needs this functionality.
-
-        self.yosys_template = [
-            "{read_files}",
-            "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
-            "synth_ice40 -top {build_name} -blif {build_name}.blif",
-        ]
-
-        self.build_template = [
-            "yosys -q -l {build_name}.rpt {build_name}.ys",
-            "arachne-pnr -q -l {pnr_pkg_opts} -p {build_name}.pcf {build_name}.blif -o {build_name}.txt",
-            "icetime {icetime_pkg_opts} -c {freq_constraint} -t -p {build_name}.pcf -r {build_name}.tim {build_name}.txt",
-            "icepack {build_name}.txt {build_name}.bin"
-        ]
-
-        self.nextpnr_yosys_template = [
-            "{read_files}",
-            "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
-            "synth_ice40 {synth_opts} -top {build_name} -json {build_name}.json",
-        ]
-
-        self.nextpnr_build_template = [
-            "yosys -q -l {build_name}.rpt {build_name}.ys",
-            "nextpnr-ice40 {pnr_pkg_opts} --pcf {build_name}.pcf --json {build_name}.json --asc {build_name}.txt --pre-pack {build_name}_pre_pack.py",
-            "icepack {build_name}.txt {build_name}.bin"
-        ]
-
-        self.freq_constraints = dict()
-
-    # platform.device should be of the form "ice40-{lp384, hx1k, etc}-{tq144, etc}"
-    def build(self, platform, fragment, build_dir="build", build_name="top",
-              toolchain_path=None, use_nextpnr=True, synth_opts="", run=True, **kwargs):
+        self.yosys_template = yosys_template
+        self.build_template = build_template
+        self.clocks         = dict()
+
+    def build(self, platform, fragment,
+        build_dir      = "build",
+        build_name     = "top",
+        toolchain_path = None,
+        synth_opts     = "",
+        run            = True,
+        **kwargs):
+
+        # 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)
 
+        # 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)
 
-        if use_nextpnr:
-            chosen_yosys_template = self.nextpnr_yosys_template
-        else:
-            chosen_yosys_template = self.yosys_template
-        ys_contents = "\n".join(_.format(build_name=build_name,
-                                         read_files=self.gen_read_files(platform),
-                                         synth_opts=synth_opts)
-                                for _ in chosen_yosys_template)
-
-        ys_name = build_name + ".ys"
-        tools.write_to_file(ys_name, ys_contents)
-
-        tools.write_to_file(build_name + ".pcf",
-                            _build_pcf(named_sc, named_pc))
-        (family, series_size, package) = self.parse_device_string(platform.device)
-        if use_nextpnr:
-            pnr_pkg_opts = "--" + series_size + " --package " + package
-        else:
-            pnr_pkg_opts = "-d " + self.get_size_string(series_size) + \
-                           " -P " + package
-        icetime_pkg_opts = "-P " + package + " -d " + series_size
-
-        if use_nextpnr:
-            tools.write_to_file(build_name + "_pre_pack.py",
-                                _build_pre_pack(v_output.ns, self.freq_constraints))
-        # icetime can only handle a single global constraint, so we test against the fastest
-        # clock; though imprecise, if the global design satisfies the fastest clock, we can
-        # be sure all other constraints are satisfied.
-        freq_constraint = str(max(self.freq_constraints.values(),
-                                  default=0.0))
-
-        if use_nextpnr:
-            chosen_build_template = self.nextpnr_build_template
-        else:
-            chosen_build_template = self.build_template
-        script = _build_script(source=False,
-                               build_template=chosen_build_template,
-                               build_name=build_name,
-                               pnr_pkg_opts=pnr_pkg_opts,
-                               icetime_pkg_opts=icetime_pkg_opts,
-                               freq_constraint=freq_constraint)
+        # Generate design io constraints file (.pcf)
+        tools.write_to_file(build_name + ".pcf",_build_pcf(named_sc, named_pc))
+
+        # Generate design timing constraints file (in pre_pack file)
+        tools.write_to_file(build_name + "_pre_pack.py", _build_pre_pack(v_output.ns, self.clocks))
+
+        # Generate Yosys script
+        _build_yosys(self.yosys_template, platform, build_name, synth_opts=synth_opts)
+
+        # Translate device to Nextpnr architecture/package
+        (family, serie, package) = parse_device(platform.device)
+        pnr_pkg_opts = "--" + serie + " --package " + package
 
+        # Generate build script
+        script = _build_script(self.build_template, build_name, pnr_pkg_opts=pnr_pkg_opts)
+
+        # Run
         if run:
             _run_script(script)
 
@@ -188,52 +192,8 @@ class LatticeIceStormToolchain:
 
         return v_output.ns
 
-    def parse_device_string(self, device_str):
-        # Arachne only understands packages based on the device size, but
-        # LP for a given size supports packages that HX for the same size
-        # doesn't and vice versa; we need to know the device series due to
-        # icetime.
-        valid_packages = {
-            "lp384": ["qn32", "cm36", "cm49"],
-            "lp1k": ["swg16tr", "cm36", "cm49", "cm81", "cb81", "qn84",
-                     "cm121", "cb121"],
-            "hx1k": ["vq100", "cb132", "tq144"],
-            "lp8k": ["cm81", "cm81:4k", "cm121", "cm121:4k", "cm225",
-                     "cm225:4k"],
-            "hx8k": ["bg121", "bg121:4k", "cb132", "cb132:4k", "cm121",
-                     "cm121:4k", "cm225", "cm225:4k", "cm81", "cm81:4k",
-                     "ct256", "tq144:4k"],
-            "up3k": ["sg48", "uwg30"],
-            "up5k": ["sg48", "uwg30"],
-        }
-
-        (family, series_size, package) = device_str.split("-")
-        if family not in ["ice40"]:
-            raise ValueError("Unknown device family")
-        if series_size not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k", "up5k"]:
-            raise ValueError("Invalid device series/size")
-        if package not in valid_packages[series_size]:
-            raise ValueError("Invalid device package")
-        return (family, series_size, package)
-
-    def get_size_string(self, series_size_str):
-        return series_size_str[2:]
-
-    def gen_read_files(self, platform):
-        incflags = ""
-        read_files = list()
-        for path in platform.verilog_include_paths:
-            incflags += " -I" + path
-        for filename, language, library in platform.sources:
-            read_files.append("read_{}{} {}".format(language,
-                                                    incflags,
-                                                    filename))
-        return "\n".join(read_files)
-
     def add_period_constraint(self, platform, clk, period):
         clk.attr.add("keep")
-        new_freq = 1000.0/period
-        if clk not in self.freq_constraints.keys():
-            self.freq_constraints[clk] = new_freq
-        else:
-            raise ConstraintError("Period constraint already added to signal.")
+        if clk in self.clocks:
+            raise ValueError("A period constraint already exists")
+        self.clocks[clk] = 1e3/period
index 472aa4a5bd12ef4d5e28d23e985ea0db750ffd98..770c336ced0fd8f50a6d248678434cd5add5d09e 100644 (file)
@@ -186,7 +186,7 @@ class LatticeTrellisToolchain:
             fragment = fragment.get_fragment()
         platform.finalize(fragment)
 
-         # Generate verilog
+        # 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"