Coverage for tsfpga/vivado/simlib_open_source.py: 47%

73 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-27 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 

11from abc import abstractmethod 

12from typing import TYPE_CHECKING, ClassVar 

13 

14from tsfpga import DEFAULT_FILE_ENCODING 

15from tsfpga.system_utils import create_directory, system_is_windows 

16 

17from .simlib_common import VivadoSimlibCommon 

18 

19if TYPE_CHECKING: 

20 from pathlib import Path 

21 

22 

23class VivadoSimlibOpenSource(VivadoSimlibCommon): 

24 """ 

25 Common methods for handling Vivado simlib with an open-source simulator. 

26 See subclasses for details: :class:`.VivadoSimlibGhdl`, :class:`.VivadoSimlibNvc`. 

27 

28 Do not instantiate this class directly. 

29 Use factory class :class:`.VivadoSimlib` instead. 

30 """ 

31 

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

33 

34 # Set in subclass. 

35 _create_library_folder_before_compile: bool 

36 

37 def _compile(self) -> None: 

38 self._compile_unisim() 

39 self._compile_secureip() 

40 self._compile_unimacro() 

41 self._compile_unifast() 

42 

43 def _compile_unisim(self) -> None: 

44 library_path = self._libraries_path / "unisims" 

45 

46 vhd_files = [] 

47 

48 for vhd_file_base in [ 

49 "unisim_VPKG", 

50 "unisim_retarget_VCOMP", 

51 ]: 

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

53 if not vhd_file.exists(): 

54 raise FileNotFoundError(f"VHDL file does not exist: {vhd_file}") 

55 

56 vhd_files.append(vhd_file) 

57 

58 primitive_dir = library_path / "primitive" 

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

60 

61 retarget_dir = library_path / "retarget" 

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

63 vhd_files.append(vhd_file) 

64 

65 self._compile_library(vhd_files=vhd_files, library_name="unisim") 

66 

67 def _compile_secureip(self) -> None: 

68 vhd_files = list((self._libraries_path / "unisims" / "secureip").glob("*.vhd")) 

69 self._compile_library(vhd_files=vhd_files, library_name="secureip") 

70 

71 def _compile_unimacro(self) -> None: 

72 library_path = self._libraries_path / "unimacro" 

73 

74 vhd_files = [] 

75 

76 vhd_file = library_path / "unimacro_VCOMP.vhd" 

77 if not vhd_file.exists(): 

78 raise FileNotFoundError(f"VHDL file does not exist: {vhd_file}") 

79 vhd_files.append(vhd_file) 

80 

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

82 

83 self._compile_library(vhd_files=vhd_files, library_name="unimacro") 

84 

85 def _compile_unifast(self) -> None: 

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

87 vhd_files = self._get_compile_order(library_path=library_path) 

88 

89 self._compile_library(vhd_files=vhd_files, library_name="unifast") 

90 

91 @staticmethod 

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

93 """ 

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

95 file provided by Xilinx. 

96 """ 

97 vhd_files = [] 

98 

99 with (library_path / "vhdl_analyze_order").open( 

100 encoding=DEFAULT_FILE_ENCODING 

101 ) as file_handle: 

102 for vhd_file_base in file_handle: 

103 vhd_file = library_path / vhd_file_base.strip() 

104 if not vhd_file.exists(): 

105 raise FileNotFoundError(f"VHDL file does not exist: {vhd_file}") 

106 

107 vhd_files.append(vhd_file) 

108 

109 return vhd_files 

110 

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

112 """ 

113 Compile all files of the library. 

114 """ 

115 output_path = self.output_path / library_name 

116 if self._create_library_folder_before_compile: 

117 create_directory(output_path, empty=True) 

118 

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

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

121 # (on Linux at least, as far as we know) the resulting command is in the order of 

122 # 90k characters long. 

123 # This does not seem to work on Windows. 

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

125 # boost on Linux. 

126 compile_file_by_file = system_is_windows() 

127 

128 if compile_file_by_file: 

129 # Compile each file in a separate command. 

130 for vhd_file in vhd_files: 

131 self._print_and_compile( 

132 output_path=output_path, library_name=library_name, vhd_files=[vhd_file] 

133 ) 

134 

135 else: 

136 # Compile all files in one single command. 

137 self._print_and_compile( 

138 output_path=output_path, library_name=library_name, vhd_files=vhd_files 

139 ) 

140 

141 def _print_and_compile( 

142 self, output_path: Path, library_name: str, vhd_files: list[Path] 

143 ) -> None: 

144 """ 

145 Print the file list and compile it. 

146 """ 

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

148 

149 # Use paths relative to the Vivado installation, in order to keep it a little shorter 

150 # (it is still massively long). 

151 vhd_paths_relative = [ 

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

153 ] 

154 paths_to_print = ", ".join(vhd_paths_relative) 

155 print(f"Compiling {paths_to_print} into {library_name}...") 

156 

157 self._execute_compile( 

158 output_path=output_path, library_name=library_name, vhd_files=vhd_paths_str 

159 ) 

160 

161 @abstractmethod 

162 def _execute_compile(self, output_path: Path, library_name: str, vhd_files: list[str]) -> None: 

163 """ 

164 Compile the files into the specified library. 

165 Raise exception upon failure. 

166 """