From cb86728ad14bae38e60f8d195c00afa4690a5913 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 12 Nov 2018 10:23:10 +0100 Subject: [PATCH] build/lattice: import changes from migen --- litex/build/altera/quartus.py | 2 +- litex/build/lattice/common.py | 15 +-- litex/build/lattice/diamond.py | 108 ++++++++++++------ litex/build/lattice/icestorm.py | 4 +- litex/build/lattice/platform.py | 6 +- .../lattice/{prjtrellis.py => trellis.py} | 101 +++++++++++----- litex/build/xilinx/ise.py | 4 +- litex/build/xilinx/vivado.py | 4 +- 8 files changed, 162 insertions(+), 82 deletions(-) rename litex/build/lattice/{prjtrellis.py => trellis.py} (58%) diff --git a/litex/build/altera/quartus.py b/litex/build/altera/quartus.py index 0dcdb53a..76ec775c 100644 --- a/litex/build/altera/quartus.py +++ b/litex/build/altera/quartus.py @@ -92,7 +92,7 @@ def _build_files(device, sources, vincpaths, named_sc, named_pc, build_name): def _run_quartus(build_name, quartus_path): - build_script_contents = """# Autogenerated by LiteX + build_script_contents = """# Autogenerated by Migen set -e diff --git a/litex/build/lattice/common.py b/litex/build/lattice/common.py index 4e843295..a4f21130 100644 --- a/litex/build/lattice/common.py +++ b/litex/build/lattice/common.py @@ -35,14 +35,13 @@ class LatticeECPXDDROutput: def lower(dr): return LatticeECPXDDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk) - lattice_ecpx_special_overrides = { AsyncResetSynchronizer: LatticeECPXAsyncResetSynchronizer, - DDROutput: LatticeECPXDDROutput + DDROutput: LatticeECPXDDROutput } -class LatticeECPXPrjTrellisTristateImpl(Module): +class LatticeECPXTrellisTristateImpl(Module): def __init__(self, io, o, oe, i): nbits, sign = value_bits_sign(io) for bit in range(nbits): @@ -55,15 +54,14 @@ class LatticeECPXPrjTrellisTristateImpl(Module): i_T=~oe, ) -class LatticeECPXPrjTrellisTristate(Module): +class LatticeECPXTrellisTristate(Module): @staticmethod def lower(dr): - return LatticeECPXPrjTrellisTristateImpl(dr.target, dr.o, dr.oe, dr.i) + return LatticeECPXTrellisTristateImpl(dr.target, dr.o, dr.oe, dr.i) - -lattice_ecpx_prjtrellis_special_overrides = { +lattice_ecpx_trellis_special_overrides = { AsyncResetSynchronizer: LatticeECPXAsyncResetSynchronizer, - Tristate: LatticeECPXPrjTrellisTristate, + Tristate: LatticeECPXTrellisTristate, DDROutput: LatticeECPXDDROutput } @@ -125,7 +123,6 @@ class LatticeiCE40DifferentialOutput: def lower(dr): return LatticeiCE40DifferentialOutputImpl(dr.i, dr.o_p, dr.o_n) - lattice_ice40_special_overrides = { AsyncResetSynchronizer: LatticeiCE40AsyncResetSynchronizer, Tristate: LatticeiCE40Tristate, diff --git a/litex/build/lattice/diamond.py b/litex/build/lattice/diamond.py index 17516aa4..1569f567 100644 --- a/litex/build/lattice/diamond.py +++ b/litex/build/lattice/diamond.py @@ -15,13 +15,17 @@ from litex.build import tools from litex.build.lattice import common +def _produces_jedec(device): + return device.startswith("LCMX") + + def _format_constraint(c): if isinstance(c, Pins): return ("LOCATE COMP ", " SITE " + "\"" + c.identifiers[0] + "\"") elif isinstance(c, IOStandard): return ("IOBUF PORT ", " IO_TYPE=" + c.name) elif isinstance(c, Misc): - return c.misc + return ("IOBUF PORT ", " " + c.misc) def _format_lpf(signame, pin, others, resname): @@ -48,56 +52,87 @@ def _build_lpf(named_sc, named_pc): def _build_files(device, sources, vincpaths, build_name): tcl = [] - tcl.append("prj_project new -name \"{}\" -impl \"implementation\" -dev {} -synthesis \"synplify\"".format(build_name, device)) + tcl.append("prj_project new -name \"{}\" -impl \"impl\" -dev {} -synthesis \"synplify\"".format(build_name, device)) for path in vincpaths: tcl.append("prj_impl option {include path} {\"" + path + "\"}") for filename, language, library in sources: tcl.append("prj_src add \"" + filename + "\" -work " + library) tcl.append("prj_impl option top \"{}\"".format(build_name)) - tcl.append("prj_run Synthesis -impl implementation -forceOne") - tcl.append("prj_run Translate -impl implementation") - tcl.append("prj_run Map -impl implementation") - tcl.append("prj_run PAR -impl implementation") - tcl.append("prj_run Export -impl implementation -task Bitgen") + tcl.append("prj_project save") + tcl.append("prj_run Synthesis -impl impl -forceOne") + tcl.append("prj_run Translate -impl impl") + tcl.append("prj_run Map -impl impl") + tcl.append("prj_run PAR -impl impl") + tcl.append("prj_run Export -impl impl -task Bitgen") + if _produces_jedec(device): + tcl.append("prj_run Export -impl impl -task Jedecgen") tools.write_to_file(build_name + ".tcl", "\n".join(tcl)) -def _run_diamond(build_name, toolchain_path, ver=None): - if sys.platform == "win32" or sys.platform == "cygwin": - build_script_contents = "REM Autogenerated by LiteX\n" - build_script_contents += "pnmainc " + build_name + ".tcl\n" - build_script_file = "build_" + build_name + ".bat" - tools.write_to_file(build_script_file, build_script_contents) - r = subprocess.call([build_script_file]) - shutil.copy(os.path.join("implementation", build_name + "_implementation.bit"), build_name + ".bit") - shutil.copy(os.path.join("implementation", build_name + "_implementation.jed"), build_name + ".jed") - elif sys.platform == "linux": - bindir = os.path.join(toolchain_path, 'bin/lin64') - envfile = os.path.join(bindir, 'diamond_env') - build_script_contents = "# Autogenerated by LiteX\n" - build_script_contents += "set -e\n" - build_script_contents += "bindir='{}/'\n".format(bindir) - build_script_contents += "source {}\n".format(envfile) - build_script_contents += "diamondc {}.tcl | tee build.log\n".format(build_name) - build_script_file = "build_{}.sh".format(build_name) - - tools.write_to_file(build_script_file, build_script_contents) - r = subprocess.call(["bash", build_script_file]) - shutil.copy(os.path.join("implementation", build_name + "_implementation.bit"), build_name + ".bit") +def _build_script(build_name, device, toolchain_path, ver=None): + if sys.platform in ("win32", "cygwin"): + script_ext = ".bat" + build_script_contents = "@echo off\nrem Autogenerated by Migen\n\n" + copy_stmt = "copy" + fail_stmt = " || exit /b" + else: + script_ext = ".sh" + build_script_contents = "# Autogenerated by Migen\nset -e\n\n" + copy_stmt = "cp" + fail_stmt = "" + + if sys.platform not in ("win32", "cygwin"): + build_script_contents += "bindir={}\n".format(toolchain_path) + build_script_contents += ". ${{bindir}}/diamond_env{fail_stmt}\n".format( + fail_stmt=fail_stmt) + build_script_contents += "{pnmainc} {tcl_script}{fail_stmt}\n".format( + pnmainc=os.path.join(toolchain_path, "pnmainc"), + tcl_script=build_name + ".tcl", + fail_stmt=fail_stmt) + for ext in (".bit", ".jed"): + if ext == ".jed" and not _produces_jedec(device): + continue + build_script_contents += "{copy_stmt} {diamond_product} {migen_product}" \ + "{fail_stmt}\n".format( + copy_stmt=copy_stmt, + fail_stmt=fail_stmt, + diamond_product=os.path.join("impl", build_name + "_impl" + ext), + migen_product=build_name + ext) + + 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 + + +def _run_script(script): + if sys.platform in ("win32", "cygwin"): + shell = ["cmd", "/c"] else: - raise NotImplementedError + shell = ["bash"] - if r != 0: + if subprocess.call(shell + [script]) != 0: raise OSError("Subprocess failed") class LatticeDiamondToolchain: - attr_translate = DummyAttrTranslate() + attr_translate = { + # FIXME: document + "keep": ("syn_keep", "true"), + "no_retiming": ("syn_no_retiming", "true"), + "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_special_overrides def build(self, platform, fragment, build_dir="build", build_name="top", - toolchain_path="/opt/Diamond", run=True, **kwargs): + toolchain_path="/opt/Diamond", run=True): os.makedirs(build_dir, exist_ok=True) cwd = os.getcwd() os.chdir(build_dir) @@ -106,17 +141,18 @@ class LatticeDiamondToolchain: fragment = fragment.get_fragment() platform.finalize(fragment) - v_output = platform.get_verilog(fragment, name=build_name, **kwargs) + 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")] + sources = platform.sources | {(v_file, "verilog", "work")} _build_files(platform.device, sources, platform.verilog_include_paths, build_name) tools.write_to_file(build_name + ".lpf", _build_lpf(named_sc, named_pc)) + script = _build_script(build_name, platform.device, toolchain_path) if run: - _run_diamond(build_name, toolchain_path) + _run_script(script) os.chdir(cwd) diff --git a/litex/build/lattice/icestorm.py b/litex/build/lattice/icestorm.py index f5b3586b..72eb0023 100644 --- a/litex/build/lattice/icestorm.py +++ b/litex/build/lattice/icestorm.py @@ -37,11 +37,11 @@ def _build_script(source, build_template, build_name, pnr_pkg_opts, icetime_pkg_opts, freq_constraint): if sys.platform in ("win32", "cygwin"): script_ext = ".bat" - build_script_contents = "@echo off\nrem Autogenerated by LiteX\n" + build_script_contents = "@echo off\nrem Autogenerated by Migen\n" fail_stmt = " || exit /b" else: script_ext = ".sh" - build_script_contents = "# Autogenerated by LiteX\nset -e\n" + build_script_contents = "# Autogenerated by Migen\nset -e\n" fail_stmt = "" for s in build_template: diff --git a/litex/build/lattice/platform.py b/litex/build/lattice/platform.py index 385fba5b..0091f97b 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, icestorm, prjtrellis +from litex.build.lattice import common, diamond, icestorm, trellis class LatticePlatform(GenericPlatform): @@ -9,8 +9,8 @@ class LatticePlatform(GenericPlatform): GenericPlatform.__init__(self, *args, **kwargs) if toolchain == "diamond": self.toolchain = diamond.LatticeDiamondToolchain() - elif toolchain == "prjtrellis": - self.toolchain = prjtrellis.LatticePrjTrellisToolchain() + elif toolchain == "trellis": + self.toolchain = trellis.LatticeTrellisToolchain() elif toolchain == "icestorm": self.bitstream_ext = ".bin" self.toolchain = icestorm.LatticeIceStormToolchain() diff --git a/litex/build/lattice/prjtrellis.py b/litex/build/lattice/trellis.py similarity index 58% rename from litex/build/lattice/prjtrellis.py rename to litex/build/lattice/trellis.py index 79df493a..8f272920 100644 --- a/litex/build/lattice/prjtrellis.py +++ b/litex/build/lattice/trellis.py @@ -1,8 +1,10 @@ # This file is Copyright (c) 2018 Florent Kermarrec +# This file is Copyright (c) 2018 William D. Jones # License: BSD import os import subprocess +import sys from migen.fhdl.structure import _Fragment @@ -11,7 +13,6 @@ from litex.build import tools from litex.build.lattice import common # TODO: -# - add timing constraint support. # - check/document attr_translate. nextpnr_ecp5_architectures = { @@ -39,6 +40,7 @@ def _format_constraint(c): def _format_lpf(signame, pin, others, resname): fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)] r = "" + print(fmt_c) for pre, suf in fmt_c: r += pre + "\"" + signame + "\"" + suf + ";\n" return r @@ -58,6 +60,41 @@ def _build_lpf(named_sc, named_pc): return r +def _build_script(source, build_template, build_name, architecture, + basecfg, freq_constraint): + if sys.platform in ("win32", "cygwin"): + script_ext = ".bat" + build_script_contents = "@echo off\nrem Autogenerated by Migen\n\n" + fail_stmt = " || exit /b" + else: + script_ext = ".sh" + build_script_contents = "# Autogenerated by Migen\nset -e\n\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, + basecfg=basecfg, + freq_constraint=freq_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) + return build_script_file + + +def _run_script(script): + if sys.platform in ("win32", "cygwin"): + shell = ["cmd", "/c"] + else: + shell = ["bash"] + + if subprocess.call(shell + [script]) != 0: + raise OSError("Subprocess failed") + + def yosys_import_sources(platform): includes = "" reads = [] @@ -69,7 +106,7 @@ def yosys_import_sources(platform): return "\n".join(reads) -class LatticePrjTrellisToolchain: +class LatticeTrellisToolchain: attr_translate = { # FIXME: document "keep": ("keep", "true"), @@ -83,7 +120,22 @@ class LatticePrjTrellisToolchain: "no_shreg_extract": None } - special_overrides = common.lattice_ecpx_prjtrellis_special_overrides + 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 -nomux -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 --basecfg {basecfg} --{architecture} --freq {freq_constraint}", + "ecppack {build_name}.config {build_name}.bit" + ] + + self.freq_constraints = dict() def build(self, platform, fragment, build_dir="build", build_name="top", toolchain_path=None, run=True): @@ -105,16 +157,14 @@ class LatticePrjTrellisToolchain: platform.add_source(top_file) # generate constraints - tools.write_to_file(build_name + ".lpf", _build_lpf(named_sc, named_pc)) + tools.write_to_file(build_name + ".lpf", + _build_lpf(named_sc, named_pc)) # generate yosys script yosys_script_file = build_name + ".ys" - yosys_script_contents = [ - yosys_import_sources(platform), - "synth_ecp5 -nomux -json {build_name}.json -top {build_name}" - ] - yosys_script_contents = "\n".join(yosys_script_contents) - yosys_script_contents = yosys_script_contents.format(build_name=build_name) + yosys_script_contents = "\n".join(_.format(build_name=build_name, + read_files=yosys_import_sources(platform)) + for _ in self.yosys_template) tools.write_to_file(yosys_script_file, yosys_script_contents) # transform platform.device to nextpnr's architecture / basecfg @@ -122,30 +172,27 @@ class LatticePrjTrellisToolchain: architecture = nextpnr_ecp5_architectures[(family + "-" + size).lower()] basecfg = "empty_" + (family + "-" + size).lower() + ".config" basecfg = os.path.join(toolchain_path, "misc", "basecfgs", basecfg) + freq_constraint = str(max(self.freq_constraints.values(), + default=0.0)) - # generate build script - build_script_file = "build_" + build_name + ".sh" - build_script_contents = [ - "yosys -q -l {build_name}.rpt {build_name}.ys", - "nextpnr-ecp5 --json {build_name}.json --lpf {build_name}.lpf --textcfg {build_name}.config --basecfg {basecfg} --{architecture}", - "ecppack {build_name}.config {build_name}.bit" - - ] - build_script_contents = "\n".join(build_script_contents) - build_script_contents = build_script_contents.format( - build_name=build_name, - architecture=architecture, - basecfg=basecfg) - tools.write_to_file(build_script_file, build_script_contents) + script = _build_script(False, self.build_template, build_name, + architecture, basecfg, freq_constraint) # run scripts if run: - if subprocess.call(["bash", build_script_file]) != 0: - raise OSError("Subprocess failed") + _run_script(script) os.chdir(cwd) return top_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): - print("TODO: add_period_constraint") + 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/xilinx/ise.py b/litex/build/xilinx/ise.py index ef561f4b..1b5a6644 100644 --- a/litex/build/xilinx/ise.py +++ b/litex/build/xilinx/ise.py @@ -88,13 +88,13 @@ def _run_ise(build_name, ise_path, source, mode, ngdbuild_opt, source_cmd = "call " script_ext = ".bat" shell = ["cmd", "/c"] - build_script_contents = "@echo off\nrem Autogenerated by LiteX\n" + build_script_contents = "@echo off\nrem Autogenerated by Migen\n" fail_stmt = " || exit /b" else: source_cmd = "source " script_ext = ".sh" shell = ["bash"] - build_script_contents = "# Autogenerated by LiteX\nset -e\n" + build_script_contents = "# Autogenerated by Migen\nset -e\n" fail_stmt = "" if source: settings = common.settings(ise_path, ver, "ISE_DS") diff --git a/litex/build/xilinx/vivado.py b/litex/build/xilinx/vivado.py index a1f94d9f..4859c149 100644 --- a/litex/build/xilinx/vivado.py +++ b/litex/build/xilinx/vivado.py @@ -56,13 +56,13 @@ def _build_xdc(named_sc, named_pc): def _run_vivado(build_name, vivado_path, source, ver=None): if sys.platform == "win32" or sys.platform == "cygwin": - build_script_contents = "REM Autogenerated by LiteX\n" + build_script_contents = "REM Autogenerated by Migen\n" build_script_contents += "vivado -mode batch -source " + build_name + ".tcl\n" build_script_file = "build_" + build_name + ".bat" tools.write_to_file(build_script_file, build_script_contents) command = build_script_file else: - build_script_contents = "# Autogenerated by LiteX\nset -e\n" + build_script_contents = "# Autogenerated by Migen\nset -e\n" # For backwards compatibility with ISE paths, also # look for a version in a subdirectory named "Vivado" -- 2.30.2