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

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# -------------------------------------------------------------------------------------------------- 

8 

9from __future__ import annotations 

10 

11import re 

12from pathlib import Path 

13from typing import TYPE_CHECKING, ClassVar 

14 

15from tsfpga import DEFAULT_FILE_ENCODING 

16from tsfpga.system_utils import create_directory, run_command, system_is_windows 

17 

18from .simlib_common import VivadoSimlibCommon 

19 

20if TYPE_CHECKING: 

21 from vunit.sim_if import SimulatorInterface 

22 from vunit.ui import VUnit 

23 

24 

25class VivadoSimlibGhdl(VivadoSimlibCommon): 

26 """ 

27 Handle Vivado simlib with GHDL. 

28 """ 

29 

30 library_names: ClassVar = ["unisim", "secureip", "unimacro", "unifast"] 

31 

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" 

47 

48 super().__init__(vivado_path=vivado_path, output_path=output_path) 

49 

50 self._vunit_proj = vunit_proj 

51 

52 def _compile(self) -> None: 

53 self._compile_unisim() 

54 self._compile_secureip() 

55 self._compile_unimacro() 

56 self._compile_unifast() 

57 

58 def _compile_unisim(self) -> None: 

59 library_path = self._libraries_path / "unisims" 

60 

61 vhd_files = [] 

62 

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}") 

70 

71 vhd_files.append(vhd_file) 

72 

73 primitive_dir = library_path / "primitive" 

74 vhd_files += self._get_compile_order(library_path=primitive_dir) 

75 

76 retarget_dir = library_path / "retarget" 

77 for vhd_file in retarget_dir.glob("*.vhd"): 

78 vhd_files.append(vhd_file) 

79 

80 self._compile_ghdl(vhd_files=vhd_files, library_name="unisim") 

81 

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") 

85 

86 def _compile_unimacro(self) -> None: 

87 library_path = self._libraries_path / "unimacro" 

88 

89 vhd_files = [] 

90 

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) 

95 

96 vhd_files += self._get_compile_order(library_path=library_path) 

97 

98 self._compile_ghdl(vhd_files=vhd_files, library_name="unimacro") 

99 

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) 

103 

104 self._compile_ghdl(vhd_files=vhd_files, library_name="unifast") 

105 

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 = [] 

113 

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}") 

121 

122 vhd_files.append(vhd_file) 

123 

124 return vhd_files 

125 

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) 

132 

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 ] 

140 

141 def print_compiling(path: str) -> None: 

142 print(f"Compiling {path} into {library_name}...") 

143 

144 def execute_ghdl(files: list[str]) -> None: 

145 self._execute_ghdl(workdir=workdir, library_name=library_name, files=files) 

146 

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() 

155 

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]) 

161 

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) 

167 

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 ] 

184 

185 run_command(cmd, cwd=self.output_path) 

186 

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 

193 

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)}") 

198 

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)}") 

203 

204 raise ValueError(f"Could not find GHDL version string: {output}") 

205 

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}") 

214 

215 self._vunit_proj.add_external_library(library_name, library_path)