Coverage for tsfpga/vivado/ip_cores.py: 98%
55 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project.
5# https://tsfpga.com
6# https://gitlab.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9import hashlib
10import json
12from tsfpga.system_utils import create_file, delete, read_file
13from .project import VivadoIpCoreProject
16class VivadoIpCores:
18 """
19 Handle a list of IP core sources. Has a mechanism to detect whether a regenerate of IP files
20 is needed.
21 """
23 project_name = "vivado_ip_project"
25 def __init__(self, modules, output_path, part_name, vivado_project_class=None):
26 """
27 Arguments:
28 modules (list(BaseModule)): IP cores from these modules will be included.
29 output_path (pathlib.Path): The Vivado project will be placed here.
30 part_name (str): Vivado part name to be used for the project.
31 vivado_project_class: The Vivado project class that will be used for the IP core
32 project. Is safe to leave at default in most cases.
33 """
34 self.project_directory = output_path.resolve() / self.project_name
35 self._part_name = part_name
37 vivado_project_class = (
38 VivadoIpCoreProject if vivado_project_class is None else vivado_project_class
39 )
41 self._hash_file = self.project_directory / "ip_files_hash.txt"
43 self._setup(modules, vivado_project_class)
45 @property
46 def compile_order_file(self):
47 """
48 pathlib.Path: Path to the generated compile order file.
49 """
50 return self.project_directory / "compile_order.txt"
52 @property
53 def vivado_project_file(self):
54 """
55 pathlib.Path: Path to the Vivado project file.
56 """
57 return self._vivado_project.project_file(self.project_directory)
59 def create_vivado_project(self):
60 """
61 Create IP core Vivado project.
62 """
63 print(f"Creating IP core project in {self.project_directory}")
64 delete(self.project_directory)
65 self._vivado_project.create(self.project_directory)
66 self._save_hash()
68 def create_vivado_project_if_needed(self):
69 """
70 Create IP core Vivado project if anything has changed since last time this was run.
71 If
73 * List of TCL files that create IP cores,
74 * and contents of these files,
76 is the same then it will not create. But if anything is added or removed from the list,
77 or the contents of a TCL file is changed, there will be a recreation.
79 Return:
80 True if Vivado project was created. False otherwise.
81 """
82 if self._should_create():
83 self.create_vivado_project()
84 return True
86 return False
88 def _setup(self, modules, vivado_project_class):
89 self._vivado_project = vivado_project_class(
90 name=self.project_name, modules=modules, part=self._part_name
91 )
93 ip_core_files = []
94 for module in modules:
95 # Send the same two arguments that are sent in the VivadoProject create flow
96 ip_core_files += module.get_ip_core_files(generics={}, part=self._part_name)
98 self._hash = self._calculate_hash(ip_core_files)
100 @staticmethod
101 def _calculate_hash(ip_core_files):
102 """
103 A string with hashes of the different IP core files.
104 """
105 data = ""
107 def sort_by_file_name(ip_core_file):
108 return ip_core_file.path.name
110 for ip_core_file in sorted(ip_core_files, key=sort_by_file_name):
111 data += f"{ip_core_file.path}\n"
113 if ip_core_file.variables:
114 data += json.dumps(ip_core_file.variables, sort_keys=True)
115 data += "\n"
117 with open(ip_core_file.path, "rb") as file_handle:
118 ip_hash = hashlib.md5()
119 ip_hash.update(file_handle.read())
120 data += f"{ip_hash.hexdigest()}\n"
122 return data
124 def _save_hash(self):
125 create_file(self._hash_file, self._hash)
127 def _should_create(self):
128 """
129 Return True if a Vivado project create is needed, i.e. if anything has changed.
130 """
131 if not (self._hash_file.exists() and self.compile_order_file.exists()):
132 return True
134 return read_file(self._hash_file) != self._hash