Coverage for tsfpga/vivado/simlib_ghdl.py: 52%
94 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-07 20:51 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-07 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 Any, 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
22class VivadoSimlibGhdl(VivadoSimlibCommon):
23 """
24 Handle Vivado simlib with GHDL.
25 """
27 library_names = ["unisim", "secureip", "unimacro", "unifast"]
29 def __init__(
30 self,
31 vivado_path: Optional[Path],
32 output_path: Path,
33 vunit_proj: Any,
34 simulator_interface: Any,
35 ) -> None:
36 """
37 Arguments:
38 output_path: The compiled simlib will be placed here.
39 vunit_proj: The VUnit project that is used to run simulation.
40 simulator_interface: A VUnit SimulatorInterface class.
41 vivado_path: Path to Vivado executable.
42 """
43 self.ghdl_binary = Path(simulator_interface.find_prefix()) / "ghdl"
45 super().__init__(vivado_path=vivado_path, output_path=output_path)
47 self._vunit_proj = vunit_proj
49 def _compile(self) -> None:
50 self._compile_unisim()
51 self._compile_secureip()
52 self._compile_unimacro()
53 self._compile_unifast()
55 def _compile_unisim(self) -> None:
56 library_path = self._libraries_path / "unisims"
58 vhd_files = []
60 for vhd_file_base in [
61 "unisim_VPKG",
62 "unisim_retarget_VCOMP",
63 ]:
64 vhd_file = library_path / f"{vhd_file_base}.vhd"
65 assert vhd_file.exists(), vhd_file
67 vhd_files.append(vhd_file)
69 primitive_dir = library_path / "primitive"
70 vhd_files += self._get_compile_order(library_path=primitive_dir)
72 retarget_dir = library_path / "retarget"
73 for vhd_file in retarget_dir.glob("*.vhd"):
74 vhd_files.append(vhd_file)
76 self._compile_ghdl(vhd_files=vhd_files, library_name="unisim")
78 def _compile_secureip(self) -> None:
79 library_path = self._libraries_path / "unisims" / "secureip"
81 vhd_files = []
82 for vhd_file in library_path.glob("*.vhd"):
83 vhd_files.append(vhd_file)
85 self._compile_ghdl(vhd_files=vhd_files, library_name="secureip")
87 def _compile_unimacro(self) -> None:
88 library_path = self._libraries_path / "unimacro"
90 vhd_files = []
92 vhd_file = library_path / "unimacro_VCOMP.vhd"
93 assert vhd_file.exists(), 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 open(
115 library_path / "vhdl_analyze_order", encoding=DEFAULT_FILE_ENCODING
116 ) as file_handle:
117 for vhd_file_base in file_handle.readlines():
118 vhd_file = library_path / vhd_file_base.strip()
119 assert vhd_file.exists(), vhd_file
120 vhd_files.append(vhd_file)
122 return vhd_files
124 def _compile_ghdl(self, vhd_files: list[Path], library_name: str) -> None:
125 """
126 Compile a list of files into the specified library.
127 """
128 workdir = self.output_path / library_name
129 create_directory(workdir, empty=False)
131 vhd_paths_str = [str(vhd_file) for vhd_file in vhd_files]
132 # We print a list of the files that will be compiled.
133 # Use relative paths to the Vivado path, in order to keep it a little shorter
134 # (it is still massively long).
135 vhd_paths_relative = [
136 str(vhd_file.relative_to(self._libraries_path)) for vhd_file in vhd_files
137 ]
139 def print_compiling(path: str) -> None:
140 print(f"Compiling {path} into {library_name}...")
142 def execute_ghdl(files: list[str]) -> None:
143 self._execute_ghdl(workdir=workdir, library_name=library_name, files=files)
145 # There seems to be a command length limit on Windows.
146 # While compiling all files in one command gives a huge performance boost
147 # (on Linux with GCC backend at least, as far as we know) the resulting command is in
148 # the order of 90k characters long.
149 # This does not seem to work on Windows.
150 # So we auto detect the OS to work around this limitation, while keeping the performance
151 # boost on Linux.
152 # See https://gitlab.com/tsfpga/tsfpga/-/merge_requests/499
153 compile_file_by_file = system_is_windows()
155 if compile_file_by_file:
156 # Compile each file in a separate command.
157 for vhd_file_idx, vhd_file_str in enumerate(vhd_paths_str):
158 print_compiling(vhd_paths_relative[vhd_file_idx])
159 execute_ghdl(files=[vhd_file_str])
161 else:
162 # Compile all files in one single command.
163 paths_to_print = ", ".join(vhd_paths_relative)
164 print_compiling(paths_to_print)
165 execute_ghdl(files=vhd_paths_str)
167 def _execute_ghdl(self, workdir: Path, library_name: str, files: list[str]) -> None:
168 cmd = [
169 str(self.ghdl_binary),
170 "-a",
171 "--ieee=synopsys",
172 "--std=08",
173 f"--workdir={str(workdir.resolve())}",
174 f"-P{str(self.output_path / 'unisim')}",
175 "-fexplicit",
176 "-frelaxed-rules",
177 "--no-vital-checks",
178 "--warn-binding",
179 "--mb-comments",
180 f"--work={library_name}",
181 ] + files
183 run_command(cmd, cwd=self.output_path)
185 def _get_simulator_tag(self) -> str:
186 """
187 Return simulator version tag as a string.
188 """
189 cmd = [str(self.ghdl_binary), "--version"]
190 output = run_command(cmd, capture_output=True).stdout
192 regexp_with_tag = re.compile(r"^GHDL (\S+) \((\S+)\).*")
193 match = regexp_with_tag.search(output)
194 if match is not None:
195 return self._format_version(f"ghdl_{match.group(1)}_{match.group(2)}")
197 regexp_without_tag = re.compile(r"^GHDL (\S+).*")
198 match = regexp_without_tag.search(output)
199 if match is not None:
200 return self._format_version(f"ghdl_{match.group(1)}")
202 raise ValueError(f"Could not find GHDL version string: {output}")
204 def _add_to_vunit_project(self) -> None:
205 """
206 Add the compiled simlib to your VUnit project.
207 """
208 for library_name in self.library_names:
209 library_path = self.output_path / library_name
210 assert library_path.exists(), library_path
212 self._vunit_proj.add_external_library(library_name, library_path)