Coverage for tsfpga/vivado/simlib_common.py: 91%

67 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 20:52 +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 platform 

11import zipfile 

12from abc import ABC, abstractmethod 

13from pathlib import Path 

14from shutil import make_archive 

15from typing import Optional 

16 

17# First party libraries 

18from tsfpga.system_utils import create_file, delete 

19from tsfpga.vivado.common import get_vivado_version 

20 

21# Local folder libraries 

22from .common import get_vivado_path 

23 

24 

25class VivadoSimlibCommon(ABC): 

26 """ 

27 Class for handling Vivado simlib used for simulation. Keeps track of when a 

28 (re)compile is needed. 

29 

30 This is a base class that defines an interface and some common methods. 

31 See subclasses for details: :class:`.VivadoSimlibGhdl`, :class:`.VivadoSimlibCommercial`. 

32 """ 

33 

34 # The version of this class. Can be bumped to force a re-compile if e.g. the TCL script changes 

35 # or the output folder structure is updated. 

36 _format_version_id = 4 

37 

38 # Set in subclass to a list of strings. The libraries that shall be compiled and added to 

39 # VUnit project. 

40 library_names: list[str] 

41 

42 def __init__(self, vivado_path: Optional[Path], output_path: Path) -> None: 

43 """ 

44 Call from subclass. Do not instantiate this class directly. 

45 """ 

46 self._vivado_path = get_vivado_path(vivado_path) 

47 self._libraries_path = (self._vivado_path.parent.parent / "data" / "vhdl" / "src").resolve() 

48 

49 self.output_path = output_path.resolve() / self._get_version_tag() 

50 

51 def compile_if_needed(self) -> bool: 

52 """ 

53 Compile if needed (if :meth:`compile_is_needed <.compile_is_needed>` condition is not 

54 fulfilled). 

55 """ 

56 if self.compile_is_needed: 

57 self.compile() 

58 return True 

59 

60 return False 

61 

62 @property 

63 def compile_is_needed(self) -> bool: 

64 """ 

65 If there is compiled simlib available that matches 

66 

67 * Operating system 

68 * Vivado version 

69 * Simulator version 

70 

71 then there should not be a recompile. 

72 

73 .. note:: 

74 Subclass implementations might add further conditions. 

75 

76 Return: 

77 True if compiled simlib is not available. False otherwise. 

78 """ 

79 if self._done_token.exists(): 

80 return False 

81 

82 return True 

83 

84 def compile(self) -> None: 

85 """ 

86 Compile simlib. 

87 """ 

88 # Delete any existing artifacts, which might be fully or partially compiled. 

89 # This also deletes the "done" token file if it exists. 

90 # Specifically GHDL compilation fails if there are existing compiled artifacts 

91 delete(self.output_path) 

92 

93 print(f"Compiling Vivado simlib from {self._libraries_path} into {self.output_path}...") 

94 self._compile() 

95 

96 create_file(self._done_token, "Done!") 

97 

98 @abstractmethod 

99 def _compile(self) -> None: 

100 """ 

101 Compile simlib. 

102 Overload in a subclass. 

103 """ 

104 

105 def add_to_vunit_project(self) -> None: 

106 """ 

107 Add the compiled simlib to your VUnit project. 

108 """ 

109 self._add_to_vunit_project() 

110 

111 @abstractmethod 

112 def _add_to_vunit_project(self) -> None: 

113 """ 

114 Add the compiled simlib to your VUnit project. 

115 Overload in a subclass. 

116 """ 

117 

118 @property 

119 def artifact_name(self) -> str: 

120 """ 

121 The name of the folder where simlib is or will be compiled. 

122 Follows a format ``vivado-simlib-WW.XX.YY.ZZ`` suitable for storage and versioning 

123 in Artifactory. 

124 """ 

125 return self.output_path.name 

126 

127 def to_archive(self) -> Path: 

128 """ 

129 Compress compiled simlib to an archive. 

130 

131 Return: 

132 Path to the archive. 

133 """ 

134 make_archive(str(self.output_path), "zip", self.output_path) 

135 archive = self.output_path.parent / (self.output_path.name + ".zip") 

136 

137 return archive 

138 

139 def from_archive(self, archive: Path) -> None: 

140 """ 

141 Unpack compiled simlib from an existing archive. 

142 

143 Arguments: 

144 archive: Path to a zip archive with previously compiled simlib. 

145 """ 

146 with zipfile.ZipFile(archive, "r") as zip_handle: 

147 zip_handle.extractall(self.output_path) 

148 

149 def _get_version_tag(self) -> str: 

150 tag = "vivado-simlib-" 

151 tag += self._get_operating_system_tag() 

152 tag += f".{self._get_vivado_version_tag()}" 

153 tag += f".{self._get_simulator_tag()}" 

154 tag += f".format_{self._format_version_id}" 

155 

156 return tag 

157 

158 def _get_operating_system_tag(self) -> str: 

159 """ 

160 Return e.g. "linux". 

161 """ 

162 return self._format_version(platform.system()) 

163 

164 def _get_vivado_version_tag(self) -> str: 

165 """ 

166 Return e.g. "vivado_2021_2". 

167 """ 

168 vivado_version = get_vivado_version(self._vivado_path) 

169 

170 return self._format_version(f"vivado_{vivado_version}") 

171 

172 @abstractmethod 

173 def _get_simulator_tag(self) -> str: 

174 """ 

175 Return a simulator tag as a string, e.g. "ghdl_1.2.3". 

176 Overload in a subclass. 

177 """ 

178 

179 @staticmethod 

180 def _format_version(version: str) -> str: 

181 """ 

182 Format version string to something suitable for artifactory versioning. 

183 """ 

184 return version.replace(".", "_").replace("-", "_").lower() 

185 

186 @property 

187 def _done_token(self) -> Path: 

188 """ 

189 Path to "done" token file. 

190 """ 

191 return self.output_path / "done.txt"