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

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# -------------------------------------------------------------------------------------------------- 

8 

9# Standard libraries 

10import hashlib 

11import json 

12 

13# First party libraries 

14from tsfpga.system_utils import create_file, delete, read_file 

15 

16# Local folder libraries 

17from .project import VivadoIpCoreProject 

18 

19 

20class VivadoIpCores: 

21 

22 """ 

23 Handle a list of IP core sources. Has a mechanism to detect whether a regenerate of IP files 

24 is needed. 

25 """ 

26 

27 project_name = "vivado_ip_project" 

28 

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 

40 

41 vivado_project_class = ( 

42 VivadoIpCoreProject if vivado_project_class is None else vivado_project_class 

43 ) 

44 

45 self._hash_file = self.project_directory / "ip_files_hash.txt" 

46 

47 self._setup(modules, vivado_project_class) 

48 

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" 

55 

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) 

62 

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) 

70 

71 assert success, "Failed to create Vivado IP core project" 

72 

73 self._save_hash() 

74 

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 

79 

80 * List of TCL files that create IP cores, 

81 * and contents of these files, 

82 

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. 

85 

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 

92 

93 return False 

94 

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 ) 

99 

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) 

104 

105 self._hash = self._calculate_hash(ip_core_files) 

106 

107 @staticmethod 

108 def _calculate_hash(ip_core_files): 

109 """ 

110 A string with hashes of the different IP core files. 

111 """ 

112 data = "" 

113 

114 def sort_by_file_name(ip_core_file): 

115 return ip_core_file.path.name 

116 

117 for ip_core_file in sorted(ip_core_files, key=sort_by_file_name): 

118 data += f"{ip_core_file.path}\n" 

119 

120 if ip_core_file.variables: 

121 data += json.dumps(ip_core_file.variables, sort_keys=True) 

122 data += "\n" 

123 

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" 

128 

129 return data 

130 

131 def _save_hash(self): 

132 create_file(self._hash_file, self._hash) 

133 

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 

140 

141 return read_file(self._hash_file) != self._hash