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

66 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-01 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 

11import platform 

12import zipfile 

13from abc import ABC, abstractmethod 

14from shutil import make_archive 

15from typing import TYPE_CHECKING 

16 

17from tsfpga.system_utils import create_file, delete 

18from tsfpga.vivado.common import get_vivado_version 

19 

20from .common import get_vivado_path 

21 

22if TYPE_CHECKING: 

23 from pathlib import Path 

24 

25 

26class VivadoSimlibCommon(ABC): 

27 """ 

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

29 (re)compile is needed. 

30 

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

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

33 """ 

34 

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

36 # or the output folder structure is updated. 

37 _format_version_id = 4 

38 

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

40 # VUnit project. 

41 library_names: list[str] 

42 

43 def __init__(self, vivado_path: Path | None, output_path: Path) -> None: 

44 """ 

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

46 """ 

47 self._vivado_path = get_vivado_path(vivado_path) 

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

49 

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

51 

52 def compile_if_needed(self) -> bool: 

53 """ 

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

55 fulfilled). 

56 """ 

57 if self.compile_is_needed: 

58 self.compile() 

59 return True 

60 

61 return False 

62 

63 @property 

64 def compile_is_needed(self) -> bool: 

65 """ 

66 If there is compiled simlib available that matches 

67 

68 * Operating system 

69 * Vivado version 

70 * Simulator version 

71 

72 then there should not be a recompile. 

73 

74 .. note:: 

75 Subclass implementations might add further conditions. 

76 

77 Return: 

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

79 """ 

80 return not self._done_token.exists() 

81 

82 def compile(self) -> None: 

83 """ 

84 Compile simlib. 

85 """ 

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

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

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

89 delete(self.output_path) 

90 

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

92 self._compile() 

93 

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

95 

96 @abstractmethod 

97 def _compile(self) -> None: 

98 """ 

99 Compile simlib. 

100 Overload in a subclass. 

101 """ 

102 

103 def add_to_vunit_project(self) -> None: 

104 """ 

105 Add the compiled simlib to your VUnit project. 

106 """ 

107 self._add_to_vunit_project() 

108 

109 @abstractmethod 

110 def _add_to_vunit_project(self) -> None: 

111 """ 

112 Add the compiled simlib to your VUnit project. 

113 Overload in a subclass. 

114 """ 

115 

116 @property 

117 def artifact_name(self) -> str: 

118 """ 

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

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

121 in Artifactory. 

122 """ 

123 return self.output_path.name 

124 

125 def to_archive(self) -> Path: 

126 """ 

127 Compress compiled simlib to an archive. 

128 

129 Return: 

130 Path to the archive. 

131 """ 

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

133 return self.output_path.parent / (self.output_path.name + ".zip") 

134 

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

136 """ 

137 Unpack compiled simlib from an existing archive. 

138 

139 Arguments: 

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

141 """ 

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

143 zip_handle.extractall(self.output_path) 

144 

145 def _get_version_tag(self) -> str: 

146 tag = "vivado-simlib-" 

147 tag += self._get_operating_system_tag() 

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

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

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

151 

152 return tag 

153 

154 def _get_operating_system_tag(self) -> str: 

155 """ 

156 Return e.g. "linux". 

157 """ 

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

159 

160 def _get_vivado_version_tag(self) -> str: 

161 """ 

162 Return e.g. "vivado_2021_2". 

163 """ 

164 vivado_version = get_vivado_version(self._vivado_path) 

165 

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

167 

168 @abstractmethod 

169 def _get_simulator_tag(self) -> str: 

170 """ 

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

172 Overload in a subclass. 

173 """ 

174 

175 @staticmethod 

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

177 """ 

178 Format version string to something suitable for artifactory versioning. 

179 """ 

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

181 

182 @property 

183 def _done_token(self) -> Path: 

184 """ 

185 Path to "done" token file. 

186 """ 

187 return self.output_path / "done.txt"