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

71 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 

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_directory, create_file 

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 from vunit.sim_if import SimulatorInterface 

26 from vunit.ui import VUnit 

27 

28 

29class VivadoSimlibCommon(ABC): 

30 """ 

31 Class for handling Vivado simlib used for simulation. 

32 Keeps track of when a (re)compile is needed. 

33 

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

35 See subclasses for details: :class:`.VivadoSimlibOpenSource`, :class:`.VivadoSimlibCommercial`. 

36 

37 Do not instantiate this class directly. 

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

39 """ 

40 

41 # The version of this class. 

42 # Can be bumped to force a re-compile if e.g. the TCL script changes or the output folder 

43 # structure is updated. 

44 _format_version_id = 4 

45 

46 # Set in subclass to a list of strings. 

47 # The libraries that shall be compiled and added to VUnit project. 

48 library_names: list[str] 

49 

50 def __init__( 

51 self, 

52 vivado_path: Path | None, 

53 output_path: Path, 

54 vunit_proj: VUnit, 

55 simulator_interface: SimulatorInterface, # noqa: ARG002 

56 ) -> None: 

57 """ 

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

59 

60 Arguments: 

61 vivado_path: Path to Vivado executable. 

62 output_path: The compiled simlib will be placed here. 

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

64 simulator_interface: A VUnit SimulatorInterface object. 

65 """ 

66 self._vivado_path = get_vivado_path(vivado_path) 

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

68 

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

70 

71 self._vunit_proj = vunit_proj 

72 

73 def compile_if_needed(self) -> bool: 

74 """ 

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

76 fulfilled). 

77 """ 

78 if self.compile_is_needed: 

79 self.compile() 

80 return True 

81 

82 return False 

83 

84 @property 

85 def compile_is_needed(self) -> bool: 

86 """ 

87 If there is compiled simlib available that matches 

88 

89 * Operating system 

90 * Vivado version 

91 * Simulator version 

92 

93 then there should not be a recompile. 

94 

95 .. note:: 

96 Subclass implementations might add further conditions. 

97 

98 Return: 

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

100 """ 

101 return not self._done_token.exists() 

102 

103 def compile(self) -> None: 

104 """ 

105 Compile simlib. 

106 """ 

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

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

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

110 create_directory(self.output_path, empty=True) 

111 

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

113 self._compile() 

114 

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

116 

117 @abstractmethod 

118 def _compile(self) -> None: 

119 """ 

120 Compile simlib. 

121 Overload in a subclass. 

122 """ 

123 

124 def add_to_vunit_project(self) -> None: 

125 """ 

126 Add the compiled simlib to your VUnit project. 

127 """ 

128 for library_name in self.library_names: 

129 library_path = self.output_path / library_name 

130 if not library_path.exists(): 

131 raise FileNotFoundError(f"Library path does not exist: {library_path}") 

132 

133 self._vunit_proj.add_external_library(library_name=library_name, path=library_path) 

134 

135 @property 

136 def artifact_name(self) -> str: 

137 """ 

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

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

140 in Artifactory. 

141 """ 

142 return self.output_path.name 

143 

144 def to_archive(self) -> Path: 

145 """ 

146 Compress compiled simlib to an archive. 

147 

148 Return: 

149 Path to the archive. 

150 """ 

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

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

153 

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

155 """ 

156 Unpack compiled simlib from an existing archive. 

157 

158 Arguments: 

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

160 """ 

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

162 zip_handle.extractall(self.output_path) 

163 

164 def _get_version_tag(self) -> str: 

165 tag = "vivado-simlib-" 

166 tag += self._get_operating_system_tag() 

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

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

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

170 

171 return tag 

172 

173 def _get_operating_system_tag(self) -> str: 

174 """ 

175 Return e.g. "linux". 

176 """ 

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

178 

179 def _get_vivado_version_tag(self) -> str: 

180 """ 

181 Return e.g. "vivado_2021_2". 

182 """ 

183 vivado_version = get_vivado_version(self._vivado_path) 

184 

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

186 

187 @abstractmethod 

188 def _get_simulator_tag(self) -> str: 

189 """ 

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

191 Overload in a subclass. 

192 """ 

193 

194 @staticmethod 

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

196 """ 

197 Format version string to something suitable for artifactory versioning. 

198 """ 

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

200 

201 @property 

202 def _done_token(self) -> Path: 

203 """ 

204 Path to "done" token file. 

205 """ 

206 return self.output_path / "done.txt"