diff --git a/litex/build/lattice/icestorm.py b/litex/build/lattice/icestorm.py index e59078508..b999e483b 100644 --- a/litex/build/lattice/icestorm.py +++ b/litex/build/lattice/icestorm.py @@ -1,4 +1,5 @@ # This file is Copyright (c) 2017-2018 William D. Jones +# This file is Copyright (c) 2019 Florent Kermarrec # 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 ------------------------------------------------------------------ -def _build_script(source, build_template, build_name, **kwargs): +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(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 = yosys_template + self.build_template = build_template + self.clocks = dict() - 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", - ] + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + toolchain_path = None, + synth_opts = "", + run = True, + **kwargs): - 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): + # 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) + # Generate design io constraints file (.pcf) + tools.write_to_file(build_name + ".pcf",_build_pcf(named_sc, named_pc)) - ys_name = build_name + ".ys" - tools.write_to_file(ys_name, ys_contents) + # 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)) - 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 + # Generate Yosys script + _build_yosys(self.yosys_template, platform, build_name, synth_opts=synth_opts) - 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)) + # Translate device to Nextpnr architecture/package + (family, serie, package) = parse_device(platform.device) + pnr_pkg_opts = "--" + serie + " --package " + package - 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 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 diff --git a/litex/build/lattice/trellis.py b/litex/build/lattice/trellis.py index 472aa4a5b..770c336ce 100644 --- a/litex/build/lattice/trellis.py +++ b/litex/build/lattice/trellis.py @@ -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"