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
« 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# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import platform
12import zipfile
13from abc import ABC, abstractmethod
14from shutil import make_archive
15from typing import TYPE_CHECKING
17from tsfpga.system_utils import create_directory, create_file
18from tsfpga.vivado.common import get_vivado_version
20from .common import get_vivado_path
22if TYPE_CHECKING:
23 from pathlib import Path
25 from vunit.sim_if import SimulatorInterface
26 from vunit.ui import VUnit
29class VivadoSimlibCommon(ABC):
30 """
31 Class for handling Vivado simlib used for simulation.
32 Keeps track of when a (re)compile is needed.
34 This is a base class that defines an interface and some common methods.
35 See subclasses for details: :class:`.VivadoSimlibOpenSource`, :class:`.VivadoSimlibCommercial`.
37 Do not instantiate this class directly.
38 Use factory class :class:`.VivadoSimlib` instead.
39 """
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
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]
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.
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()
69 self.output_path = output_path.resolve() / self._get_version_tag()
71 self._vunit_proj = vunit_proj
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
82 return False
84 @property
85 def compile_is_needed(self) -> bool:
86 """
87 If there is compiled simlib available that matches
89 * Operating system
90 * Vivado version
91 * Simulator version
93 then there should not be a recompile.
95 .. note::
96 Subclass implementations might add further conditions.
98 Return:
99 True if compiled simlib is not available. False otherwise.
100 """
101 return not self._done_token.exists()
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)
112 print(f"Compiling Vivado simlib from {self._libraries_path} into {self.output_path}...")
113 self._compile()
115 create_file(self._done_token, "Done!")
117 @abstractmethod
118 def _compile(self) -> None:
119 """
120 Compile simlib.
121 Overload in a subclass.
122 """
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}")
133 self._vunit_proj.add_external_library(library_name=library_name, path=library_path)
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
144 def to_archive(self) -> Path:
145 """
146 Compress compiled simlib to an archive.
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")
154 def from_archive(self, archive: Path) -> None:
155 """
156 Unpack compiled simlib from an existing archive.
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)
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}"
171 return tag
173 def _get_operating_system_tag(self) -> str:
174 """
175 Return e.g. "linux".
176 """
177 return self._format_version(platform.system())
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)
185 return self._format_version(f"vivado_{vivado_version}")
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 """
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()
201 @property
202 def _done_token(self) -> Path:
203 """
204 Path to "done" token file.
205 """
206 return self.output_path / "done.txt"