From f096030fc8d1b46ace231f4ccb9844a6cce5a749 Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Mon, 18 Dec 2017 20:36:21 -0500 Subject: [PATCH] Import Icestorm backend improvements from Migen. --- litex/boards/platforms/icestick.py | 4 +- litex/build/lattice/common.py | 72 +++++++++++--- litex/build/lattice/diamond.py | 5 + litex/build/lattice/icestorm.py | 147 +++++++++++++++++++---------- litex/build/lattice/platform.py | 7 +- 5 files changed, 168 insertions(+), 67 deletions(-) diff --git a/litex/boards/platforms/icestick.py b/litex/boards/platforms/icestick.py index 0d49944dd..0827561d4 100644 --- a/litex/boards/platforms/icestick.py +++ b/litex/boards/platforms/icestick.py @@ -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() diff --git a/litex/build/lattice/common.py b/litex/build/lattice/common.py index 52cf28d9a..0050e4147 100644 --- a/litex/build/lattice/common.py +++ b/litex/build/lattice/common.py @@ -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 } diff --git a/litex/build/lattice/diamond.py b/litex/build/lattice/diamond.py index 582d24bcf..c3065fe20 100644 --- a/litex/build/lattice/diamond.py +++ b/litex/build/lattice/diamond.py @@ -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) diff --git a/litex/build/lattice/icestorm.py b/litex/build/lattice/icestorm.py index d13321077..6e19a6ed1 100644 --- a/litex/build/lattice/icestorm.py +++ b/litex/build/lattice/icestorm.py @@ -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 diff --git a/litex/build/lattice/platform.py b/litex/build/lattice/platform.py index 1910d09c2..7fbf9c57e 100644 --- a/litex/build/lattice/platform.py +++ b/litex/build/lattice/platform.py @@ -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)