diff --git a/litex/build/gowin/apicula.py b/litex/build/gowin/apicula.py new file mode 100644 index 000000000..5f33b4d09 --- /dev/null +++ b/litex/build/gowin/apicula.py @@ -0,0 +1,48 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2024 Mai Lapyst +# SPDX-License-Identifier: BSD-2-Clause + +from litex.build.generic_platform import * +from litex.build import tools +from litex.build.gowin.gowin import _build_cst +from litex.build.yosys_nextpnr_toolchain import YosysNextPNRToolchain + +class GowinApiculaToolchain(YosysNextPNRToolchain): + family = "gowin" + synth_fmt = "json" + pnr_fmt = "report" + packer_cmd = "gowin_pack" + + def __init__(self): + super().__init__() + self.additional_cst_commands = [] + + def build_io_constraints(self): + _build_cst(self.named_sc, self.named_pc, self.additional_cst_commands, self._build_name) + return (self._build_name + ".cst", "CST") + + def finalize(self): + pnr_opts = "--write {top}_routed.json --top {top} --device {device}" + \ + " --vopt family={devicename} --vopt cst={top}.cst" + self._pnr_opts += pnr_opts.format( + top = self._build_name, + device = self.platform.device, + devicename = self.platform.devicename + ) + + self._packer_opts += "-d {devicename} -o {top}.fs {top}_routed.json".format( + devicename = self.platform.devicename, + top = self._build_name + ) + + YosysNextPNRToolchain.finalize(self) + + # family is gowin but NextPNRWrapper needs to call 'nextpnr-himbaechel' not 'nextpnr-gowin' + self._nextpnr.name = "nextpnr-himbaechel" + + def build(self, platform, fragment, **kwargs): + self.platform = platform + + return YosysNextPNRToolchain.build(self, platform, fragment, **kwargs) diff --git a/litex/build/gowin/gowin.py b/litex/build/gowin/gowin.py index eef6b3cf3..51d423fe0 100644 --- a/litex/build/gowin/gowin.py +++ b/litex/build/gowin/gowin.py @@ -17,6 +17,61 @@ from litex.build.generic_toolchain import GenericToolchain from litex.build.generic_platform import * from litex.build import tools +# Constraints (.cst) ------------------------------------------------------------------------------- + +def _build_cst(named_sc, named_pc, additional_cst_commands, build_name): + 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)) + + def _search_pin_entry(pin_lst, pin_name): + for name, pin, other in pin_lst: + if pin_name == name: + return (name, pin, other) + return (None, None, None) + + for name, pin, other in flat_sc: + if pin != "X": + t_name = name.split('[') # avoid index pins + tmp_name = t_name[0] + if tmp_name[-2:] == "_p": + pn = tmp_name[:-2] + "_n" + if len(t_name) > 1: + pn += '[' + t_name[1] + (_, n_pin, _) = _search_pin_entry(flat_sc, pn) + if n_pin is not None: + pin = f"{pin},{n_pin}" + elif tmp_name[-2:] == "_n": + pp = tmp_name[:-2] + "_p" + if len(t_name) > 1: + pp += '[' + t_name[1] + (p_name, _, _) = _search_pin_entry(flat_sc, pp) + if p_name is not None: + continue + cst.append(f"IO_LOC \"{name}\" {pin};") + + other_cst = [] + for c in other: + if isinstance(c, IOStandard): + other_cst.append(f"IO_TYPE={c.name}") + elif isinstance(c, Misc): + other_cst.append(f"{c.misc}") + if len(other_cst): + t = " ".join(other_cst) + cst.append(f"IO_PORT \"{name}\" {t};") + + if named_pc: + cst.extend(named_pc) + + cst.extend(additional_cst_commands) + + tools.write_to_file(build_name + ".cst", "\n".join(cst)) # GowinToolchain ----------------------------------------------------------------------------------- @@ -60,58 +115,7 @@ class GowinToolchain(GenericToolchain): # Constraints (.cst ) -------------------------------------------------------------------------- def build_io_constraints(self): - cst = [] - - 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)) - - def _search_pin_entry(pin_lst, pin_name): - for name, pin, other in pin_lst: - if pin_name == name: - return (name, pin, other) - return (None, None, None) - - for name, pin, other in flat_sc: - if pin != "X": - t_name = name.split('[') # avoid index pins - tmp_name = t_name[0] - if tmp_name[-2:] == "_p": - pn = tmp_name[:-2] + "_n" - if len(t_name) > 1: - pn += '[' + t_name[1] - (_, n_pin, _) = _search_pin_entry(flat_sc, pn) - if n_pin is not None: - pin = f"{pin},{n_pin}" - elif tmp_name[-2:] == "_n": - pp = tmp_name[:-2] + "_p" - if len(t_name) > 1: - pp += '[' + t_name[1] - (p_name, _, _) = _search_pin_entry(flat_sc, pp) - if p_name is not None: - continue - cst.append(f"IO_LOC \"{name}\" {pin};") - - other_cst = [] - for c in other: - if isinstance(c, IOStandard): - other_cst.append(f"IO_TYPE={c.name}") - elif isinstance(c, Misc): - other_cst.append(f"{c.misc}") - if len(other_cst): - t = " ".join(other_cst) - cst.append(f"IO_PORT \"{name}\" {t};") - - if self.named_pc: - cst.extend(self.named_pc) - - cst.extend(self.additional_cst_commands) - - tools.write_to_file(f"{self._build_name}.cst", "\n".join(cst)) + _build_cst(self.named_sc, self.named_pc, self.additional_cst_commands, self._build_name) return (f"{self._build_name}.cst", "CST") # Timing Constraints (.sdc ) ------------------------------------------------------------------- diff --git a/litex/build/gowin/platform.py b/litex/build/gowin/platform.py index a693db1fb..6706a8608 100644 --- a/litex/build/gowin/platform.py +++ b/litex/build/gowin/platform.py @@ -8,7 +8,7 @@ import os from litex.build.generic_platform import GenericPlatform -from litex.build.gowin import common, gowin +from litex.build.gowin import common, gowin, apicula # GowinPlatform ------------------------------------------------------------------------------------ @@ -28,7 +28,7 @@ class GowinPlatform(GenericPlatform): if toolchain == "gowin": self.toolchain = gowin.GowinToolchain() elif toolchain == "apicula": - raise ValueError("Apicula toolchain needs more work") + self.toolchain = apicula.GowinApiculaToolchain() else: raise ValueError(f"Unknown toolchain {toolchain}") diff --git a/litex/build/nextpnr_wrapper.py b/litex/build/nextpnr_wrapper.py index 451e2ec0e..18112013d 100644 --- a/litex/build/nextpnr_wrapper.py +++ b/litex/build/nextpnr_wrapper.py @@ -83,9 +83,10 @@ class NextPNRWrapper(): ======= str containing instruction and/or rule """ - cmd = "{pnr_name} --{in_fmt} {build_name}.{in_fmt} --{constr_fmt}" + \ - " {build_name}.{constr_fmt}" + \ - " --{out_fmt} {build_name}.{out_ext} {pnr_opts}" + cmd = "{pnr_name} --{in_fmt} {build_name}.{in_fmt}" + if self._constr_format != "": + cmd += " --{constr_fmt} {build_name}.{constr_fmt}" + cmd += " --{out_fmt} {build_name}.{out_ext} {pnr_opts}" base_cmd = cmd.format( pnr_name = self.name, build_name = self._build_name,