From 6541a6c93b6daec3b47726ff117ddd08640a78d9 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sun, 26 Jun 2022 21:40:56 +0200 Subject: [PATCH] build: gowin lattice/diamond lattice/oxide lattice/radiant microsemi quicklogic move to GenericToolchain --- litex/build/gowin/gowin.py | 246 +++++++--------- litex/build/lattice/diamond.py | 411 +++++++++++++------------- litex/build/lattice/oxide.py | 229 ++++++--------- litex/build/lattice/radiant.py | 418 ++++++++++++-------------- litex/build/microsemi/libero_soc.py | 440 +++++++++++++--------------- litex/build/quicklogic/f4pga.py | 152 ++++------ 6 files changed, 849 insertions(+), 1047 deletions(-) diff --git a/litex/build/gowin/gowin.py b/litex/build/gowin/gowin.py index f88fc4120..0f8c38f5a 100644 --- a/litex/build/gowin/gowin.py +++ b/litex/build/gowin/gowin.py @@ -13,85 +13,25 @@ from shutil import which, copyfile from migen.fhdl.structure import _Fragment +from litex.build.generic_toolchain import GenericToolchain from litex.build.generic_platform import * from litex.build import tools -# Constraints (.cst and .tcl) ---------------------------------------------------------------------- - -def _build_cst(named_sc, named_pc): - cst = [] - - flat_sc = [] - for name, pins, other, resource in named_sc: - if len(pins) > 1: - for i, p in enumerate(pins): - flat_sc.append((f"{name}[{i}]", p, other)) - else: - flat_sc.append((name, pins[0], other)) - - for name, pin, other in flat_sc: - if pin != "X": - cst.append(f"IO_LOC \"{name}\" {pin};") - - for c in other: - if isinstance(c, IOStandard): - cst.append(f"IO_PORT \"{name}\" IO_TYPE={c.name};") - elif isinstance(c, Misc): - cst.append(f"IO_PORT \"{name}\" {c.misc};") - - if named_pc: - cst.extend(named_pc) - - with open("top.cst", "w") as f: - f.write("\n".join(cst)) - -def _build_sdc(clocks, vns): - sdc = [] - for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): - sdc.append(f"create_clock -name {vns.get_name(clk)} -period {str(period)} [get_ports {{{vns.get_name(clk)}}}]") - with open("top.sdc", "w") as f: - f.write("\n".join(sdc)) - -# Script ------------------------------------------------------------------------------------------- - -def _build_tcl(name, partnumber, files, options): - tcl = [] - - # Set Device. - tcl.append(f"set_device -name {name} {partnumber}") - - # Add IOs Constraints. - tcl.append("add_file top.cst") - - # Add Timings Constraints. - tcl.append("add_file top.sdc") - - # Add Sources. - for f, typ, lib in files: - # Support windows/powershell - if sys.platform == "win32": - f = f.replace("\\", "\\\\") - tcl.append(f"add_file {f}") - - # Set Options. - for opt, val in options.items(): - tcl.append(f"set_option -{opt} {val}") - - # Run. - tcl.append("run all") - - # Generate .tcl. - with open("run.tcl", "w") as f: - f.write("\n".join(tcl)) # GowinToolchain ----------------------------------------------------------------------------------- -class GowinToolchain: +class GowinToolchain(GenericToolchain): attr_translate = {} def __init__(self): + super().__init__() self.options = {} - self.clocks = dict() + + def finalize(self): + if self.platform.verilog_include_paths: + self.options["include_path"] = "{" + ";".join(self.platform.verilog_include_paths) + "}" + + self.apply_hyperram_integration_hack(self._build_name + ".v") def apply_hyperram_integration_hack(self, v_file): # FIXME: Gowin EDA expects a very specific HypeRAM integration pattern, modify generated verilog to match it. @@ -116,85 +56,101 @@ class GowinToolchain: tools.replace_in_file(v_file, "[1:0] IO_psram_rwds,", "[1:0] IO_psram_rwds, /* synthesis syn_tristate = 1 */") tools.replace_in_file(v_file, "[15:0] IO_psram_dq,", "[15:0] IO_psram_dq, /* synthesis syn_tristate = 1 */") - def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, - **kwargs): + def build(self, platform, fragment, **kwargs): + return self._build(platform, fragment, **kwargs) - # Create build directory. - cwd = os.getcwd() - os.makedirs(build_dir, exist_ok=True) - os.chdir(build_dir) + # Constraints (.cst ) -------------------------------------------------------------------------- - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + def build_io_constraints(self): + cst = [] - # 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) - self.apply_hyperram_integration_hack(v_file) + flat_sc = [] + for name, pins, other, resource in self.named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + flat_sc.append((f"{name}[{i}]", p, other)) + else: + flat_sc.append((name, pins[0], other)) - if platform.verilog_include_paths: - self.options["include_path"] = "{" + ";".join(platform.verilog_include_paths) + "}" + for name, pin, other in flat_sc: + if pin != "X": + cst.append(f"IO_LOC \"{name}\" {pin};") - # Generate constraints file. - # IOs (.cst). - _build_cst( - named_sc = named_sc, - named_pc = named_pc + for c in other: + if isinstance(c, IOStandard): + cst.append(f"IO_PORT \"{name}\" IO_TYPE={c.name};") + elif isinstance(c, Misc): + cst.append(f"IO_PORT \"{name}\" {c.misc};") + + if self.named_pc: + cst.extend(self.named_pc) + + tools.write_to_file("top.cst", "\n".join(cst)) + return ("top.cst", "CST") + + # Timing Constraints (.sdc ) ------------------------------------------------------------------- + + def build_timing_constraints(self, vns): + sdc = [] + for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): + sdc.append(f"create_clock -name {vns.get_name(clk)} -period {str(period)} [get_ports {{{vns.get_name(clk)}}}]") + tools.write_to_file("top.sdc", "\n".join(sdc)) + return ("top.sdc", "SDC") + + # Project (tcl) -------------------------------------------------------------------------------- + + def build_project(self): + tcl = [] + + # Set Device. + tcl.append(f"set_device -name {self.platform.devicename} {self.platform.device}") + + # Add IOs Constraints. + tcl.append("add_file top.cst") + + # Add Timings Constraints. + tcl.append("add_file top.sdc") + + # Add Sources. + for f, typ, lib in self.platform.sources: + # Support windows/powershell + if sys.platform == "win32": + f = f.replace("\\", "\\\\") + tcl.append(f"add_file {f}") + + # Set Options. + for opt, val in self.options.items(): + tcl.append(f"set_option -{opt} {val}") + + # Run. + tcl.append("run all") + + # Generate .tcl. + tools.write_to_file("run.tcl", "\n".join(tcl)) + + # Script --------------------------------------------------------------------------------------- + + def build_script(self): + return "" # gw_sh use + + def run_script(self, script): + # Support Powershell/WSL platform + # Some python distros for windows (e.g, oss-cad-suite) + # which does not have 'os.uname' support, we should check 'sys.platform' firstly. + gw_sh = "gw_sh" + if sys.platform.find("linux") >= 0: + if os.uname().release.find("WSL") > 0: + gw_sh += ".exe" + if which(gw_sh) is None: + msg = "Unable to find Gowin toolchain, please:\n" + msg += "- Add Gowin toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call([gw_sh, "run.tcl"]) != 0: + raise OSError("Error occured during Gowin's script execution.") + + # Copy Bitstream to from impl to gateware directory. + copyfile( + os.path.join("impl", "pnr", "project.fs"), + os.path.join(build_name + ".fs") ) - - # Timings (.sdc) - _build_sdc( - clocks = self.clocks, - vns = v_output.ns - ) - - # Generate build script (.tcl) - script = _build_tcl( - name = platform.devicename, - partnumber = platform.device, - files = platform.sources, - options = self.options) - - # Run - if run: - # Support Powershell/WSL platform - # Some python distros for windows (e.g, oss-cad-suite) - # which does not have 'os.uname' support, we should check 'sys.platform' firstly. - gw_sh = "gw_sh" - if sys.platform.find("linux") >= 0: - if os.uname().release.find("WSL") > 0: - gw_sh += ".exe" - if which(gw_sh) is None: - msg = "Unable to find Gowin toolchain, please:\n" - msg += "- Add Gowin toolchain to your $PATH." - raise OSError(msg) - - if subprocess.call([gw_sh, "run.tcl"]) != 0: - raise OSError("Error occured during Gowin's script execution.") - - # Copy Bitstream to from impl to gateware directory. - copyfile( - os.path.join("impl", "pnr", "project.fs"), - os.path.join(build_name + ".fs") - ) - - os.chdir(cwd) - - return v_output.ns - - def add_period_constraint(self, platform, clk, period): - clk.attr.add("keep") - period = math.floor(period*1e3)/1e3 # round to lowest picosecond - if clk in self.clocks: - if period != self.clocks[clk]: - raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns" - .format(self.clocks[clk], period)) - self.clocks[clk] = period diff --git a/litex/build/lattice/diamond.py b/litex/build/lattice/diamond.py index 9e701bc48..649ce72b2 100644 --- a/litex/build/lattice/diamond.py +++ b/litex/build/lattice/diamond.py @@ -19,177 +19,14 @@ from migen.fhdl.structure import _Fragment from litex.gen.fhdl.verilog import DummyAttrTranslate from litex.build.generic_platform import * +from litex.build.generic_toolchain import GenericToolchain from litex.build import tools from litex.build.lattice import common -# Helpers ------------------------------------------------------------------------------------------ - -def _produces_jedec(device): - return device.startswith("LCMX") - -# Constraints (.lpf) ------------------------------------------------------------------------------- - -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 ("IOBUF PORT ", " " + c.misc) - - -def _format_lpf(signame, pin, others, resname): - fmt_c = [_format_constraint(c) for c in ([Pins(pin)] + others)] - lpf = [] - for pre, suf in fmt_c: - lpf.append(pre + "\"" + signame + "\"" + suf + ";") - return "\n".join(lpf) - - -def _build_lpf(named_sc, named_pc, clocks, vns, 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): - lpf.append(_format_lpf(sig + "[" + str(i) + "]", p, others, resname)) - else: - lpf.append(_format_lpf(sig, pins[0], others, resname)) - if named_pc: - lpf.append("\n".join(named_pc)) - - # Note: .lpf is only used post-synthesis, Synplify constraints clocks by default to 200MHz. - for clk, period in clocks.items(): - clk_name = vns.get_name(clk) - lpf.append("FREQUENCY {} \"{}\" {} MHz;".format( - "PORT" if clk_name in [name for name, _, _, _ in named_sc] else "NET", - clk_name, - str(1e3/period))) - - tools.write_to_file(build_name + ".lpf", "\n".join(lpf)) - -# Project (.tcl) ----------------------------------------------------------------------------------- - -def _build_tcl(device, sources, vincpaths, build_name): - tcl = [] - # Create project - tcl.append(" ".join([ - "prj_project", - "new -name \"{}\"".format(build_name), - "-impl \"impl\"", - "-dev {}".format(device), - "-synthesis \"synplify\"" - ])) - - def tcl_path(path): return path.replace("\\", "/") - - # Add include paths - vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths)) - tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}") - - # Add sources - for filename, language, library, *copy in sources: - tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library)) - - # Set top level - tcl.append("prj_impl option top \"{}\"".format(build_name)) - - # Save project - tcl.append("prj_project save") - - # Build flow - 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") - - # Close project - tcl.append("prj_project close") - - tools.write_to_file(build_name + ".tcl", "\n".join(tcl)) - -# Script ------------------------------------------------------------------------------------------- - -def _build_script(build_name, device): - on_windows = sys.platform in ("win32", "cygwin") - if on_windows: - script_ext = ".bat" - script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" - copy_stmt = "copy" - fail_stmt = " || exit /b" - else: - script_ext = ".sh" - script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" - copy_stmt = "cp" - fail_stmt = "" - - script_contents += "{tool} {tcl_script}{fail_stmt}\n".format( - tool = "pnmainc" if on_windows else "diamondc", - tcl_script = build_name + ".tcl", - fail_stmt = fail_stmt) - for ext in (".bit", ".jed"): - if ext == ".jed" and not _produces_jedec(device): - continue - 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, script_contents, force_unix=False) - return build_script_file - -def _run_script(script): - on_windows = sys.platform in ("win32", "cygwin") - if on_windows: - shell = ["cmd", "/c"] - else: - shell = ["bash"] - - if which("pnmainc" if on_windows else "diamondc") is None: - msg = "Unable to find Diamond toolchain, please:\n" - msg += "- Add Diamond toolchain to your $PATH.\n" - raise OSError(msg) - - if subprocess.call(shell + [script]) != 0: - raise OSError("Error occured during Diamond's script execution.") - -def _check_timing(build_name): - lines = open("impl/{}_impl.par".format(build_name), "r").readlines() - runs = [None, None] - for i in range(len(lines)-1): - if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "): - runs[0] = i + 2 - if lines[i].startswith("* : Design saved.") and runs[0] is not None: - runs[1] = i - break - assert all(map(lambda x: x is not None, runs)) - - p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)") - for l in lines[runs[0]:runs[1]]: - m = p.match(l) - if m is None: continue - limit = 1e-8 - setup = m.group(2) - hold = m.group(4) - # If there were no freq constraints in lpf, ratings will be dashed. - # results will likely be terribly unreliable, so bail - assert not setup == hold == "-", "No timing constraints were provided" - setup, hold = map(float, (setup, hold)) - if setup > limit and hold > limit: - # At least one run met timing - # XXX is this necessarily the run from which outputs will be used? - return - raise Exception("Failed to meet timing") # LatticeDiamondToolchain -------------------------------------------------------------------------- -class LatticeDiamondToolchain: +class LatticeDiamondToolchain(GenericToolchain): attr_translate = { "keep": ("syn_keep", "true"), "no_retiming": ("syn_no_retiming", "true"), @@ -198,63 +35,217 @@ class LatticeDiamondToolchain: special_overrides = common.lattice_ecp5_special_overrides def __init__(self): - self.clocks = {} - self.false_paths = set() # FIXME: use it + super().__init__() def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, timingstrict = False, **kwargs): - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) + self._timinstrict = timingstrict - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + return self._build(platform, fragment, **kwargs) - # 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) + ## Create build directory + #os.makedirs(build_dir, exist_ok=True) + #cwd = os.getcwd() + #os.chdir(build_dir) - # Generate design constraints file (.lpf) - _build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name) + ## Finalize design + #if not isinstance(fragment, _Fragment): + # fragment = fragment.get_fragment() + #platform.finalize(fragment) - # Generate design script file (.tcl) - _build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name) + ## 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) - # Generate build script - script = _build_script(build_name, platform.device) + ## Generate design constraints file (.lpf) + #_build_lpf(named_sc, named_pc, self.clocks, v_output.ns, build_name) - # Run - if run: - _run_script(script) - if timingstrict: - _check_timing(build_name) + ## Generate design script file (.tcl) + #_build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name) - os.chdir(cwd) + ## Generate build script + #script = _build_script(build_name, platform.device) - return v_output.ns + ## Run + #if run: + # _run_script(script) + # if timingstrict: + # _check_timing(build_name) - def add_period_constraint(self, platform, clk, period): - clk.attr.add("keep") - period = math.floor(period*1e3)/1e3 # round to lowest picosecond - if clk in self.clocks: - if period != self.clocks[clk]: - raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns" - .format(self.clocks[clk], period)) - self.clocks[clk] = period + #os.chdir(cwd) - def add_false_path_constraint(self, platform, from_, to): - from_.attr.add("keep") - to.attr.add("keep") - if (to, from_) not in self.false_paths: - self.false_paths.add((from_, to)) + #return v_output.ns + + # Helpers -------------------------------------------------------------------------------------- + + @classmethod + def _produces_jedec(cls, device): + return device.startswith("LCMX") + + # Constraints (.lpf) --------------------------------------------------------------------------- + + @classmethod + def _format_constraint(cls, 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 ("IOBUF PORT ", " " + c.misc) + + @classmethod + def _format_lpf(cls, signame, pin, others, resname): + fmt_c = [cls._format_constraint(c) for c in ([Pins(pin)] + others)] + lpf = [] + for pre, suf in fmt_c: + lpf.append(pre + "\"" + signame + "\"" + suf + ";") + return "\n".join(lpf) + + def build_io_constraints(self): + lpf = [] + lpf.append("BLOCK RESETPATHS;") + lpf.append("BLOCK ASYNCPATHS;") + for sig, pins, others, resname in self.named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + lpf.append(self._format_lpf(sig + "[" + str(i) + "]", p, others, resname)) + else: + lpf.append(self._format_lpf(sig, pins[0], others, resname)) + if self.named_pc: + lpf.append("\n".join(self.named_pc)) + + # Note: .lpf is only used post-synthesis, Synplify constraints clocks by default to 200MHz. + for clk, period in self.clocks.items(): + clk_name = self._vns.get_name(clk) + lpf.append("FREQUENCY {} \"{}\" {} MHz;".format( + "PORT" if clk_name in [name for name, _, _, _ in self.named_sc] else "NET", + clk_name, + str(1e3/period))) + + tools.write_to_file(self._build_name + ".lpf", "\n".join(lpf)) + + # Project (.tcl) ------------------------------------------------------------------------------- + + def build_project(self): + tcl = [] + # Create project + tcl.append(" ".join([ + "prj_project", + "new -name \"{}\"".format(self._build_name), + "-impl \"impl\"", + "-dev {}".format(self.platform.device), + "-synthesis \"synplify\"" + ])) + + def tcl_path(path): return path.replace("\\", "/") + + # Add include paths + vincpath = ";".join(map(lambda x: tcl_path(x), self.platform.verilog_include_paths)) + tcl.append("prj_impl option {include path} {\"" + vincpath + "\"}") + + # Add sources + for filename, language, library, *copy in self.platform.sources: + tcl.append("prj_src add \"{}\" -work {}".format(tcl_path(filename), library)) + + # Set top level + tcl.append("prj_impl option top \"{}\"".format(self._build_name)) + + # Save project + tcl.append("prj_project save") + + # Build flow + 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 self._produces_jedec(self.platform.device): + tcl.append("prj_run Export -impl impl -task Jedecgen") + + # Close project + tcl.append("prj_project close") + + tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl)) + + # Script --------------------------------------------------------------------------------------- + + def build_script(self): + on_windows = sys.platform in ("win32", "cygwin") + if on_windows: + script_ext = ".bat" + script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" + copy_stmt = "copy" + fail_stmt = " || exit /b" + else: + script_ext = ".sh" + script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" + copy_stmt = "cp" + fail_stmt = "" + + script_contents += "{tool} {tcl_script}{fail_stmt}\n".format( + tool = "pnmainc" if on_windows else "diamondc", + tcl_script = self._build_name + ".tcl", + fail_stmt = fail_stmt) + for ext in (".bit", ".jed"): + if ext == ".jed" and not self._produces_jedec(self.platform.device): + continue + 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", self._build_name + "_impl" + ext), + migen_product = self._build_name + ext) + + build_script_file = "build_" + self._build_name + script_ext + tools.write_to_file(build_script_file, script_contents, force_unix=False) + return build_script_file + + def run_script(self, script): + on_windows = sys.platform in ("win32", "cygwin") + if on_windows: + shell = ["cmd", "/c"] + else: + shell = ["bash"] + + if which("pnmainc" if on_windows else "diamondc") is None: + msg = "Unable to find Diamond toolchain, please:\n" + msg += "- Add Diamond toolchain to your $PATH.\n" + raise OSError(msg) + + if subprocess.call(shell + [script]) != 0: + raise OSError("Error occured during Diamond's script execution.") + + if self.timingstrict: + self._check_timing() + + def _check_timing(self): + lines = open("impl/{}_impl.par".format(self._build_name), "r").readlines() + runs = [None, None] + for i in range(len(lines)-1): + if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "): + runs[0] = i + 2 + if lines[i].startswith("* : Design saved.") and runs[0] is not None: + runs[1] = i + break + assert all(map(lambda x: x is not None, runs)) + + p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)") + for l in lines[runs[0]:runs[1]]: + m = p.match(l) + if m is None: continue + limit = 1e-8 + setup = m.group(2) + hold = m.group(4) + # If there were no freq constraints in lpf, ratings will be dashed. + # results will likely be terribly unreliable, so bail + assert not setup == hold == "-", "No timing constraints were provided" + setup, hold = map(float, (setup, hold)) + if setup > limit and hold > limit: + # At least one run met timing + # XXX is this necessarily the run from which outputs will be used? + return + raise Exception("Failed to meet timing") diff --git a/litex/build/lattice/oxide.py b/litex/build/lattice/oxide.py index 07f792c5a..cf325ff41 100644 --- a/litex/build/lattice/oxide.py +++ b/litex/build/lattice/oxide.py @@ -14,98 +14,17 @@ from shutil import which from migen.fhdl.structure import _Fragment from litex.build.generic_platform import * +from litex.build.generic_toolchain import GenericToolchain from litex.build import tools from litex.build.lattice import common from litex.build.lattice.radiant import _format_constraint, _format_ldc, _build_pdc import math -# Yosys/Nextpnr Helpers/Templates ------------------------------------------------------------------ - -_yosys_template = [ - "verilog_defaults -push", - "verilog_defaults -add -defer", - "{read_files}", - "verilog_defaults -pop", - "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0", - "synth_nexus -flatten {nwl} {abc} -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, *copy in platform.sources: - # yosys has no such function read_systemverilog - if language == "systemverilog": - language = "verilog -sv" - reads.append("read_{}{} {}".format( - language, includes, filename)) - return "\n".join(reads) - -def _build_yosys(template, platform, nowidelut, abc9, build_name): - ys = [] - for l in template: - ys.append(l.format( - build_name = build_name, - nwl = "-nowidelut" if nowidelut else "", - abc = "-abc9" if abc9 else "", - read_files = _yosys_import_sources(platform) - )) - tools.write_to_file(build_name + ".ys", "\n".join(ys)) - -# Script ------------------------------------------------------------------------------------------- - -_build_template = [ - "yosys -l {build_name}.rpt {build_name}.ys", - "nextpnr-nexus --json {build_name}.json --pdc {build_name}.pdc --fasm {build_name}.fasm \ - --device {device} {timefailarg} {ignoreloops} --seed {seed}", - "prjoxide pack {build_name}.fasm {build_name}.bit" -] - -def _build_script(source, build_template, build_name, device, timingstrict, ignoreloops, seed): - if sys.platform in ("win32", "cygwin"): - script_ext = ".bat" - script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" - fail_stmt = " || exit /b" - else: - script_ext = ".sh" - 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. - script_contents += s_fail.format( - build_name = build_name, - device = device, - timefailarg = "--timing-allow-fail" if not timingstrict else "", - ignoreloops = "--ignore-loops" if ignoreloops else "", - fail_stmt = fail_stmt, - seed = seed, - ) - 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"): - shell = ["cmd", "/c"] - else: - shell = ["bash"] - - if which("yosys") is None or which("nextpnr-nexus") is None: - msg = "Unable to find Yosys/Nextpnr toolchain, please:\n" - msg += "- Add Yosys/Nextpnr toolchain to your $PATH." - raise OSError(msg) - - if subprocess.call(shell + [script]) != 0: - raise OSError("Error occured during Yosys/Nextpnr's script execution.") # LatticeOxideToolchain -------------------------------------------------------------------------- -class LatticeOxideToolchain: +class LatticeOxideToolchain(GenericToolchain): attr_translate = { "keep": ("keep", "true"), "syn_useioff": ("syn_useioff", 1), @@ -114,15 +33,11 @@ class LatticeOxideToolchain: special_overrides = common.lattice_NX_special_overrides_for_oxide def __init__(self): - self.yosys_template = _yosys_template - self.build_template = _build_template - self.clocks = {} - self.false_paths = set() # FIXME: use it + super().__init__() + self.yosys_template = self._yosys_template + self.build_template = self._build_template def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, nowidelut = False, abc9 = False, timingstrict = False, @@ -131,62 +46,104 @@ class LatticeOxideToolchain: es_device = False, **kwargs): - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) + self._nowidelut = nowidelut + self._abc9 = abc9 + self._timingstrict = timingstrict + self._ignoreloops = ignoreloops + self._seed = seed + self._es_device = es_device - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + return self._build(platform, fragment, **kwargs) - # 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" - v_output.write(top_file) - platform.add_source(top_file) + # Constraints (.ldc) --------------------------------------------------------------------------- - # Generate design constraints file (.pdc) - _build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name) + def build_io_constraints(self): + _build_pdc(self.named_sc, self.named_pc, self.clocks, self._vns, self._build_name) + return (self._build_name + ".pdc", "PDC") - # Generate Yosys script - _build_yosys(self.yosys_template, platform, nowidelut, abc9, build_name) + # Yosys/Nextpnr Helpers/Templates -------------------------------------------------------------- - # N.B. Radiant does not allow a choice between ES1/production, this is determined - # solely by the installed Radiant version. nextpnr/oxide supports both, so we - # must choose what we are dealing with - device = platform.device - if es_device: - device += "ES" + _yosys_template = [ + "verilog_defaults -push", + "verilog_defaults -add -defer", + "{read_files}", + "verilog_defaults -pop", + "attrmap -tocase keep -imap keep=\"true\" keep=1 -imap keep=\"false\" keep=0 -remove keep=0", + "synth_nexus -flatten {nwl} {abc} -json {build_name}.json -top {build_name}", + ] - # Generate build script - script = _build_script(False, self.build_template, build_name, device, - timingstrict, ignoreloops, seed) - # Run - if run: - _run_script(script) + def _yosys_import_sources(self): + includes = "" + reads = [] + for path in self.platform.verilog_include_paths: + includes += " -I" + path + for filename, language, library, *copy in self.platform.sources: + # yosys has no such function read_systemverilog + if language == "systemverilog": + language = "verilog -sv" + reads.append("read_{}{} {}".format( + language, includes, filename)) + return "\n".join(reads) - os.chdir(cwd) + def build_project(self): + ys = [] + for l in self.yosys_template: + ys.append(l.format( + build_name = self._build_name, + nwl = "-nowidelut" if self._nowidelut else "", + abc = "-abc9" if self._abc9 else "", + read_files = self._yosys_import_sources() + )) + tools.write_to_file(self._build_name + ".ys", "\n".join(ys)) - return v_output.ns + # Script --------------------------------------------------------------------------------------- - # N.B. these are currently ignored, but will be supported very soon - def add_period_constraint(self, platform, clk, period): - clk.attr.add("keep") - period = math.floor(period*1e3)/1e3 # round to lowest picosecond - if clk in self.clocks: - if period != self.clocks[clk]: - raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns" - .format(self.clocks[clk], period)) - self.clocks[clk] = period + _build_template = [ + "yosys -l {build_name}.rpt {build_name}.ys", + "nextpnr-nexus --json {build_name}.json --pdc {build_name}.pdc --fasm {build_name}.fasm \ + --device {device} {timefailarg} {ignoreloops} --seed {seed}", + "prjoxide pack {build_name}.fasm {build_name}.bit" + ] + + def build_script(self): + if sys.platform in ("win32", "cygwin"): + script_ext = ".bat" + script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" + fail_stmt = " || exit /b" + else: + script_ext = ".sh" + script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" + fail_stmt = "" + + for s in self.build_template: + s_fail = s + "{fail_stmt}\n" # Required so Windows scripts fail early. + script_contents += s_fail.format( + build_name = self._build_name, + device = "ES" if self._es_device else self.platform.device, + timefailarg = "--timing-allow-fail" if not self._timingstrict else "", + ignoreloops = "--ignore-loops" if self._ignoreloops else "", + fail_stmt = fail_stmt, + seed = self._seed, + ) + script_file = "build_" + self._build_name + script_ext + tools.write_to_file(script_file, script_contents, force_unix=False) + + return script_file + + def run_script(self, script): + if sys.platform in ("win32", "cygwin"): + shell = ["cmd", "/c"] + else: + shell = ["bash"] + + if which("yosys") is None or which("nextpnr-nexus") is None: + msg = "Unable to find Yosys/Nextpnr toolchain, please:\n" + msg += "- Add Yosys/Nextpnr toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call(shell + [script]) != 0: + raise OSError("Error occured during Yosys/Nextpnr's script execution.") - def add_false_path_constraint(self, platform, from_, to): - from_.attr.add("keep") - to.attr.add("keep") - if (to, from_) not in self.false_paths: - self.false_paths.add((from_, to)) def oxide_args(parser): toolchain_group = parser.add_argument_group(title="Toolchain options") diff --git a/litex/build/lattice/radiant.py b/litex/build/lattice/radiant.py index 2b4f8eb22..4085ef607 100644 --- a/litex/build/lattice/radiant.py +++ b/litex/build/lattice/radiant.py @@ -22,59 +22,9 @@ from litex.gen.fhdl.verilog import DummyAttrTranslate from litex.build.generic_platform import * from litex.build import tools from litex.build.lattice import common +from litex.build.generic_toolchain import GenericToolchain -# Mixed Radiant+Yosys support - -def _run_yosys(device, sources, vincpaths, build_name): - ys_contents = "" - incflags = "" - for path in vincpaths: - incflags += " -I" + path - for filename, language, library, *copy in sources: - assert language != "vhdl" - ys_contents += "read_{}{} {}\n".format(language, incflags, filename) - - ys_contents += """\ -hierarchy -top {build_name} - -# Map keep to keep=1 for yosys -log -log XX. Converting (* keep = "xxxx" *) attribute for Yosys -log -attrmap -tocase keep -imap keep="true" keep=1 -imap keep="false" keep=0 -remove keep=0 -select -list a:keep=1 - -# Add keep=1 for yosys to objects which have dont_touch="true" attribute. -log -log XX. Converting (* dont_touch = "true" *) attribute for Yosys -log -select -list a:dont_touch=true -setattr -set keep 1 a:dont_touch=true - -# Convert (* async_reg = "true" *) to async registers for Yosys. -# (* async_reg = "true", dont_touch = "true" *) reg xilinxmultiregimpl0_regs1 = 1'd0; -log -log XX. Converting (* async_reg = "true" *) attribute to async registers for Yosys -log -select -list a:async_reg=true -setattr -set keep 1 a:async_reg=true - -synth_nexus -top {build_name} -vm {build_name}_yosys.vm -""".format(build_name=build_name) - - ys_name = build_name + ".ys" - tools.write_to_file(ys_name, ys_contents) - - if which("yosys") is None: - msg = "Unable to find Yosys toolchain, please:\n" - msg += "- Add Yosys toolchain to your $PATH." - raise OSError(msg) - - if subprocess.call(["yosys", ys_name]) != 0: - raise OSError("Subprocess failed") - -# Constraints (.ldc) ------------------------------------------------------------------------------- - +# Required by oxide too (FIXME) def _format_constraint(c): if isinstance(c, Pins): return ("ldc_set_location -site {" + c.identifiers[0] + "} [get_ports ","]") @@ -116,136 +66,10 @@ def _build_pdc(named_sc, named_pc, clocks, vns, build_name): tools.write_to_file(build_name + ".pdc", "\n".join(pdc)) -# Project (.tcl) ----------------------------------------------------------------------------------- - -def _build_tcl(device, sources, vincpaths, build_name, pdc_file, synth_mode): - tcl = [] - # Create project - syn = "lse" if synth_mode == "lse" else "synplify" - tcl.append(" ".join([ - "prj_create", - "-name \"{}\"".format(build_name), - "-impl \"impl\"", - "-dev {}".format(device), - "-synthesis \"" + syn + "\"" - ])) - - def tcl_path(path): return path.replace("\\", "/") - - # Add include paths - vincpath = ";".join(map(lambda x: tcl_path(x), vincpaths)) - tcl.append("prj_set_impl_opt {include path} {\"" + vincpath + "\"}") - - # Add sources - if synth_mode == "yosys": - # NOTE: it is seemingly impossible to skip synthesis using the Tcl flow - # so we give Synplify the structural netlist from Yosys which it won't actually touch - # The other option is to call the low level Radiant commands starting from 'map' - # with the structural netlist from Yosys, but this would be harder to do in a cross - # platform way. - tcl.append("prj_add_source \"{}_yosys.vm\" -work work".format(build_name)) - library = "work" - else: - for filename, language, library, *copy in sources: - tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(filename), library)) - - tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(pdc_file), library)) - - # Set top level - tcl.append("prj_set_impl_opt top \"{}\"".format(build_name)) - - # Save project - tcl.append("prj_save") - - # Build flow - tcl.append("prj_run Synthesis -impl impl -forceOne") - tcl.append("prj_run Map -impl impl") - tcl.append("prj_run PAR -impl impl") - tcl.append("prj_run Export -impl impl -task Bitgen") - - # Close project - tcl.append("prj_close") - - tools.write_to_file(build_name + ".tcl", "\n".join(tcl)) - -# Script ------------------------------------------------------------------------------------------- - -def _build_script(build_name, device): - if sys.platform in ("win32", "cygwin"): - tool = "pnmainc" - script_ext = ".bat" - script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" - copy_stmt = "copy" - fail_stmt = " || exit /b" - else: - tool = "radiantc" - script_ext = ".sh" - script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" - copy_stmt = "cp" - fail_stmt = "" - - script_contents += "{tool} {tcl_script}{fail_stmt}\n".format( - tool = tool, - tcl_script = build_name + ".tcl", - fail_stmt = fail_stmt) - - script_contents += "{copy_stmt} {radiant_product} {migen_product} {fail_stmt}\n".format( - copy_stmt = copy_stmt, - fail_stmt = fail_stmt, - radiant_product = os.path.join("impl", build_name + "_impl.bit"), - migen_product = build_name + ".bit") - - build_script_file = "build_" + build_name + script_ext - tools.write_to_file(build_script_file, script_contents, force_unix=False) - return build_script_file - -def _run_script(script): - if sys.platform in ("win32", "cygwin"): - shell = ["cmd", "/c"] - tool = "pnmainc" - else: - shell = ["bash"] - tool = "radiantc" - - if which(tool) is None: - msg = "Unable to find Radiant toolchain, please:\n" - msg += "- Add Radiant toolchain to your $PATH." - raise OSError(msg) - - if subprocess.call(shell + [script]) != 0: - raise OSError("Error occured during Radiant's script execution.") - -def _check_timing(build_name): - lines = open("impl/{}_impl.par".format(build_name), "r").readlines() - runs = [None, None] - for i in range(len(lines)-1): - if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "): - runs[0] = i + 2 - if lines[i].startswith("* : Design saved.") and runs[0] is not None: - runs[1] = i - break - assert all(map(lambda x: x is not None, runs)) - - p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)") - for l in lines[runs[0]:runs[1]]: - m = p.match(l) - if m is None: continue - limit = 1e-8 - setup = m.group(2) - hold = m.group(4) - # If there were no freq constraints in ldc, ratings will be dashed. - # results will likely be terribly unreliable, so bail - assert not setup == hold == "-", "No timing constraints were provided" - setup, hold = map(float, (setup, hold)) - if setup > limit and hold > limit: - # At least one run met timing - # XXX is this necessarily the run from which outputs will be used? - return - raise Exception("Failed to meet timing") # LatticeRadiantToolchain -------------------------------------------------------------------------- -class LatticeRadiantToolchain: +class LatticeRadiantToolchain(GenericToolchain): attr_translate = { "keep": ("syn_keep", "true"), "no_retiming": ("syn_no_retiming", "true"), @@ -254,70 +78,210 @@ class LatticeRadiantToolchain: special_overrides = common.lattice_NX_special_overrides def __init__(self): - self.clocks = {} - self.false_paths = set() # FIXME: use it + super().__init__() + + self._timingstrict = True + self._synth_mode = "radiant" def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = True, timingstrict = True, synth_mode = "radiant", **kwargs): - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) + self._timingstrict = timingstrict + self._synth_mode = synth_mode - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + return self._build(platform, fragment, **kwargs) - # 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) + # Mixed Radiant+Yosys support ------------------------------------------------------------------ - # Generate design constraints file (.pdc) - _build_pdc(named_sc, named_pc, self.clocks, v_output.ns, build_name) - pdc_file = build_dir + "\\" + build_name + ".pdc" + def _run_yosys(self): + ys_contents = "" + incflags = "" + for path in self.platform.verilog_include_paths: + incflags += " -I" + path + for filename, language, library, *copy in self.platform.sources: + assert language != "vhdl" + ys_contents += "read_{}{} {}\n".format(language, incflags, filename) - # Generate design script file (.tcl) - _build_tcl(platform.device, platform.sources, platform.verilog_include_paths, build_name, pdc_file, synth_mode) + ys_contents += """\ +hierarchy -top {build_name} - # Generate build script - script = _build_script(build_name, platform.device) +# Map keep to keep=1 for yosys +log +log XX. Converting (* keep = "xxxx" *) attribute for Yosys +log +attrmap -tocase keep -imap keep="true" keep=1 -imap keep="false" keep=0 -remove keep=0 +select -list a:keep=1 - # Run - if run: - if synth_mode == "yosys": - _run_yosys(platform.device, platform.sources, platform.verilog_include_paths, build_name) - _run_script(script) - if timingstrict: - _check_timing(build_name) +# Add keep=1 for yosys to objects which have dont_touch="true" attribute. +log +log XX. Converting (* dont_touch = "true" *) attribute for Yosys +log +select -list a:dont_touch=true +setattr -set keep 1 a:dont_touch=true - os.chdir(cwd) +# Convert (* async_reg = "true" *) to async registers for Yosys. +# (* async_reg = "true", dont_touch = "true" *) reg xilinxmultiregimpl0_regs1 = 1'd0; +log +log XX. Converting (* async_reg = "true" *) attribute to async registers for Yosys +log +select -list a:async_reg=true +setattr -set keep 1 a:async_reg=true - return v_output.ns +synth_nexus -top {build_name} -vm {build_name}_yosys.vm +""".format(build_name=self._build_name) - def add_period_constraint(self, platform, clk, period): - clk.attr.add("keep") - period = math.floor(period*1e3)/1e3 # round to lowest picosecond - if clk in self.clocks: - if period != self.clocks[clk]: - raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns" - .format(self.clocks[clk], period)) - self.clocks[clk] = period + ys_name = self._build_name + ".ys" + tools.write_to_file(ys_name, ys_contents) + + if which("yosys") is None: + msg = "Unable to find Yosys toolchain, please:\n" + msg += "- Add Yosys toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call(["yosys", ys_name]) != 0: + raise OSError("Subprocess failed") + + # Constraints (.ldc) --------------------------------------------------------------------------- + + def build_io_constraints(self): + _build_pdc(self.named_sc, self.named_pc, self.clocks, self._vns, self._build_name) + return (self._build_name + ".pdc", "PDC") + + # Project (.tcl) ------------------------------------------------------------------------------- + + def build_project(self): + pdc_file = os.path.join(self._build_dir, self._build_name + ".pdc") + tcl = [] + # Create project + syn = "lse" if self._synth_mode == "lse" else "synplify" + tcl.append(" ".join([ + "prj_create", + "-name \"{}\"".format(self._build_name), + "-impl \"impl\"", + "-dev {}".format(self.platform.device), + "-synthesis \"" + syn + "\"" + ])) + + def tcl_path(path): return path.replace("\\", "/") + + # Add include paths + vincpath = ";".join(map(lambda x: tcl_path(x), self.platform.verilog_include_paths)) + tcl.append("prj_set_impl_opt {include path} {\"" + vincpath + "\"}") + + # Add sources + if self._synth_mode == "yosys": + # NOTE: it is seemingly impossible to skip synthesis using the Tcl flow + # so we give Synplify the structural netlist from Yosys which it won't actually touch + # The other option is to call the low level Radiant commands starting from 'map' + # with the structural netlist from Yosys, but this would be harder to do in a cross + # platform way. + tcl.append("prj_add_source \"{}_yosys.vm\" -work work".format(self._build_name)) + library = "work" + else: + for filename, language, library, *copy in self.platform.sources: + tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(filename), library)) + + tcl.append("prj_add_source \"{}\" -work {}".format(tcl_path(pdc_file), library)) + + # Set top level + tcl.append("prj_set_impl_opt top \"{}\"".format(self._build_name)) + + # Save project + tcl.append("prj_save") + + # Build flow + tcl.append("prj_run Synthesis -impl impl -forceOne") + tcl.append("prj_run Map -impl impl") + tcl.append("prj_run PAR -impl impl") + tcl.append("prj_run Export -impl impl -task Bitgen") + + # Close project + tcl.append("prj_close") + + tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl)) + + # Script --------------------------------------------------------------------------------------- + + def build_script(self): + if sys.platform in ("win32", "cygwin"): + tool = "pnmainc" + script_ext = ".bat" + script_contents = "@echo off\nrem Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" + copy_stmt = "copy" + fail_stmt = " || exit /b" + else: + tool = "radiantc" + script_ext = ".sh" + script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\nset -e\n" + copy_stmt = "cp" + fail_stmt = "" + + script_contents += "{tool} {tcl_script}{fail_stmt}\n".format( + tool = tool, + tcl_script = self._build_name + ".tcl", + fail_stmt = fail_stmt) + + script_contents += "{copy_stmt} {radiant_product} {migen_product} {fail_stmt}\n".format( + copy_stmt = copy_stmt, + fail_stmt = fail_stmt, + radiant_product = os.path.join("impl", self._build_name + "_impl.bit"), + migen_product = self._build_name + ".bit") + + build_script_file = "build_" + self._build_name + script_ext + tools.write_to_file(build_script_file, script_contents, force_unix=False) + return build_script_file + + def run_script(self, script): + if synth_mode == "yosys": + self._run_yosys() + + if sys.platform in ("win32", "cygwin"): + shell = ["cmd", "/c"] + tool = "pnmainc" + else: + shell = ["bash"] + tool = "radiantc" + + if which(tool) is None: + msg = "Unable to find Radiant toolchain, please:\n" + msg += "- Add Radiant toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call(shell + [script]) != 0: + raise OSError("Error occured during Radiant's script execution.") + if timingstrict: + self._check_timing() + + def _check_timing(self): + lines = open("impl/{}_impl.par".format(self._build_name), "r").readlines() + runs = [None, None] + for i in range(len(lines)-1): + if lines[i].startswith("Level/") and lines[i+1].startswith("Cost "): + runs[0] = i + 2 + if lines[i].startswith("* : Design saved.") and runs[0] is not None: + runs[1] = i + break + assert all(map(lambda x: x is not None, runs)) + + p = re.compile(r"(^\s*\S+\s+\*?\s+[0-9]+\s+)(\S+)(\s+\S+\s+)(\S+)(\s+.*)") + for l in lines[runs[0]:runs[1]]: + m = p.match(l) + if m is None: continue + limit = 1e-8 + setup = m.group(2) + hold = m.group(4) + # If there were no freq constraints in ldc, ratings will be dashed. + # results will likely be terribly unreliable, so bail + assert not setup == hold == "-", "No timing constraints were provided" + setup, hold = map(float, (setup, hold)) + if setup > limit and hold > limit: + # At least one run met timing + # XXX is this necessarily the run from which outputs will be used? + return + raise Exception("Failed to meet timing") - def add_false_path_constraint(self, platform, from_, to): - from_.attr.add("keep") - to.attr.add("keep") - if (to, from_) not in self.false_paths: - self.false_paths.add((from_, to)) def radiant_build_args(parser): toolchain_group = parser.add_argument_group(title="Toolchain options") diff --git a/litex/build/microsemi/libero_soc.py b/litex/build/microsemi/libero_soc.py index 734e8717e..df889c31c 100644 --- a/litex/build/microsemi/libero_soc.py +++ b/litex/build/microsemi/libero_soc.py @@ -13,269 +13,233 @@ from migen.fhdl.structure import _Fragment from litex.build.generic_platform import * from litex.build import tools +from litex.build.generic_toolchain import GenericToolchain from litex.build.microsemi import common -# Helpers ------------------------------------------------------------------------------------------ - -def tcl_name(name): - return "{" + name + "}" - -# IO Constraints (.pdc) ---------------------------------------------------------------------------- - -def _format_io_constraint(c): - if isinstance(c, Pins): - return "-pin_name {} ".format(c.identifiers[0]) - elif isinstance(c, IOStandard): - return "-io_std {} ".format(c.name) - elif isinstance(c, Misc): - return "-RES_PULL {} ".format(c.misc) - else: - raise NotImplementedError - - -def _format_io_pdc(signame, pin, others): - fmt_c = [_format_io_constraint(c) for c in ([Pins(pin)] + others)] - r = "set_io " - r += "-port_name {} ".format(tcl_name(signame)) - for c in ([Pins(pin)] + others): - r += _format_io_constraint(c) - r += "-fixed true " - r += "\n" - return r - -def _build_io_pdc(named_sc, named_pc, build_name, additional_io_constraints): - pdc = "" - for sig, pins, others, resname in named_sc: - if len(pins) > 1: - for i, p in enumerate(pins): - pdc += _format_io_pdc(sig + "[" + str(i) + "]", p, others) - else: - pdc += _format_io_pdc(sig, pins[0], others) - pdc += "\n".join(additional_io_constraints) - tools.write_to_file(build_name + "_io.pdc", pdc) - -# Placement Constraints (.pdc) --------------------------------------------------------------------- - -def _build_fp_pdc(build_name, additional_fp_constraints): - pdc = "\n".join(additional_fp_constraints) - tools.write_to_file(build_name + "_fp.pdc", pdc) - -# Project (.tcl) ----------------------------------------------------------------------------------- - -def _build_tcl(platform, sources, build_dir, build_name): - tcl = [] - - # Create project - tcl.append(" ".join([ - "new_project", - "-location {./impl}", - "-name {}".format(tcl_name(build_name)), - "-project_description {}", - "-block_mode 0", - "-standalone_peripheral_initialization 0", - "-instantiate_in_smartdesign 1", - "-ondemand_build_dh 0", - "-use_enhanced_constraint_flow 1", - "-hdl {VERILOG}", - "-family {PolarFire}", - "-die {}", - "-package {}", - "-speed {}", - "-die_voltage {}", - "-part_range {}", - "-adv_options {}" - ])) - - die, package, speed = platform.device.split("-") - tcl.append(" ".join([ - "set_device", - "-family {PolarFire}", - "-die {}".format(tcl_name(die)), - "-package {}".format(tcl_name(package)), - "-speed {}".format(tcl_name("-" + speed)), - # FIXME: common to all PolarFire devices? - "-die_voltage {1.0}", - "-part_range {EXT}", - "-adv_options {IO_DEFT_STD:LVCMOS 1.8V}", - "-adv_options {RESTRICTPROBEPINS:1}", - "-adv_options {RESTRICTSPIPINS:0}", - "-adv_options {TEMPR:EXT}", - "-adv_options {UNUSED_MSS_IO_RESISTOR_PULL:None}", - "-adv_options {VCCI_1.2_VOLTR:EXT}", - "-adv_options {VCCI_1.5_VOLTR:EXT}", - "-adv_options {VCCI_1.8_VOLTR:EXT}", - "-adv_options {VCCI_2.5_VOLTR:EXT}", - "-adv_options {VCCI_3.3_VOLTR:EXT}", - "-adv_options {VOLTR:EXT} " - ])) - - # Add sources - for filename, language, library, *copy in sources: - filename_tcl = "{" + filename + "}" - tcl.append("import_files -hdl_source " + filename_tcl) - - # Set top level - tcl.append("set_root -module {}".format(tcl_name(build_name))) - - # Copy init files FIXME: support for include path on LiberoSoC? - for file in os.listdir(build_dir): - if file.endswith(".init"): - tcl.append("file copy -- {} impl/synthesis".format(file)) - - # Import io constraints - tcl.append("import_files -io_pdc {}".format(tcl_name(build_name + "_io.pdc"))) - - # Import floorplanner constraints - tcl.append("import_files -fp_pdc {}".format(tcl_name(build_name + "_fp.pdc"))) - - # Import timing constraints - tcl.append("import_files -convert_EDN_to_HDL 0 -sdc {}".format(tcl_name(build_name + ".sdc"))) - - # Associate constraints with tools - tcl.append(" ".join(["organize_tool_files", - "-tool {SYNTHESIZE}", - "-file impl/constraint/{}.sdc".format(build_name), - "-module {}".format(build_name), - "-input_type {constraint}" - ])) - tcl.append(" ".join(["organize_tool_files", - "-tool {PLACEROUTE}", - "-file impl/constraint/io/{}_io.pdc".format(build_name), - "-file impl/constraint/fp/{}_fp.pdc".format(build_name), - "-file impl/constraint/{}.sdc".format(build_name), - "-module {}".format(build_name), - "-input_type {constraint}" - ])) - tcl.append(" ".join(["organize_tool_files", - "-tool {VERIFYTIMING}", - "-file impl/constraint/{}.sdc".format(build_name), - "-module {}".format(build_name), - "-input_type {constraint}" - ])) - - # Build flow - tcl.append("run_tool -name {CONSTRAINT_MANAGEMENT}") - tcl.append("run_tool -name {SYNTHESIZE}") - tcl.append("run_tool -name {PLACEROUTE}") - tcl.append("run_tool -name {GENERATEPROGRAMMINGDATA}") - tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}") - - # Generate tcl - tools.write_to_file(build_name + ".tcl", "\n".join(tcl)) - -# Timing Constraints (.sdc) ------------------------------------------------------------------------ - -def _build_timing_sdc(vns, clocks, false_paths, build_name, additional_timing_constraints): - sdc = [] - - for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid): - sdc.append( - "create_clock -name {clk} -period " + str(period) + - " [get_nets {clk}]".format(clk=vns.get_name(clk))) - for from_, to in sorted(false_paths, - key=lambda x: (x[0].duid, x[1].duid)): - sdc.append( - "set_clock_groups " - "-group [get_clocks -include_generated_clocks -of [get_nets {from_}]] " - "-group [get_clocks -include_generated_clocks -of [get_nets {to}]] " - "-asynchronous".format(from_=from_, to=to)) - - # generate sdc - sdc += additional_timing_constraints - tools.write_to_file(build_name + ".sdc", "\n".join(sdc)) - -# Script ------------------------------------------------------------------------------------------- - -def _build_script(build_name, device): - if sys.platform in ("win32", "cygwin"): - script_ext = ".bat" - script_contents = "@echo off\nREM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" - copy_stmt = "copy" - fail_stmt = " || exit /b" - else: - script_ext = ".sh" - script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n" - copy_stmt = "cp" - fail_stmt = " || exit 1" - - 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"]: - shell = ["cmd", "/c"] - else: - shell = ["bash"] - - if subprocess.call(shell + [script]) != 0: - raise OSError("Subprocess failed") - # MicrosemiLiberoSoCPolarfireToolchain ------------------------------------------------------------- -class MicrosemiLiberoSoCPolarfireToolchain: +class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain): attr_translate = {} special_overrides = common.microsemi_polarfire_special_overrides def __init__(self): - self.clocks = dict() - self.false_paths = set() + super().__init__() self.additional_io_constraints = [] self.additional_fp_constraints = [] self.additional_timing_constraints = [] - def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = False, - **kwargs): + def build(self, platform, fragment, **kwargs): - # Create build directory - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) + return self._build(platform, fragment, **kwargs) - # Finalize design - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + # Helpers -------------------------------------------------------------------------------------- - # 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" - v_output.write(top_file) - platform.add_source(top_file) + @classmethod + def tcl_name(cls, name): + return "{" + name + "}" - # Generate design script file (.tcl) - _build_tcl(platform, platform.sources, build_dir, build_name) + # IO Constraints (.pdc) ------------------------------------------------------------------------ - # Generate design io constraints file (.pdc) - _build_io_pdc(named_sc, named_pc, build_name, self.additional_io_constraints) + @classmethod + def _format_io_constraint(cls, c): + if isinstance(c, Pins): + return "-pin_name {} ".format(c.identifiers[0]) + elif isinstance(c, IOStandard): + return "-io_std {} ".format(c.name) + elif isinstance(c, Misc): + return "-RES_PULL {} ".format(c.misc) + else: + raise NotImplementedError - # Generate design placement constraints file (.pdc) - _build_fp_pdc(build_name, self.additional_fp_constraints) + @classmethod + def _format_io_pdc(cls, signame, pin, others): + fmt_c = [_format_io_constraint(c) for c in ([Pins(pin)] + others)] + r = "set_io " + r += "-port_name {} ".format(tcl_name(signame)) + for c in ([Pins(pin)] + others): + r += _format_io_constraint(c) + r += "-fixed true " + r += "\n" + return r - # Generate design timing constraints file (.sdc) - _build_timing_sdc(v_output.ns, self.clocks, self.false_paths, build_name, - self.additional_timing_constraints) + def build_io_constraints(self): + pdc = "" + for sig, pins, others, resname in self.named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + pdc += self._format_io_pdc(sig + "[" + str(i) + "]", p, others) + else: + pdc += self._format_io_pdc(sig, pins[0], others) + pdc += "\n".join(self.additional_io_constraints) + tools.write_to_file(self._build_name + "_io.pdc", pdc) + return (self._build_name + "_io.pdc", "PDC") - # Generate build script - script = _build_script(build_name, platform.device) + # Placement Constraints (.pdc) ----------------------------------------------------------------- - # Run - if run: - # Delete previous impl - if os.path.exists("impl"): - shutil.rmtree("impl") - _run_script(script) + def build_placement_constraints(self): + pdc = "\n".join(self.additional_fp_constraints) + tools.write_to_file(self._build_name + "_fp.pdc", pdc) + return (self._build_name + "_fp.pdc", "PDC") - os.chdir(cwd) + # Project (.tcl) ------------------------------------------------------------------------------- - return v_output.ns + def build_project(self): + tcl = [] + + # Create project + tcl.append(" ".join([ + "new_project", + "-location {./impl}", + "-name {}".format(tcl_name(self._build_name)), + "-project_description {}", + "-block_mode 0", + "-standalone_peripheral_initialization 0", + "-instantiate_in_smartdesign 1", + "-ondemand_build_dh 0", + "-use_enhanced_constraint_flow 1", + "-hdl {VERILOG}", + "-family {PolarFire}", + "-die {}", + "-package {}", + "-speed {}", + "-die_voltage {}", + "-part_range {}", + "-adv_options {}" + ])) + + die, package, speed = self.platform.device.split("-") + tcl.append(" ".join([ + "set_device", + "-family {PolarFire}", + "-die {}".format(tcl_name(die)), + "-package {}".format(tcl_name(package)), + "-speed {}".format(tcl_name("-" + speed)), + # FIXME: common to all PolarFire devices? + "-die_voltage {1.0}", + "-part_range {EXT}", + "-adv_options {IO_DEFT_STD:LVCMOS 1.8V}", + "-adv_options {RESTRICTPROBEPINS:1}", + "-adv_options {RESTRICTSPIPINS:0}", + "-adv_options {TEMPR:EXT}", + "-adv_options {UNUSED_MSS_IO_RESISTOR_PULL:None}", + "-adv_options {VCCI_1.2_VOLTR:EXT}", + "-adv_options {VCCI_1.5_VOLTR:EXT}", + "-adv_options {VCCI_1.8_VOLTR:EXT}", + "-adv_options {VCCI_2.5_VOLTR:EXT}", + "-adv_options {VCCI_3.3_VOLTR:EXT}", + "-adv_options {VOLTR:EXT} " + ])) + + # Add sources + for filename, language, library, *copy in self.platform.sources: + filename_tcl = "{" + filename + "}" + tcl.append("import_files -hdl_source " + filename_tcl) + + # Set top level + tcl.append("set_root -module {}".format(tcl_name(self._build_name))) + + # Copy init files FIXME: support for include path on LiberoSoC? + for file in os.listdir(self._build_dir): + if file.endswith(".init"): + tcl.append("file copy -- {} impl/synthesis".format(file)) + + # Import io constraints + tcl.append("import_files -io_pdc {}".format(tcl_name(self._build_name + "_io.pdc"))) + + # Import floorplanner constraints + tcl.append("import_files -fp_pdc {}".format(tcl_name(self._build_name + "_fp.pdc"))) + + # Import timing constraints + tcl.append("import_files -convert_EDN_to_HDL 0 -sdc {}".format(tcl_name(self._build_name + ".sdc"))) + + # Associate constraints with tools + tcl.append(" ".join(["organize_tool_files", + "-tool {SYNTHESIZE}", + "-file impl/constraint/{}.sdc".format(self._build_name), + "-module {}".format(self._build_name), + "-input_type {constraint}" + ])) + tcl.append(" ".join(["organize_tool_files", + "-tool {PLACEROUTE}", + "-file impl/constraint/io/{}_io.pdc".format(self._build_name), + "-file impl/constraint/fp/{}_fp.pdc".format(self._build_name), + "-file impl/constraint/{}.sdc".format(self._build_name), + "-module {}".format(self._build_name), + "-input_type {constraint}" + ])) + tcl.append(" ".join(["organize_tool_files", + "-tool {VERIFYTIMING}", + "-file impl/constraint/{}.sdc".format(self._build_name), + "-module {}".format(self._build_name), + "-input_type {constraint}" + ])) + + # Build flow + tcl.append("run_tool -name {CONSTRAINT_MANAGEMENT}") + tcl.append("run_tool -name {SYNTHESIZE}") + tcl.append("run_tool -name {PLACEROUTE}") + tcl.append("run_tool -name {GENERATEPROGRAMMINGDATA}") + tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}") + + # Generate tcl + tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl)) + return self._build_name + ".tcl" + + # Timing Constraints (.sdc) -------------------------------------------------------------------- + + def build_timing_constraints(self, vns): + sdc = [] + + for clk, period in sorted(self.clocks.items(), key=lambda x: x[0].duid): + sdc.append( + "create_clock -name {clk} -period " + str(period) + + " [get_nets {clk}]".format(clk=vns.get_name(clk))) + for from_, to in sorted(self.false_paths, + key=lambda x: (x[0].duid, x[1].duid)): + sdc.append( + "set_clock_groups " + "-group [get_clocks -include_generated_clocks -of [get_nets {from_}]] " + "-group [get_clocks -include_generated_clocks -of [get_nets {to}]] " + "-asynchronous".format(from_=from_, to=to)) + + # generate sdc + sdc += self.additional_timing_constraints + tools.write_to_file(self._build_name + ".sdc", "\n".join(sdc)) + return (self._build_name + ".sdc", "SDC") + + # Script --------------------------------------------------------------------------------------- + + def build_script(self): + if sys.platform in ("win32", "cygwin"): + script_ext = ".bat" + script_contents = "@echo off\nREM Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n\n" + copy_stmt = "copy" + fail_stmt = " || exit /b" + else: + script_ext = ".sh" + script_contents = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n" + copy_stmt = "cp" + fail_stmt = " || exit 1" + + script_file = "build_" + self._build_name + script_ext + tools.write_to_file(script_file, script_contents, + force_unix=False) + return script_file + + def run_script(self.script): + # Delete previous impl + if os.path.exists("impl"): + shutil.rmtree("impl") + + if sys.platform in ["win32", "cygwin"]: + shell = ["cmd", "/c"] + else: + shell = ["bash"] + + if subprocess.call(shell + [script]) != 0: + raise OSError("Subprocess failed") def add_period_constraint(self, platform, clk, period): if clk in self.clocks: diff --git a/litex/build/quicklogic/f4pga.py b/litex/build/quicklogic/f4pga.py index c8a873bfd..26eaa6c44 100644 --- a/litex/build/quicklogic/f4pga.py +++ b/litex/build/quicklogic/f4pga.py @@ -13,115 +13,85 @@ from shutil import which from migen.fhdl.structure import _Fragment from litex.build.generic_platform import * +from litex.build.generic_toolchain import GenericToolchain from litex.build import tools from litex.build.quicklogic import common -# IO Constraints (.pcf) ---------------------------------------------------------------------------- -def _format_io_pcf(signame, pin, others): - r = f"set_io {signame} {Pins(pin).identifiers[0]}\n" - return r - -def _build_io_pcf(named_sc, named_pc, build_name): - pcf = "" - for sig, pins, others, resname in named_sc: - if len(pins) > 1: - for i, p in enumerate(pins): - pcf += _format_io_pcf(sig + "(" + str(i) + ")", p, others) - else: - pcf += _format_io_pcf(sig, pins[0], others) - tools.write_to_file(build_name + ".pcf", pcf) - -# Build Makefile ----------------------------------------------------------------------------------- - -def _build_makefile(platform, sources, build_dir, build_name): - makefile = [] - - # Define Paths. - makefile.append("mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))") - makefile.append("current_dir := $(patsubst %/,%,$(dir $(mkfile_path)))") - # bit -> h and bit -> bin requires TOP_F - makefile.append(f"TOP_F={build_name}") - - # Create Project. - # FIXME: Only use top file for now and ignore .init files. - makefile.append("all: {top}_bit.h {top}.bin build/{top}.bit".format(top=build_name)) - # build bit file (default) - makefile.append(f"build/{build_name}.bit:") - makefile.append("\tql_symbiflow -compile -d {device} -P {part} -v {verilog} -t {top} -p {pcf}".format( - device = platform.device, - part = {"ql-eos-s3": "PU64"}.get(platform.device), - verilog = f"{build_name}.v", - top = build_name, - pcf = f"{build_name}.pcf" - )) - # build header to include in CPU firmware - makefile.append("{top}_bit.h: build/{top}.bit".format(top=build_name)) - makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_bitheader)") - # build binary to write in dedicated FLASH area - makefile.append("{top}.bin: build/{top}.bit".format(top=build_name)) - makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_binary)") - - # Generate Makefile. - tools.write_to_file("Makefile", "\n".join(makefile)) - -def _run_make(): - make_cmd = ["make", "-j1"] - - if which("ql_symbiflow") is None: - msg = "Unable to find QuickLogic Symbiflow toolchain, please:\n" - msg += "- Add QuickLogic Symbiflow toolchain to your $PATH." - raise OSError(msg) - - if subprocess.call(make_cmd) != 0: - raise OSError("Error occured during QuickLogic Symbiflow's script execution.") - - # F4PGAToolchain ------------------------------------------------------------------------------- # Formerly SymbiflowToolchain, Symbiflow has been renamed to F4PGA ----------------------------- -class F4PGAToolchain: +class F4PGAToolchain(GenericToolchain): attr_translate = {} special_overrides = common.quicklogic_special_overrides def __init__(self): - self.clocks = dict() - self.false_paths = set() + super().__init__() - def build(self, platform, fragment, - build_dir = "build", - build_name = "top", - run = False, - **kwargs): + def build(self, platform, fragment, **kwargs): + return self._build(platform, fragment, **kwargs) - # Create build directory. - os.makedirs(build_dir, exist_ok=True) - cwd = os.getcwd() - os.chdir(build_dir) + # IO Constraints (.pcf) ------------------------------------------------------------------------ - # Finalize design. - if not isinstance(fragment, _Fragment): - fragment = fragment.get_fragment() - platform.finalize(fragment) + @classmethod + def _format_io_pcf(cls, signame, pin, others): + r = f"set_io {signame} {Pins(pin).identifiers[0]}\n" + return r - # 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" - v_output.write(top_file) - platform.add_source(top_file) + def build_io_constraints(self): + pcf = "" + for sig, pins, others, resname in self.named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + pcf += self._format_io_pcf(sig + "(" + str(i) + ")", p, others) + else: + pcf += self._format_io_pcf(sig, pins[0], others) + tools.write_to_file(self._build_name + ".pcf", pcf) + return (self._build_name + ".pcf", "PCF") - # Generate .pcf IO constraints file. - _build_io_pcf(named_sc, named_pc, build_name) + # Build Makefile ------------------------------------------------------------------------------- - # Generate Makefie. - _build_makefile(platform, platform.sources, build_dir, build_name) + def build_script(self): + makefile = [] - # Run. - if run: - _run_make() + # Define Paths. + makefile.append("mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))") + makefile.append("current_dir := $(patsubst %/,%,$(dir $(mkfile_path)))") + # bit -> h and bit -> bin requires TOP_F + makefile.append(f"TOP_F={self._build_name}") - os.chdir(cwd) + # Create Project. + # FIXME: Only use top file for now and ignore .init files. + makefile.append("all: {top}_bit.h {top}.bin build/{top}.bit".format(top=self._build_name)) + # build bit file (default) + makefile.append(f"build/{self._build_name}.bit:") + makefile.append("\tql_symbiflow -compile -d {device} -P {part} -v {verilog} -t {top} -p {pcf}".format( + device = self.platform.device, + part = {"ql-eos-s3": "PU64"}.get(self.platform.device), + verilog = f"{self._build_name}.v", + top = self._build_name, + pcf = f"{self._build_name}.pcf" + )) + # build header to include in CPU firmware + makefile.append("{top}_bit.h: build/{top}.bit".format(top=self._build_name)) + makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_bitheader)") + # build binary to write in dedicated FLASH area + makefile.append("{top}.bin: build/{top}.bit".format(top=self._build_name)) + makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_binary)") - return v_output.ns + # Generate Makefile. + tools.write_to_file("Makefile", "\n".join(makefile)) + + return "Makefile" + + def run_script(self, script): + make_cmd = ["make", "-j1"] + + if which("ql_symbiflow") is None: + msg = "Unable to find QuickLogic Symbiflow toolchain, please:\n" + msg += "- Add QuickLogic Symbiflow toolchain to your $PATH." + raise OSError(msg) + + if subprocess.call(make_cmd) != 0: + raise OSError("Error occured during QuickLogic Symbiflow's script execution.")