diff --git a/litex/build/VHDLWrapper.py b/litex/build/VHDLWrapper.py new file mode 100644 index 000000000..8bdb61a8b --- /dev/null +++ b/litex/build/VHDLWrapper.py @@ -0,0 +1,144 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2022 Gwenhael Goavec-Merou +# Copyright (c) 2022 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import os + +from migen import * + +# VHDLWrapper -------------------------------------------------------------------------------------- + +class VHDLWrapper(Module): + """ + VHDLWrapper simplify use of VHDL code: used to convert with ghdl the code if + needed or simply pass list of files to platform. May also add an Instance. + Attributes + ========== + _top_entity: str + name of the core highest level entity + _build_dir: str + directory where .ys and .v must be written and where to build + _work_package: str + when package is not default one, used to provides its name + _platform: subclass of GenericPlatform + current platform + _sources: list + list of files contained into the core (relative or absolute path) + _params: dict + Instance like params (p_ generics, o_ output, ...) when add_instance, + generics without prefix otherwise + _add_instance: bool + add if True an Instance() + _force_convert: bool + force use of GHDL even if the platform supports VHDL + _ghdl_opts: str + options to pass to ghdl + """ + def __init__(self, platform, top_entity, build_dir, + work_package = None, + force_convert = False, + add_instance = False, + params = dict(), + files = list()): + """ + constructor (see class attributes) + """ + self._top_entity = top_entity + self._build_dir = build_dir + self._work_package = work_package + self._platform = platform + self._sources = files + self._params = params + self._force_convert = force_convert + self._add_instance = add_instance + + self._ghdl_opts = "--ieee=synopsys -fexplicit -frelaxed-rules --std=08 " + if work_package is not None: + self._ghdl_opts += f"--work={self._work_package} " + self._ghdl_opts += "\\" + + def add_source(self, filename): + """ + append the source list with the path + name of a file + Parameters + ========== + filename: str + file name + path + """ + self._sources.append(filename) + + def add_sources(self, path, filenames): + """ + append the source list with a list of file after adding path + Parameters + ========== + path: str + absolute or relative path for all files + filenames: list + list of file to add + """ + self._sources += [os.path.join(path, f) for f in filenames] + + def do_finalize(self): + """ + - convert vhdl to verilog when toolchain can't deal with VHDL or + when force_convert is set to true + - appends platform file's list with the list of VHDL sources or + with resulting verilog + - add an Instance for this core + """ + inst_name = self._top_entity + + # platform able to synthesis verilog and vhdl -> no conversion + if self._platform.support_mixed_language and not self._force_convert: + ip_params = self._params + for file in self._files: + platform.add_source(file) + else: # platform is only able to synthesis verilog -> convert vhdl to verilog + # check if more than one core is instanciated + # if so -> append with _X + # FIXME: better solution ? + v_list = [] + for file, _, _ in self._platform.sources: + if self._top_entity in file: + v_list.append(file) + if len(v_list) != 0: + inst_name += f"_{len(v_list)}" + + verilog_out = os.path.join(self._build_dir, f"{inst_name}.v") + script = os.path.join(self._build_dir, f"{inst_name}.ys") + ys = [] + ys.append("ghdl " + self._ghdl_opts) + + ip_params = dict() + generics = [] + if self._add_instance: + for k, v in self._params.items(): + if k.startswith("p_"): + ys.append("-g" + k[2:] + "=" + str(v) + " \\") + else: + ip_params[k] = v + else: + ip_params = self._params + + from litex.build import tools + import subprocess + for source in self._sources: + ys.append(source + " \\") + ys.append(f"-e {self._top_entity}") + ys.append("chformal -assert -remove") + ys.append("write_verilog {}".format(verilog_out)) + tools.write_to_file(script, "\n".join(ys)) + if subprocess.call(["yosys", "-q", "-m", "ghdl", script]): + raise OSError(f"Unable to convert {inst_name} to verilog, please check your GHDL-Yosys-plugin install") + + # more than one instance of this core? rename top entity to avoid conflict + if inst_name != self._top_entity: + tools.replace_in_file(verilog_out, f"module {self._top_entity}(", f"module {inst_name}(") + self._platform.add_source(verilog_out) + + if self._add_instance: + self.specials += Instance(inst_name, **ip_params) diff --git a/litex/build/generic_platform.py b/litex/build/generic_platform.py index c327b9f6a..0826e4a28 100644 --- a/litex/build/generic_platform.py +++ b/litex/build/generic_platform.py @@ -323,6 +323,7 @@ class ConstraintManager: class GenericPlatform: def __init__(self, device, io, connectors=[], name=None): + self.toolchain = None self.device = device self.constraint_manager = ConstraintManager(io, connectors) if name is None: @@ -462,3 +463,7 @@ class GenericPlatform: def create_programmer(self): raise NotImplementedError + + @property + def support_mixed_language(self): + return self.toolchain.support_mixed_language diff --git a/litex/build/generic_toolchain.py b/litex/build/generic_toolchain.py index 08c6c2144..998f66576 100644 --- a/litex/build/generic_toolchain.py +++ b/litex/build/generic_toolchain.py @@ -18,6 +18,7 @@ class GenericToolchain: } supported_build_backend = ["litex"] + _support_mixed_language = True def __init__(self): self.clocks = dict() @@ -27,6 +28,10 @@ class GenericToolchain: self._vns = None self._synth_opts = "" + @property + def support_mixed_language(self): + return self._support_mixed_language + def finalize(self): pass # Pass since optional. diff --git a/litex/build/yosys_nextpnr_toolchain.py b/litex/build/yosys_nextpnr_toolchain.py index 04fc3a65b..e875bf3e0 100644 --- a/litex/build/yosys_nextpnr_toolchain.py +++ b/litex/build/yosys_nextpnr_toolchain.py @@ -57,10 +57,13 @@ class YosysNextPNRToolchain(GenericToolchain): target package (optional/target dependant) _speed_grade: str target speed grade (optional/target dependant) + _support_mixed_language: bool + informs if toolchain is able to use only verilog or verilog + vhdl """ attr_translate = { "keep": ("keep", "true"), } + _support_mixed_language = False family = "" synth_fmt = "" diff --git a/litex/soc/cores/cpu/neorv32/core.py b/litex/soc/cores/cpu/neorv32/core.py index 6053a5f8e..bffa497b9 100644 --- a/litex/soc/cores/cpu/neorv32/core.py +++ b/litex/soc/cores/cpu/neorv32/core.py @@ -8,6 +8,8 @@ import os from migen import * +from litex.build.VHDLWrapper import * + from litex.soc.interconnect import wishbone from litex.soc.cores.cpu import CPU, CPU_GCC_TRIPLE_RISCV32 @@ -97,15 +99,21 @@ class NEORV32(CPU): i_wb_err_i = idbus.err, ) + self.submodules.vhdlwrapper = VHDLWrapper(platform, + top_entity = "neorv32_litex_core_complex", + build_dir = os.path.abspath(os.path.dirname(__file__)), + work_package = "neorv32", + force_convert = True, + ) + # Add Verilog sources - self.add_sources(platform, variant) + self.add_sources(variant) def set_reset_address(self, reset_address): self.reset_address = reset_address assert reset_address == 0x0000_0000 - @staticmethod - def add_sources(platform, variant): + def add_sources(self, variant): cdir = os.path.abspath(os.path.dirname(__file__)) # List VHDL sources. sources = { @@ -152,6 +160,7 @@ class NEORV32(CPU): # Download VHDL sources (if not already present). for directory, vhds in sources.items(): for vhd in vhds: + self.vhdlwrapper.add_source(os.path.join(cdir, vhd)) if not os.path.exists(os.path.join(cdir, vhd)): os.system(f"wget https://raw.githubusercontent.com/stnolting/neorv32/main/rtl/{directory}/{vhd} -P {cdir}") @@ -188,22 +197,6 @@ class NEORV32(CPU): variant = variant, ) - # Convert VHDL to Verilog through GHDL/Yosys. - from litex.build import tools - import subprocess - cdir = os.path.dirname(__file__) - ys = [] - ys.append("ghdl --ieee=synopsys -fexplicit -frelaxed-rules --std=08 --work=neorv32 \\") - for directory, vhds in sources.items(): - for vhd in vhds: - ys.append(os.path.join(cdir, vhd) + " \\") - ys.append("-e neorv32_litex_core_complex") - ys.append("chformal -assert -remove") - ys.append("write_verilog {}".format(os.path.join(cdir, "neorv32_litex_core_complex.v"))) - tools.write_to_file(os.path.join(cdir, "neorv32_litex_core_complex.ys"), "\n".join(ys)) - if subprocess.call(["yosys", "-q", "-m", "ghdl", os.path.join(cdir, "neorv32_litex_core_complex.ys")]): - raise OSError("Unable to convert NEORV32 CPU to verilog, please check your GHDL-Yosys-plugin install.") - platform.add_source(os.path.join(cdir, "neorv32_litex_core_complex.v")) def do_finalize(self): assert hasattr(self, "reset_address")