Coverage for tsfpga/vivado/simlib_ghdl.py: 52%
97 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-04 20:51 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-04 20:51 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9# Standard libraries
10import re
11from pathlib import Path
12from typing import TYPE_CHECKING, Optional
14# First party libraries
15from tsfpga import DEFAULT_FILE_ENCODING
16from tsfpga.system_utils import create_directory, run_command, system_is_windows
18# Local folder libraries
19from .simlib_common import VivadoSimlibCommon
21if TYPE_CHECKING:
22 # Third party libraries
23 from vunit.sim_if import SimulatorInterface
24 from vunit.ui import VUnit
27class VivadoSimlibGhdl(VivadoSimlibCommon):
28 """
29 Handle Vivado simlib with GHDL.
30 """
32 library_names = ["unisim", "secureip", "unimacro", "unifast"]
34 def __init__(
35 self,
36 vivado_path: Optional[Path],
37 output_path: Path,
38 vunit_proj: "VUnit",
39 simulator_interface: "SimulatorInterface",
40 ) -> None:
41 """
42 Arguments:
43 output_path: The compiled simlib will be placed here.
44 vunit_proj: The VUnit project that is used to run simulation.
45 simulator_interface: A VUnit SimulatorInterface class.
46 vivado_path: Path to Vivado executable.
47 """
48 self.ghdl_binary = Path(simulator_interface.find_prefix()) / "ghdl"
50 super().__init__(vivado_path=vivado_path, output_path=output_path)
52 self._vunit_proj = vunit_proj
54 def _compile(self) -> None:
55 self._compile_unisim()
56 self._compile_secureip()
57 self._compile_unimacro()
58 self._compile_unifast()
60 def _compile_unisim(self) -> None:
61 library_path = self._libraries_path / "unisims"
63 vhd_files = []
65 for vhd_file_base in [
66 "unisim_VPKG",
67 "unisim_retarget_VCOMP",
68 ]:
69 vhd_file = library_path / f"{vhd_file_base}.vhd"
70 assert vhd_file.exists(), vhd_file
72 vhd_files.append(vhd_file)
74 primitive_dir = library_path / "primitive"
75 vhd_files += self._get_compile_order(library_path=primitive_dir)
77 retarget_dir = library_path / "retarget"
78 for vhd_file in retarget_dir.glob("*.vhd"):
79 vhd_files.append(vhd_file)
81 self._compile_ghdl(vhd_files=vhd_files, library_name="unisim")
83 def _compile_secureip(self) -> None:
84 library_path = self._libraries_path / "unisims" / "secureip"
86 vhd_files = []
87 for vhd_file in library_path.glob("*.vhd"):
88 vhd_files.append(vhd_file)
90 self._compile_ghdl(vhd_files=vhd_files, library_name="secureip")
92 def _compile_unimacro(self) -> None:
93 library_path = self._libraries_path / "unimacro"
95 vhd_files = []
97 vhd_file = library_path / "unimacro_VCOMP.vhd"
98 assert vhd_file.exists(), vhd_file
99 vhd_files.append(vhd_file)
101 vhd_files += self._get_compile_order(library_path=library_path)
103 self._compile_ghdl(vhd_files=vhd_files, library_name="unimacro")
105 def _compile_unifast(self) -> None:
106 library_path = self._libraries_path / "unifast" / "primitive"
107 vhd_files = self._get_compile_order(library_path=library_path)
109 self._compile_ghdl(vhd_files=vhd_files, library_name="unifast")
111 @staticmethod
112 def _get_compile_order(library_path: Path) -> list[Path]:
113 """
114 Get compile order (list of file paths, in order) from an existing compile order
115 file provided by Xilinx.
116 """
117 vhd_files = []
119 with open(
120 library_path / "vhdl_analyze_order", encoding=DEFAULT_FILE_ENCODING
121 ) as file_handle:
122 for vhd_file_base in file_handle.readlines():
123 vhd_file = library_path / vhd_file_base.strip()
124 assert vhd_file.exists(), vhd_file
125 vhd_files.append(vhd_file)
127 return vhd_files
129 def _compile_ghdl(self, vhd_files: list[Path], library_name: str) -> None:
130 """
131 Compile a list of files into the specified library.
132 """
133 workdir = self.output_path / library_name
134 create_directory(workdir, empty=False)
136 vhd_paths_str = [str(vhd_file) for vhd_file in vhd_files]
137 # We print a list of the files that will be compiled.
138 # Use relative paths to the Vivado path, in order to keep it a little shorter
139 # (it is still massively long).
140 vhd_paths_relative = [
141 str(vhd_file.relative_to(self._libraries_path)) for vhd_file in vhd_files
142 ]
144 def print_compiling(path: str) -> None:
145 print(f"Compiling {path} into {library_name}...")
147 def execute_ghdl(files: list[str]) -> None:
148 self._execute_ghdl(workdir=workdir, library_name=library_name, files=files)
150 # There seems to be a command length limit on Windows.
151 # While compiling all files in one command gives a huge performance boost
152 # (on Linux with GCC backend at least, as far as we know) the resulting command is in
153 # the order of 90k characters long.
154 # This does not seem to work on Windows.
155 # So we auto detect the OS to work around this limitation, while keeping the performance
156 # boost on Linux.
157 compile_file_by_file = system_is_windows()
159 if compile_file_by_file:
160 # Compile each file in a separate command.
161 for vhd_file_idx, vhd_file_str in enumerate(vhd_paths_str):
162 print_compiling(vhd_paths_relative[vhd_file_idx])
163 execute_ghdl(files=[vhd_file_str])
165 else:
166 # Compile all files in one single command.
167 paths_to_print = ", ".join(vhd_paths_relative)
168 print_compiling(paths_to_print)
169 execute_ghdl(files=vhd_paths_str)
171 def _execute_ghdl(self, workdir: Path, library_name: str, files: list[str]) -> None:
172 cmd = [
173 str(self.ghdl_binary),
174 "-a",
175 "--ieee=synopsys",
176 "--std=08",
177 f"--workdir={str(workdir.resolve())}",
178 f"-P{str(self.output_path / 'unisim')}",
179 "-fexplicit",
180 "-frelaxed-rules",
181 "--no-vital-checks",
182 "--warn-binding",
183 "--mb-comments",
184 f"--work={library_name}",
185 ] + files
187 run_command(cmd, cwd=self.output_path)
189 def _get_simulator_tag(self) -> str:
190 """
191 Return simulator version tag as a string.
192 """
193 cmd = [str(self.ghdl_binary), "--version"]
194 output = run_command(cmd, capture_output=True).stdout
196 regexp_with_tag = re.compile(r"^GHDL (\S+) \((\S+)\).*")
197 match = regexp_with_tag.search(output)
198 if match is not None:
199 return self._format_version(f"ghdl_{match.group(1)}_{match.group(2)}")
201 regexp_without_tag = re.compile(r"^GHDL (\S+).*")
202 match = regexp_without_tag.search(output)
203 if match is not None:
204 return self._format_version(f"ghdl_{match.group(1)}")
206 raise ValueError(f"Could not find GHDL version string: {output}")
208 def _add_to_vunit_project(self) -> None:
209 """
210 Add the compiled simlib to your VUnit project.
211 """
212 for library_name in self.library_names:
213 library_path = self.output_path / library_name
214 assert library_path.exists(), library_path
216 self._vunit_proj.add_external_library(library_name, library_path)