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

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 

9# Standard libraries 

10import re 

11from pathlib import Path 

12from typing import TYPE_CHECKING, Optional 

13 

14# First party libraries 

15from tsfpga import DEFAULT_FILE_ENCODING 

16from tsfpga.system_utils import create_directory, run_command, system_is_windows 

17 

18# Local folder libraries 

19from .simlib_common import VivadoSimlibCommon 

20 

21if TYPE_CHECKING: 

22 # Third party libraries 

23 from vunit.sim_if import SimulatorInterface 

24 from vunit.ui import VUnit 

25 

26 

27class VivadoSimlibGhdl(VivadoSimlibCommon): 

28 """ 

29 Handle Vivado simlib with GHDL. 

30 """ 

31 

32 library_names = ["unisim", "secureip", "unimacro", "unifast"] 

33 

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" 

49 

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

51 

52 self._vunit_proj = vunit_proj 

53 

54 def _compile(self) -> None: 

55 self._compile_unisim() 

56 self._compile_secureip() 

57 self._compile_unimacro() 

58 self._compile_unifast() 

59 

60 def _compile_unisim(self) -> None: 

61 library_path = self._libraries_path / "unisims" 

62 

63 vhd_files = [] 

64 

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 

71 

72 vhd_files.append(vhd_file) 

73 

74 primitive_dir = library_path / "primitive" 

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

76 

77 retarget_dir = library_path / "retarget" 

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

79 vhd_files.append(vhd_file) 

80 

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

82 

83 def _compile_secureip(self) -> None: 

84 library_path = self._libraries_path / "unisims" / "secureip" 

85 

86 vhd_files = [] 

87 for vhd_file in library_path.glob("*.vhd"): 

88 vhd_files.append(vhd_file) 

89 

90 self._compile_ghdl(vhd_files=vhd_files, library_name="secureip") 

91 

92 def _compile_unimacro(self) -> None: 

93 library_path = self._libraries_path / "unimacro" 

94 

95 vhd_files = [] 

96 

97 vhd_file = library_path / "unimacro_VCOMP.vhd" 

98 assert vhd_file.exists(), vhd_file 

99 vhd_files.append(vhd_file) 

100 

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

102 

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

104 

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) 

108 

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

110 

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

118 

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) 

126 

127 return vhd_files 

128 

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) 

135 

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 ] 

143 

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

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

146 

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

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

149 

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

158 

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

164 

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) 

170 

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 

186 

187 run_command(cmd, cwd=self.output_path) 

188 

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 

195 

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

200 

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

205 

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

207 

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 

215 

216 self._vunit_proj.add_external_library(library_name, library_path)