diff --git a/litex/build/lattice/icestorm.py b/litex/build/lattice/icestorm.py new file mode 100644 index 000000000..d13321077 --- /dev/null +++ b/litex/build/lattice/icestorm.py @@ -0,0 +1,148 @@ +# This file is Copyright (c) 2017 William D. Jones +# License: BSD + +import os +import sys +import subprocess + +from litex.gen.fhdl.structure import _Fragment + +from litex.build.generic_platform import * +from litex.build import tools + + +def _format_constraint(c): + pass + + +def _format_pcf(signame, pin, others, resname): + return "set_io " + signame + " " + pin + "\n" + + +def _build_pcf(named_sc, named_pc): + r = "" + for sig, pins, others, resname in named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + r += _format_pcf(sig + "[" + str(i) + "]", p, others, resname) + else: + r += _format_pcf(sig, pins[0], others, resname) + if named_pc: + r += "\n" + "\n\n".join(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): + if sys.platform == "win32" or sys.platform == "cygwin": + source_cmd = "call " + script_ext = ".bat" + shell = ["cmd", "/c"] + build_script_contents = "@echo off\nrem Autogenerated by LiteX\n" + fail_stmt = " || exit /b" + else: + source_cmd = "source " + script_ext = ".sh" + shell = ["bash"] + 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) + build_script_file = "build_" + build_name + script_ext + tools.write_to_file(build_script_file, build_script_contents, + force_unix=False) + command = shell + [build_script_file] + r = subprocess.call(command) + if r != 0: + raise OSError("Subprocess failed") + + +class LatticeIceStormToolchain: + def __init__(self): + self.yosys_opt = "-q" + self.pnr_opt = "-q" + self.icetime_opt = "" + self.icepack_opt = "" + self.freq_constraints = dict() + + # platform.device should be of the form "ice40-{1k,8k}-{tq144, etc}"" + def build(self, platform, fragment, build_dir="build", build_name="top", + run=True): + os.makedirs(build_dir, exist_ok=True) + cwd = os.getcwd() + os.chdir(build_dir) + + if not isinstance(fragment, _Fragment): + fragment = fragment.get_fragment() + platform.finalize(fragment) + + v_output = platform.get_verilog(fragment) + 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) + + 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) + + os.chdir(cwd) + + return v_output.ns + + def parse_device_string(self, device_str): + (family, 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"]: + raise ValueError("Invalid device package") + return (family, size, package) + + # icetime can only handle a single global constraint. Pending more + # finely-tuned analysis features in arachne-pnr and IceStorm, save + # all the constraints in a dictionary and test against the fastest clk. + # Though imprecise, if the global design satisfies the fastest clock, + # we can be sure all other constraints are satisfied. + def add_period_constraint(self, platform, clk, period): + 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.") diff --git a/litex/build/lattice/platform.py b/litex/build/lattice/platform.py index d7170636f..1910d09c2 100644 --- a/litex/build/lattice/platform.py +++ b/litex/build/lattice/platform.py @@ -1,5 +1,5 @@ from litex.build.generic_platform import GenericPlatform -from litex.build.lattice import common, diamond +from litex.build.lattice import common, diamond, icestorm class LatticePlatform(GenericPlatform): @@ -9,6 +9,9 @@ class LatticePlatform(GenericPlatform): GenericPlatform.__init__(self, *args, **kwargs) if toolchain == "diamond": self.toolchain = diamond.LatticeDiamondToolchain() + elif toolchain == "icestorm": + self.bitstream_ext = ".bin" + self.toolchain = icestorm.LatticeIceStormToolchain() else: raise ValueError("Unknown toolchain") diff --git a/litex/build/lattice/programmer.py b/litex/build/lattice/programmer.py index 0923a65c2..94282d42c 100644 --- a/litex/build/lattice/programmer.py +++ b/litex/build/lattice/programmer.py @@ -20,3 +20,24 @@ class LatticeProgrammer(GenericProgrammer): else: pgrcmr = 'pgrcmr' subprocess.call([pgrcmd, "-infile", xcf_file]) + + +class IceStormProgrammer(GenericProgrammer): + needs_bitreverse = False + + def flash(self, address, bitstream_file): + subprocess.call(["iceprog", "-o", str(address), bitstream_file]) + + def load_bitstream(self, bitstream_file): + subprocess.call(["iceprog", "-S", bitstream_file]) + + +class IceBurnProgrammer(GenericProgrammer): + def __init__(self, iceburn_path): + GenericProgrammer.__init__(self) + self.iceburn = iceburn_path + + needs_bitreverse = False + + def load_bitstream(self, bitstream_file): + subprocess.call([self.iceburn, "-evw", bitstream_file])