Coverage for tsfpga/vivado/simlib_ghdl.py: 52%

94 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-10 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 """ 

25 Handle Vivado simlib with GHDL. 

26 """ 

27 

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

29 

30 def __init__( 

31 self, 

32 vivado_path: Optional[Path], 

33 output_path: Path, 

34 vunit_proj: Any, 

35 simulator_interface: Any, 

36 ) -> None: 

37 """ 

38 Arguments: 

39 output_path: The compiled simlib will be placed here. 

40 vunit_proj: The VUnit project that is used to run simulation. 

41 simulator_interface: A VUnit SimulatorInterface class. 

42 vivado_path: Path to Vivado executable. 

43 """ 

44 self.ghdl_binary = Path(simulator_interface.find_prefix()) / "ghdl" 

45 

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

47 

48 self._vunit_proj = vunit_proj 

49 

50 def _compile(self) -> None: 

51 self._compile_unisim() 

52 self._compile_secureip() 

53 self._compile_unimacro() 

54 self._compile_unifast() 

55 

56 def _compile_unisim(self) -> None: 

57 library_path = self._libraries_path / "unisims" 

58 

59 vhd_files = [] 

60 

61 for vhd_file_base in [ 

62 "unisim_VPKG", 

63 "unisim_retarget_VCOMP", 

64 ]: 

65 vhd_file = library_path / f"{vhd_file_base}.vhd" 

66 assert vhd_file.exists(), vhd_file 

67 

68 vhd_files.append(vhd_file) 

69 

70 primitive_dir = library_path / "primitive" 

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

72 

73 retarget_dir = library_path / "retarget" 

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

75 vhd_files.append(vhd_file) 

76 

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

78 

79 def _compile_secureip(self) -> None: 

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

81 

82 vhd_files = [] 

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

84 vhd_files.append(vhd_file) 

85 

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

87 

88 def _compile_unimacro(self) -> None: 

89 library_path = self._libraries_path / "unimacro" 

90 

91 vhd_files = [] 

92 

93 vhd_file = library_path / "unimacro_VCOMP.vhd" 

94 assert vhd_file.exists(), vhd_file 

95 vhd_files.append(vhd_file) 

96 

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

98 

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

100 

101 def _compile_unifast(self) -> None: 

102 library_path = self._libraries_path / "unifast" / "primitive" 

103 vhd_files = self._get_compile_order(library_path=library_path) 

104 

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

106 

107 @staticmethod 

108 def _get_compile_order(library_path: Path) -> list[Path]: 

109 """ 

110 Get compile order (list of file paths, in order) from an existing compile order 

111 file provided by Xilinx. 

112 """ 

113 vhd_files = [] 

114 

115 with open( 

116 library_path / "vhdl_analyze_order", encoding=DEFAULT_FILE_ENCODING 

117 ) as file_handle: 

118 for vhd_file_base in file_handle.readlines(): 

119 vhd_file = library_path / vhd_file_base.strip() 

120 assert vhd_file.exists(), vhd_file 

121 vhd_files.append(vhd_file) 

122 

123 return vhd_files 

124 

125 def _compile_ghdl(self, vhd_files: list[Path], library_name: str) -> None: 

126 """ 

127 Compile a list of files into the specified library. 

128 """ 

129 workdir = self.output_path / library_name 

130 create_directory(workdir, empty=False) 

131 

132 vhd_paths_str = [str(vhd_file) for vhd_file in vhd_files] 

133 # We print a list of the files that will be compiled. 

134 # Use relative paths to the Vivado path, in order to keep it a little shorter 

135 # (it is still massively long). 

136 vhd_paths_relative = [ 

137 str(vhd_file.relative_to(self._libraries_path)) for vhd_file in vhd_files 

138 ] 

139 

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

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

142 

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

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

145 

146 # There seems to be a command length limit on Windows. 

147 # While compiling all files in one command gives a huge performance boost 

148 # (on Linux with GCC backend at least, as far as we know) the resulting command is in 

149 # the order of 90k characters long. 

150 # This does not seem to work on Windows. 

151 # So we auto detect the OS to work around this limitation, while keeping the performance 

152 # boost on Linux. 

153 # See https://gitlab.com/tsfpga/tsfpga/-/merge_requests/499 

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={str(workdir.resolve())}", 

175 f"-P{str(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 run_command(cmd, cwd=self.output_path) 

185 

186 def _get_simulator_tag(self) -> str: 

187 """ 

188 Return simulator version tag as a string. 

189 """ 

190 cmd = [str(self.ghdl_binary), "--version"] 

191 output = run_command(cmd, capture_output=True).stdout 

192 

193 regexp_with_tag = re.compile(r"^GHDL (\S+) \((\S+)\).*") 

194 match = regexp_with_tag.search(output) 

195 if match is not None: 

196 return self._format_version(f"ghdl_{match.group(1)}_{match.group(2)}") 

197 

198 regexp_without_tag = re.compile(r"^GHDL (\S+).*") 

199 match = regexp_without_tag.search(output) 

200 if match is not None: 

201 return self._format_version(f"ghdl_{match.group(1)}") 

202 

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

204 

205 def _add_to_vunit_project(self) -> None: 

206 """ 

207 Add the compiled simlib to your VUnit project. 

208 """ 

209 for library_name in self.library_names: 

210 library_path = self.output_path / library_name 

211 assert library_path.exists(), library_path 

212 

213 self._vunit_proj.add_external_library(library_name, library_path)