Import Icestorm backend improvements from Migen.
authorWilliam D. Jones <thor0505@comcast.net>
Tue, 19 Dec 2017 01:36:21 +0000 (20:36 -0500)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Tue, 26 Dec 2017 22:57:13 +0000 (23:57 +0100)
litex/boards/platforms/icestick.py
litex/build/lattice/common.py
litex/build/lattice/diamond.py
litex/build/lattice/icestorm.py
litex/build/lattice/platform.py

index 0d49944dd9b5b3dc44855df65447b050df8505d2..0827561d4a4fc8bac9842b6a6f56a35cc36d22b9 100644 (file)
@@ -50,8 +50,8 @@ class Platform(LatticePlatform):
     default_clk_period = 83.333
 
     def __init__(self):
-        LatticePlatform.__init__(self, "ice40-1k-tq144", _io, _connectors,
-            toolchain="icestorm")
+        LatticePlatform.__init__(self, "ice40-hx1k-tq144", _io, _connectors,
+                                 toolchain="icestorm")
 
     def create_programmer(self):
         return IceStormProgrammer()
index 52cf28d9a5b040c45170fa065a641fef649d9ff7..0050e4147ada5288812d3d086bdf96ef32bd0e37 100644 (file)
@@ -4,38 +4,80 @@ from litex.gen.genlib.io import *
 from litex.gen.genlib.resetsync import AsyncResetSynchronizer
 
 
-class LatticeAsyncResetSynchronizerImpl(Module):
+class DiamondAsyncResetSynchronizerImpl(Module):
     def __init__(self, cd, async_reset):
         rst1 = Signal()
         self.specials += [
             Instance("FD1S3BX", i_D=0, i_PD=async_reset,
-                i_CK=cd.clk, o_Q=rst1),
+                     i_CK=cd.clk, o_Q=rst1),
             Instance("FD1S3BX", i_D=rst1, i_PD=async_reset,
-                i_CK=cd.clk, o_Q=cd.rst)
+                     i_CK=cd.clk, o_Q=cd.rst)
         ]
 
 
-class LatticeAsyncResetSynchronizer:
+class DiamondAsyncResetSynchronizer:
     @staticmethod
     def lower(dr):
-        return LatticeAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
+        return DiamondAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
 
 
-class LatticeDDROutputImpl(Module):
+class DiamondDDROutputImpl(Module):
     def __init__(self, i1, i2, o, clk):
         self.specials += Instance("ODDRXD1",
-                synthesis_directive="ODDRAPPS=\"SCLK_ALIGNED\"",
-                i_SCLK=clk,
-                i_DA=i1, i_DB=i2, o_Q=o,
-        )
+                                  synthesis_directive="ODDRAPPS=\"SCLK_ALIGNED\"",
+                                  i_SCLK=clk,
+                                  i_DA=i1, i_DB=i2, o_Q=o)
 
 
-class LatticeDDROutput:
+class DiamondDDROutput:
     @staticmethod
     def lower(dr):
-        return LatticeDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk)
+        return DiamondDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk)
 
-lattice_special_overrides = {
-    AsyncResetSynchronizer: LatticeAsyncResetSynchronizer,
-    DDROutput: LatticeDDROutput
+diamond_special_overrides = {
+    AsyncResetSynchronizer: DiamondAsyncResetSynchronizer,
+    DDROutput: DiamondDDROutput
+}
+
+
+class IcestormAsyncResetSynchronizerImpl(Module):
+    def __init__(self, cd, async_reset):
+        rst1 = Signal()
+        self.specials += [
+            Instance("SB_DFFS", i_D=0, i_S=async_reset,
+                     i_C=cd.clk, o_Q=rst1),
+            Instance("SB_DFFS", i_D=rst1, i_S=async_reset,
+                     i_C=cd.clk, o_Q=cd.rst)
+        ]
+
+
+class IcestormAsyncResetSynchronizer:
+    @staticmethod
+    def lower(dr):
+        return IcestormAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
+
+
+class IcestormDifferentialOutputImpl(Module):
+    def __init__(self, i, o_p, o_n):
+        self.specials += Instance("SB_IO",
+                                  p_PIN_TYPE=C(0b011000, 6),
+                                  p_IO_STANDARD="SB_LVCMOS",
+                                  io_PACKAGE_PIN=o_p,
+                                  i_D_OUT_0=i)
+
+        self.specials += Instance("SB_IO",
+                                  p_PIN_TYPE=C(0b011000, 6),
+                                  p_IO_STANDARD="SB_LVCMOS",
+                                  io_PACKAGE_PIN=o_n,
+                                  i_D_OUT_0=~i)
+
+
+class IcestormDifferentialOutput:
+    @staticmethod
+    def lower(dr):
+        return IcestormDifferentialOutputImpl(dr.i, dr.o_p, dr.o_n)
+
+icestorm_special_overrides = {
+    AsyncResetSynchronizer: IcestormAsyncResetSynchronizer,
+    DifferentialOutput:     IcestormDifferentialOutput
 }
index 582d24bcfe4435337a1aaf1bc1e7edfe44d9b1b0..c3065fe20cecbf8fa91b5c521bef791d9f94cd28 100644 (file)
@@ -7,6 +7,7 @@ import subprocess
 import shutil
 
 from litex.gen.fhdl.structure import _Fragment
+from litex.gen.fhdl.verilog import DummyAttrTranslate
 
 from litex.build.generic_platform import *
 from litex.build import tools
@@ -89,6 +90,10 @@ def _run_diamond(build_name, toolchain_path, ver=None):
 
 
 class LatticeDiamondToolchain:
+    attr_translate = DummyAttrTranslate()
+
+    special_overrides = common.diamond_special_overrides
+
     def build(self, platform, fragment, build_dir="build", build_name="top",
               toolchain_path="/opt/Diamond", run=True, **kwargs):
         os.makedirs(build_dir, exist_ok=True)
index d13321077eca7eb482eb7ed049919394310ff9ef..6e19a6ed16ed13913f860dd7ccbf4697e008378f 100644 (file)
@@ -9,6 +9,7 @@ from litex.gen.fhdl.structure import _Fragment
 
 from litex.build.generic_platform import *
 from litex.build import tools
+from litex.build.lattice import common
 
 
 def _format_constraint(c):
@@ -32,23 +33,8 @@ def _build_pcf(named_sc, named_pc):
     return r
 
 
-def _build_yosys(device, sources, vincpaths, build_name):
-    ys_contents = ""
-    incflags = ""
-    for path in vincpaths:
-        incflags += " -I" + path
-    for filename, language, library in sources:
-        ys_contents += "read_{}{} {}\n".format(language, incflags, filename)
-
-    ys_contents += """synth_ice40 -top top -blif {build_name}.blif""".format(
-        build_name=build_name)
-
-    ys_name = build_name + ".ys"
-    tools.write_to_file(ys_name, ys_contents)
-
-
-def _run_icestorm(build_name, source, yosys_opt, pnr_opt,
-                  icetime_opt, icepack_opt):
+def _run_icestorm(source, build_template, build_name, pnr_pkg_opts,
+                  icetime_pkg_opts, icetime_constraint):
     if sys.platform == "win32" or sys.platform == "cygwin":
         source_cmd = "call "
         script_ext = ".bat"
@@ -62,16 +48,14 @@ def _run_icestorm(build_name, source, yosys_opt, pnr_opt,
         build_script_contents = "# Autogenerated by LiteX\nset -e\n"
         fail_stmt = ""
 
-    build_script_contents += """
-yosys {yosys_opt} -l {build_name}.rpt {build_name}.ys{fail_stmt}
-arachne-pnr {pnr_opt} -p {build_name}.pcf {build_name}.blif -o {build_name}.txt{fail_stmt}
-icetime {icetime_opt} -t -p {build_name}.pcf -r {build_name}.tim {build_name}.txt{fail_stmt}
-icepack {icepack_opt} {build_name}.txt {build_name}.bin{fail_stmt}
-"""
-    build_script_contents = build_script_contents.format(
-        build_name=build_name,
-        yosys_opt=yosys_opt, pnr_opt=pnr_opt, icepack_opt=icepack_opt,
-        icetime_opt=icetime_opt, fail_stmt=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,
+                                               pnr_pkg_opts=pnr_pkg_opts,
+                                               icetime_pkg_opts=icetime_pkg_opts,
+                                               icetime_constraint=icetime_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)
@@ -82,14 +66,47 @@ icepack {icepack_opt} {build_name}.txt {build_name}.bin{fail_stmt}
 
 
 class LatticeIceStormToolchain:
+    attr_translate = {
+        "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_shreg_extract": None
+    }
+
+    special_overrides = common.icestorm_special_overrides
+
     def __init__(self):
-        self.yosys_opt = "-q"
-        self.pnr_opt = "-q"
-        self.icetime_opt = ""
-        self.icepack_opt = ""
+        self.yosys_template = [
+            "{read_files}",
+            "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0",
+            "synth_ice40 -top top -blif {build_name}.blif",
+        ]
+
+        self.build_template = [
+            "yosys -q -l {build_name}.rpt {build_name}.ys",
+            "arachne-pnr -q {pnr_pkg_opts} -p {build_name}.pcf {build_name}.blif -o {build_name}.txt",
+            "icetime {icetime_pkg_opts} -c {icetime_constraint} -t -p {build_name}.pcf -r {build_name}.tim {build_name}.txt",
+            "icepack {build_name}.txt {build_name}.bin"
+        ]
+
         self.freq_constraints = dict()
 
-    # platform.device should be of the form "ice40-{1k,8k}-{tq144, etc}""
+    # platform.device should be of the form "ice40-{lp384, hx1k, etc}-{tq144, etc}"
     def build(self, platform, fragment, build_dir="build", build_name="top",
               run=True):
         os.makedirs(build_dir, exist_ok=True)
@@ -104,35 +121,69 @@ class LatticeIceStormToolchain:
         named_sc, named_pc = platform.resolve_signals(v_output.ns)
         v_file = build_name + ".v"
         v_output.write(v_file)
-        sources = platform.sources | {(v_file, "verilog", "work")}
-        _build_yosys(platform.device, sources, platform.verilog_include_paths,
-                     build_name)
+
+        ys_contents = "\n".join(_.format(build_name=build_name,
+                                         read_files=self.gen_read_files(platform, v_file))
+                                for _ in self.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))
         if run:
-            (family, size, package) = self.parse_device_string(platform.device)
-            pnr_opt = self.pnr_opt + " -d " + size + " -P " + package
-            # TODO: PNR will probably eventually support LP devices.
-            icetime_opt = self.icetime_opt + " -P " + package + \
-                " -d " + "hx" + size + " -c " + \
-                str(max(self.freq_constraints.values(), default=0.0))
-            _run_icestorm(build_name, False, self.yosys_opt, pnr_opt,
-                          icetime_opt, self.icepack_opt)
+            (family, series_size, package) = self.parse_device_string(platform.device)
+            pnr_pkg_opts = "-d " + self.get_size_string(series_size) + \
+                           " -P " + package
+            icetime_pkg_opts = "-P " + package + " -d " + series_size
+            icetime_constraint = str(max(self.freq_constraints.values(),
+                                         default=0.0))
+
+            _run_icestorm(False, self.build_template, build_name, pnr_pkg_opts,
+                          icetime_pkg_opts, icetime_constraint)
 
         os.chdir(cwd)
 
         return v_output.ns
 
     def parse_device_string(self, device_str):
-        (family, size, package) = device_str.split("-")
+        # 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": ["cb132", "cb132:4k", "tq144:4k", "cm225", "ct256"],
+        }
+
+        (family, series_size, package) = device_str.split("-")
         if family not in ["ice40"]:
             raise ValueError("Unknown device family")
-        if size not in ["1k", "8k"]:
-            raise ValueError("Invalid device size")
-        if package not in ["tq144", "ct256", "vq100"]:
+        if series_size not in ["lp384", "lp1k", "hx1k", "lp8k", "hx8k"]:
+            raise ValueError("Invalid device series/size")
+        if package not in valid_packages[series_size]:
             raise ValueError("Invalid device package")
-        return (family, size, 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, main):
+        sources = platform.sources | {(main, "verilog", "work")}
+        incflags = ""
+        read_files = list()
+        for path in platform.verilog_include_paths:
+            incflags += " -I" + path
+        for filename, language, library in sources:
+            read_files.append("read_{}{} {}".format(language,
+                                                    incflags,
+                                                    filename))
+        return "\n".join(read_files)
 
     # icetime can only handle a single global constraint. Pending more
     # finely-tuned analysis features in arachne-pnr and IceStorm, save
index 1910d09c2128733f0896fcf570f81d6033d96bc9..7fbf9c57e92be3595d10059ba6485487b5e30e6a 100644 (file)
@@ -16,9 +16,12 @@ class LatticePlatform(GenericPlatform):
             raise ValueError("Unknown toolchain")
 
     def get_verilog(self, *args, special_overrides=dict(), **kwargs):
-        so = dict(common.lattice_special_overrides)
+        so = dict()  # No common overrides between ECP and ice40.
+        so.update(self.toolchain.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,
+                                           attr_translate=self.toolchain.attr_translate,
+                                           **kwargs)
 
     def build(self, *args, **kwargs):
         return self.toolchain.build(self, *args, **kwargs)