Coverage for tsfpga/vivado/ip_cores.py: 98%
56 statements
« prev ^ index » next coverage.py v7.2.1, created at 2023-09-27 20:00 +0000
« prev ^ index » next coverage.py v7.2.1, created at 2023-09-27 20:00 +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://gitlab.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9# Standard libraries
10import hashlib
11import json
13# First party libraries
14from tsfpga.system_utils import create_file, delete, read_file
16# Local folder libraries
17from .project import VivadoIpCoreProject
20class VivadoIpCores:
22 """
23 Handle a list of IP core sources. Has a mechanism to detect whether a regenerate of IP files
24 is needed.
25 """
27 project_name = "vivado_ip_project"
29 def __init__(self, modules, output_path, part_name, vivado_project_class=None):
30 """
31 Arguments:
32 modules (list(BaseModule)): IP cores from these modules will be included.
33 output_path (pathlib.Path): The Vivado project will be placed here.
34 part_name (str): Vivado part name to be used for the project.
35 vivado_project_class: The Vivado project class that will be used for the IP core
36 project. Is safe to leave at default in most cases.
37 """
38 self.project_directory = output_path.resolve() / self.project_name
39 self._part_name = part_name
41 vivado_project_class = (
42 VivadoIpCoreProject if vivado_project_class is None else vivado_project_class
43 )
45 self._hash_file = self.project_directory / "ip_files_hash.txt"
47 self._setup(modules, vivado_project_class)
49 @property
50 def compile_order_file(self):
51 """
52 pathlib.Path: Path to the generated compile order file.
53 """
54 return self.project_directory / "compile_order.txt"
56 @property
57 def vivado_project_file(self):
58 """
59 pathlib.Path: Path to the Vivado project file.
60 """
61 return self._vivado_project.project_file(self.project_directory)
63 def create_vivado_project(self):
64 """
65 Create IP core Vivado project.
66 """
67 print(f"Creating IP core project in {self.project_directory}")
68 delete(self.project_directory)
69 success = self._vivado_project.create(self.project_directory)
71 assert success, "Failed to create Vivado IP core project"
73 self._save_hash()
75 def create_vivado_project_if_needed(self):
76 """
77 Create IP core Vivado project if anything has changed since last time this was run.
78 If
80 * List of TCL files that create IP cores,
81 * and contents of these files,
83 is the same then it will not create. But if anything is added or removed from the list,
84 or the contents of a TCL file is changed, there will be a recreation.
86 Return:
87 True if Vivado project was created. False otherwise.
88 """
89 if self._should_create():
90 self.create_vivado_project()
91 return True
93 return False
95 def _setup(self, modules, vivado_project_class):
96 self._vivado_project = vivado_project_class(
97 name=self.project_name, modules=modules, part=self._part_name
98 )
100 ip_core_files = []
101 for module in modules:
102 # Send the same two arguments that are sent in the VivadoProject create flow
103 ip_core_files += module.get_ip_core_files(generics={}, part=self._part_name)
105 self._hash = self._calculate_hash(ip_core_files)
107 @staticmethod
108 def _calculate_hash(ip_core_files):
109 """
110 A string with hashes of the different IP core files.
111 """
112 data = ""
114 def sort_by_file_name(ip_core_file):
115 return ip_core_file.path.name
117 for ip_core_file in sorted(ip_core_files, key=sort_by_file_name):
118 data += f"{ip_core_file.path}\n"
120 if ip_core_file.variables:
121 data += json.dumps(ip_core_file.variables, sort_keys=True)
122 data += "\n"
124 with open(ip_core_file.path, "rb") as file_handle:
125 ip_hash = hashlib.md5()
126 ip_hash.update(file_handle.read())
127 data += f"{ip_hash.hexdigest()}\n"
129 return data
131 def _save_hash(self):
132 create_file(self._hash_file, self._hash)
134 def _should_create(self):
135 """
136 Return True if a Vivado project create is needed, i.e. if anything has changed.
137 """
138 if not (self._hash_file.exists() and self.compile_order_file.exists()):
139 return True
141 return read_file(self._hash_file) != self._hash