Coverage for tsfpga/vivado/simlib_ghdl.py: 52%
99 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 20:51 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 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# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import re
12from pathlib import Path
13from typing import TYPE_CHECKING, ClassVar
15from tsfpga import DEFAULT_FILE_ENCODING
16from tsfpga.system_utils import create_directory, run_command, system_is_windows
18from .simlib_common import VivadoSimlibCommon
20if TYPE_CHECKING:
21 from vunit.sim_if import SimulatorInterface
22 from vunit.ui import VUnit
25class VivadoSimlibGhdl(VivadoSimlibCommon):
26 """
27 Handle Vivado simlib with GHDL.
28 """
30 library_names: ClassVar = ["unisim", "secureip", "unimacro", "unifast"]
32 def __init__(
33 self,
34 vivado_path: Path | None,
35 output_path: Path,
36 vunit_proj: VUnit,
37 simulator_interface: SimulatorInterface,
38 ) -> None:
39 """
40 Arguments:
41 output_path: The compiled simlib will be placed here.
42 vunit_proj: The VUnit project that is used to run simulation.
43 simulator_interface: A VUnit SimulatorInterface class.
44 vivado_path: Path to Vivado executable.
45 """
46 self.ghdl_binary = Path(simulator_interface.find_prefix()) / "ghdl"
48 super().__init__(vivado_path=vivado_path, output_path=output_path)
50 self._vunit_proj = vunit_proj
52 def _compile(self) -> None:
53 self._compile_unisim()
54 self._compile_secureip()
55 self._compile_unimacro()
56 self._compile_unifast()
58 def _compile_unisim(self) -> None:
59 library_path = self._libraries_path / "unisims"
61 vhd_files = []
63 for vhd_file_base in [
64 "unisim_VPKG",
65 "unisim_retarget_VCOMP",
66 ]:
67 vhd_file = library_path / f"{vhd_file_base}.vhd"
68 if not vhd_file.exists():
69 raise FileNotFoundError(f"VHDL file does not exist: {vhd_file}")
71 vhd_files.append(vhd_file)
73 primitive_dir = library_path / "primitive"
74 vhd_files += self._get_compile_order(library_path=primitive_dir)
76 retarget_dir = library_path / "retarget"
77 for vhd_file in retarget_dir.glob("*.vhd"):
78 vhd_files.append(vhd_file)
80 self._compile_ghdl(vhd_files=vhd_files, library_name="unisim")
82 def _compile_secureip(self) -> None:
83 vhd_files = (self._libraries_path / "unisims" / "secureip").glob("*.vhd")
84 self._compile_ghdl(vhd_files=vhd_files, library_name="secureip")
86 def _compile_unimacro(self) -> None:
87 library_path = self._libraries_path / "unimacro"
89 vhd_files = []
91 vhd_file = library_path / "unimacro_VCOMP.vhd"
92 if not vhd_file.exists():
93 raise FileNotFoundError(f"VHDL file does not exist: {vhd_file}")
94 vhd_files.append(vhd_file)
96 vhd_files += self._get_compile_order(library_path=library_path)
98 self._compile_ghdl(vhd_files=vhd_files, library_name="unimacro")
100 def _compile_unifast(self) -> None:
101 library_path = self._libraries_path / "unifast" / "primitive"
102 vhd_files = self._get_compile_order(library_path=library_path)
104 self._compile_ghdl(vhd_files=vhd_files, library_name="unifast")
106 @staticmethod
107 def _get_compile_order(library_path: Path) -> list[Path]:
108 """
109 Get compile order (list of file paths, in order) from an existing compile order
110 file provided by Xilinx.
111 """
112 vhd_files = []
114 with (library_path / "vhdl_analyze_order").open(
115 encoding=DEFAULT_FILE_ENCODING
116 ) as file_handle:
117 for vhd_file_base in file_handle:
118 vhd_file = library_path / vhd_file_base.strip()
119 if not vhd_file.exists():
120 raise FileNotFoundError(f"VHDL file does not exist: {vhd_file}")
122 vhd_files.append(vhd_file)
124 return vhd_files
126 def _compile_ghdl(self, vhd_files: list[Path], library_name: str) -> None:
127 """
128 Compile a list of files into the specified library.
129 """
130 workdir = self.output_path / library_name
131 create_directory(workdir, empty=False)
133 vhd_paths_str = [str(vhd_file) for vhd_file in vhd_files]
134 # We print a list of the files that will be compiled.
135 # Use relative paths to the Vivado path, in order to keep it a little shorter
136 # (it is still massively long).
137 vhd_paths_relative = [
138 str(vhd_file.relative_to(self._libraries_path)) for vhd_file in vhd_files
139 ]
141 def print_compiling(path: str) -> None:
142 print(f"Compiling {path} into {library_name}...")
144 def execute_ghdl(files: list[str]) -> None:
145 self._execute_ghdl(workdir=workdir, library_name=library_name, files=files)
147 # There seems to be a command length limit on Windows.
148 # While compiling all files in one command gives a huge performance boost
149 # (on Linux with GCC backend at least, as far as we know) the resulting command is in
150 # the order of 90k characters long.
151 # This does not seem to work on Windows.
152 # So we auto detect the OS to work around this limitation, while keeping the performance
153 # boost on Linux.
154 compile_file_by_file = system_is_windows()
156 if compile_file_by_file:
157 # Compile each file in a separate command.
158 for vhd_file_idx, vhd_file_str in enumerate(vhd_paths_str):
159 print_compiling(vhd_paths_relative[vhd_file_idx])
160 execute_ghdl(files=[vhd_file_str])
162 else:
163 # Compile all files in one single command.
164 paths_to_print = ", ".join(vhd_paths_relative)
165 print_compiling(paths_to_print)
166 execute_ghdl(files=vhd_paths_str)
168 def _execute_ghdl(self, workdir: Path, library_name: str, files: list[str]) -> None:
169 cmd = [
170 str(self.ghdl_binary),
171 "-a",
172 "--ieee=synopsys",
173 "--std=08",
174 f"--workdir={workdir.resolve()}",
175 f"-P{self.output_path / 'unisim'}",
176 "-fexplicit",
177 "-frelaxed-rules",
178 "--no-vital-checks",
179 "--warn-binding",
180 "--mb-comments",
181 f"--work={library_name}",
182 *files,
183 ]
185 run_command(cmd, cwd=self.output_path)
187 def _get_simulator_tag(self) -> str:
188 """
189 Return simulator version tag as a string.
190 """
191 cmd = [str(self.ghdl_binary), "--version"]
192 output = run_command(cmd, capture_output=True).stdout
194 regexp_with_tag = re.compile(r"^GHDL (\S+) \((\S+)\).*")
195 match = regexp_with_tag.search(output)
196 if match is not None:
197 return self._format_version(f"ghdl_{match.group(1)}_{match.group(2)}")
199 regexp_without_tag = re.compile(r"^GHDL (\S+).*")
200 match = regexp_without_tag.search(output)
201 if match is not None:
202 return self._format_version(f"ghdl_{match.group(1)}")
204 raise ValueError(f"Could not find GHDL version string: {output}")
206 def _add_to_vunit_project(self) -> None:
207 """
208 Add the compiled simlib to your VUnit project.
209 """
210 for library_name in self.library_names:
211 library_path = self.output_path / library_name
212 if not library_path.exists():
213 raise FileNotFoundError(f"Library path does not exist: {library_path}")
215 self._vunit_proj.add_external_library(library_name, library_path)