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

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 Any, 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 

21 

22class VivadoSimlibGhdl(VivadoSimlibCommon): 

23 """ 

24 Handle Vivado simlib with GHDL. 

25 """ 

26 

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

28 

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" 

44 

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

46 

47 self._vunit_proj = vunit_proj 

48 

49 def _compile(self) -> None: 

50 self._compile_unisim() 

51 self._compile_secureip() 

52 self._compile_unimacro() 

53 self._compile_unifast() 

54 

55 def _compile_unisim(self) -> None: 

56 library_path = self._libraries_path / "unisims" 

57 

58 vhd_files = [] 

59 

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 

66 

67 vhd_files.append(vhd_file) 

68 

69 primitive_dir = library_path / "primitive" 

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

71 

72 retarget_dir = library_path / "retarget" 

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

74 vhd_files.append(vhd_file) 

75 

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

77 

78 def _compile_secureip(self) -> None: 

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

80 

81 vhd_files = [] 

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

83 vhd_files.append(vhd_file) 

84 

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

86 

87 def _compile_unimacro(self) -> None: 

88 library_path = self._libraries_path / "unimacro" 

89 

90 vhd_files = [] 

91 

92 vhd_file = library_path / "unimacro_VCOMP.vhd" 

93 assert vhd_file.exists(), 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 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) 

121 

122 return vhd_files 

123 

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) 

130 

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 ] 

138 

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

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

141 

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

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

144 

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

154 

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

160 

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) 

166 

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 

182 

183 run_command(cmd, cwd=self.output_path) 

184 

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 

191 

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

196 

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

201 

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

203 

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 

211 

212 self._vunit_proj.add_external_library(library_name, library_path)