From 8fb3f9a90d8a6560300b8d0055a33fe29a39ec00 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Fri, 6 Dec 2019 12:51:50 +0100 Subject: [PATCH] build/lattice: cleanup/simplify (no functional changes) icestorm still need to be cleaned up --- litex/build/lattice/diamond.py | 8 +- litex/build/lattice/programmer.py | 8 +- litex/build/lattice/trellis.py | 243 ++++++++++++++++-------------- 3 files changed, 141 insertions(+), 118 deletions(-) diff --git a/litex/build/lattice/diamond.py b/litex/build/lattice/diamond.py index c3b17662..b8aefc80 100644 --- a/litex/build/lattice/diamond.py +++ b/litex/build/lattice/diamond.py @@ -19,7 +19,7 @@ from litex.build.lattice import common def _produces_jedec(device): return device.startswith("LCMX") -# IO Constraints (.lpf) ---------------------------------------------------------------------------- +# Constraints (.lpf) ------------------------------------------------------------------------------- def _format_constraint(c): if isinstance(c, Pins): @@ -176,12 +176,12 @@ class LatticeDiamondToolchain: v_output.write(v_file) platform.add_source(v_file) + # Generate design constraints file (.lpf) + _build_lpf(named_sc, named_pc, build_name) + # Generate design script file (.tcl) _build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name) - # Generate design timing constraints file (.lpf) - _build_lpf(named_sc, named_pc, build_name) - # Generate build script script = _build_script(build_name, platform.device, toolchain_path) diff --git a/litex/build/lattice/programmer.py b/litex/build/lattice/programmer.py index 0c9d5d0e..c8cae794 100644 --- a/litex/build/lattice/programmer.py +++ b/litex/build/lattice/programmer.py @@ -1,4 +1,4 @@ -# This file is Copyright (c) 2015-2018 Florent Kermarrec +# This file is Copyright (c) 2015-2019 Florent Kermarrec # This file is Copyright (c) 2017-2018 William D. Jones # License: BSD @@ -8,6 +8,7 @@ import subprocess from litex.build.generic_programmer import GenericProgrammer from litex.build import tools +# LatticeProgrammer -------------------------------------------------------------------------------- class LatticeProgrammer(GenericProgrammer): needs_bitreverse = False @@ -21,6 +22,7 @@ class LatticeProgrammer(GenericProgrammer): tools.write_to_file(xcf_file, xcf_content) subprocess.call(["pgrcmd", "-infile", xcf_file]) +# IceStormProgrammer ------------------------------------------------------------------------------- class IceStormProgrammer(GenericProgrammer): needs_bitreverse = False @@ -31,6 +33,7 @@ class IceStormProgrammer(GenericProgrammer): def load_bitstream(self, bitstream_file): subprocess.call(["iceprog", "-S", bitstream_file]) +# IceBurnProgrammer -------------------------------------------------------------------------------- class IceBurnProgrammer(GenericProgrammer): def __init__(self, iceburn_path): @@ -42,6 +45,7 @@ class IceBurnProgrammer(GenericProgrammer): def load_bitstream(self, bitstream_file): subprocess.call([self.iceburn, "-evw", bitstream_file]) +# TinyFpgaBProgrammer ------------------------------------------------------------------------------ class TinyFpgaBProgrammer(GenericProgrammer): needs_bitreverse = False @@ -57,6 +61,7 @@ class TinyFpgaBProgrammer(GenericProgrammer): def boot(self): subprocess.call(["tinyfpgab", "-b"]) +# TinyProgProgrammer ------------------------------------------------------------------------------- # Different bootloader protocol requires different application. In the basic # case, command-line arguments are the same. Note that this programmer can @@ -85,6 +90,7 @@ class TinyProgProgrammer(GenericProgrammer): def boot(self): subprocess.call(["tinyprog", "-b"]) +# MyStormProgrammer -------------------------------------------------------------------------------- class MyStormProgrammer(GenericProgrammer): def __init__(self, serial_port): diff --git a/litex/build/lattice/trellis.py b/litex/build/lattice/trellis.py index 8ccd0569..a02e49a9 100644 --- a/litex/build/lattice/trellis.py +++ b/litex/build/lattice/trellis.py @@ -1,4 +1,4 @@ -# This file is Copyright (c) 2018 Florent Kermarrec +# This file is Copyright (c) 2018-2019 Florent Kermarrec # This file is Copyright (c) 2018-2019 David Shah # This file is Copyright (c) 2018 William D. Jones # License: BSD @@ -13,35 +13,7 @@ from litex.build.generic_platform import * from litex.build import tools from litex.build.lattice import common -# TODO: -# - check/document attr_translate. - -nextpnr_ecp5_architectures = { - "lfe5u-25f": "25k", - "lfe5u-45f": "45k", - "lfe5u-85f": "85k", - "lfe5um-25f": "um-25k", - "lfe5um-45f": "um-45k", - "lfe5um-85f": "um-85k", - "lfe5um5g-25f": "um5g-25k", - "lfe5um5g-45f": "um5g-45k", - "lfe5um5g-85f": "um5g-85k", -} - - -def nextpnr_ecp5_package(package): - if "285" in package: - return "CSFBGA285" - elif "256" in package: - return "CABGA256" - elif "381" in package: - return "CABGA381" - elif "554" in package: - return "CABGA554" - elif "756" in package: - return "CABGA756" - raise ValueError("Unknown package") - +# IO Constraints (.lpf) ---------------------------------------------------------------------------- def _format_constraint(c): if isinstance(c, Pins): @@ -54,51 +26,112 @@ def _format_constraint(c): def _format_lpf(signame, pin, others, resname): fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)] - r = "" + lpf = [] for pre, suf in fmt_c: - r += pre + "\"" + signame + "\"" + suf + ";\n" - return r + lpf.append(pre + "\"" + signame + "\"" + suf + ";") + return "\n".join(lpf) -def _build_lpf(named_sc, named_pc): - r = "BLOCK RESETPATHS;\n" - r += "BLOCK ASYNCPATHS;\n" +def _build_lpf(named_sc, named_pc, build_name): + lpf = [] + lpf.append("BLOCK RESETPATHS;") + lpf.append("BLOCK ASYNCPATHS;") for sig, pins, others, resname in named_sc: if len(pins) > 1: for i, p in enumerate(pins): - r += _format_lpf(sig + "[" + str(i) + "]", p, others, resname) + lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname)) else: - r += _format_lpf(sig, pins[0], others, resname) + lpf.append(_format_lpf(sig, pins[0], others, resname)) if named_pc: - r += "\n" + "\n\n".join(named_pc) - return r + lpf.append("\n\n".join(named_pc)) + tools.write_to_file(build_name + ".lpf", "\n".join(lpf)) +# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------ -def _build_script(source, build_template, build_name, architecture, - package, freq_constraint, timingstrict): +_yosys_template = [ + "{read_files}", + "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0", + "synth_ecp5 -abc9 {nwl} -json {build_name}.json -top {build_name}", +] + +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, nowidelut, build_name): + ys = [] + for l in template: + ys.append(l.format( + build_name = build_name, + nwl = "-nowidelut" if nowidelut else "", + read_files = _yosys_import_sources(platform) + )) + tools.write_to_file(build_name + ".ys", "\n".join(ys)) + +nextpnr_ecp5_architectures = { + "lfe5u-25f" : "25k", + "lfe5u-45f" : "45k", + "lfe5u-85f" : "85k", + "lfe5um-25f" : "um-25k", + "lfe5um-45f" : "um-45k", + "lfe5um-85f" : "um-85k", + "lfe5um5g-25f": "um5g-25k", + "lfe5um5g-45f": "um5g-45k", + "lfe5um5g-85f": "um5g-85k", +} + +def nextpnr_ecp5_package(package): + if "256" in package: + return "CABGA256" + elif "285" in package: + return "CSFBGA285" + elif "381" in package: + return "CABGA381" + elif "554" in package: + return "CABGA554" + elif "756" in package: + return "CABGA756" + raise ValueError("Unknown package {}".format(package)) + +# Script ------------------------------------------------------------------------------------------- + +_build_template = [ + "yosys -q -l {build_name}.rpt {build_name}.ys", + "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config \ + --{architecture} --package {package} --freq {freq_constraint} {timefailarg}", + "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit" +] + +def _build_script(source, build_template, build_name, architecture, package, freq_constraint, timingstrict): 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" + script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" fail_stmt = " || exit /b" else: script_ext = ".sh" - build_script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" + script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" 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, - architecture=architecture, - package=package, - freq_constraint=freq_constraint, - timefailarg="--timing-allow-fail" if not timingstrict else "", - fail_stmt=fail_stmt) + script_contents += s_fail.format( + build_name = build_name, + architecture = architecture, + package = package, + freq_constraint = freq_constraint, + timefailarg = "--timing-allow-fail" if not timingstrict else "", + 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) - return build_script_file + script_file = "build_" + build_name + script_ext + tools.write_to_file(script_file, script_contents, force_unix=False) + return script_file def _run_script(script): if sys.platform in ("win32", "cygwin"): @@ -109,106 +142,90 @@ def _run_script(script): if subprocess.call(shell + [script]) != 0: raise OSError("Subprocess failed") - -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) - +# LatticeTrellisToolchain -------------------------------------------------------------------------- class LatticeTrellisToolchain: attr_translate = { # FIXME: document "keep": ("keep", "true"), - "no_retiming": None, - "async_reg": None, - "mr_ff": None, - "mr_false_path": None, - "ars_ff1": None, - "ars_ff2": None, - "ars_false_path": None, + "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_ecpx_trellis_special_overrides def __init__(self): - self.yosys_template = [ - "{read_files}", - "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0", - "synth_ecp5 -abc9 {nwl} -json {build_name}.json -top {build_name}", - ] - - self.build_template = [ - "yosys -q -l {build_name}.rpt {build_name}.ys", - "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config --{architecture} --package {package} --freq {freq_constraint} {timefailarg}", - "ecppack {build_name}.config --svf {build_name}.svf --bit {build_name}.bit" - ] - + self.yosys_template = _yosys_template + self.build_template = _build_template self.freq_constraints = dict() - def build(self, platform, fragment, build_dir="build", build_name="top", - toolchain_path=None, run=True, - nowidelut=False, timingstrict=False, - **kwargs): + def build(self, platform, fragment, + build_dir = "build", + build_name = "top", + toolchain_path = None, + run = True, + nowidelut = False, + timingstrict = False, + **kwargs): + + # Get default toolchain path (if not specified) if toolchain_path is None: toolchain_path = "/usr/share/trellis/" + + # Create build directory os.makedirs(build_dir, exist_ok=True) cwd = os.getcwd() os.chdir(build_dir) - # generate verilog + # Finalize design if not isinstance(fragment, _Fragment): fragment = fragment.get_fragment() platform.finalize(fragment) - top_output = platform.get_verilog(fragment, name=build_name, **kwargs) - named_sc, named_pc = platform.resolve_signals(top_output.ns) + # 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" - top_output.write(top_file) + v_output.write(top_file) platform.add_source(top_file) - # generate constraints - tools.write_to_file(build_name + ".lpf", - _build_lpf(named_sc, named_pc)) + # Generate design constraints file (.lpf) + _build_lpf(named_sc, named_pc, build_name) - # generate yosys script - yosys_script_file = build_name + ".ys" - yosys_script_contents = "\n".join(_.format(build_name=build_name, - nwl="-nowidelut" if nowidelut else "", - read_files=yosys_import_sources(platform)) - for _ in self.yosys_template) - tools.write_to_file(yosys_script_file, yosys_script_contents) + # Generate Yosys script + _build_yosys(self.yosys_template, platform, nowidelut, build_name) - # transform platform.device to nextpnr's architecture + # Translate device to Nextpnr architecture/package (family, size, package) = platform.device.split("-") architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()] - package = nextpnr_ecp5_package(package) - freq_constraint = str(max(self.freq_constraints.values(), - default=0.0)) + package = nextpnr_ecp5_package(package) + + freq_constraint = str(max(self.freq_constraints.values(), default=0.0)) - script = _build_script(False, self.build_template, build_name, - architecture, package, freq_constraint, - timingstrict) + # Generate build script + script = _build_script(False, self.build_template, build_name, architecture, package, + freq_constraint, timingstrict) - # run scripts + # Run if run: _run_script(script) os.chdir(cwd) - return top_output.ns + return v_output.ns # Until nextpnr-ecp5 can handle multiple clock domains, use the same # approach as the icestorm and use the fastest clock for timing # constraints. def add_period_constraint(self, platform, clk, period): - platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format(freq=str(float(1/period)*1000), clk="{clk}"), clk=clk) + platform.add_platform_command("""FREQUENCY PORT "{clk}" {freq} MHz;""".format( + freq=str(float(1/period)*1000), clk="{clk}"), clk=clk) def trellis_args(parser): parser.add_argument("--yosys-nowidelut", action="store_true", @@ -218,6 +235,6 @@ def trellis_args(parser): def trellis_argdict(args): return { - "nowidelut": args.yosys_nowidelut, + "nowidelut": args.yosys_nowidelut, "timingstrict": args.nextpnr_timingstrict, } -- 2.30.2