Coverage for tsfpga/vivado/build_result_checker.py: 97%

72 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-10 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 

9# Standard libraries 

10from abc import ABC, abstractmethod 

11 

12# Local folder libraries 

13from .build_result import BuildResult 

14 

15 

16class Limit(ABC): 

17 """ 

18 Base class for limit checks. 

19 Inherit and implement the check in subclass. 

20 """ 

21 

22 def __init__(self, value: int) -> None: 

23 """ 

24 Arguments: 

25 value: The result value shall be compared with this number. 

26 """ 

27 self.value = value 

28 

29 @abstractmethod 

30 def check(self, result_value: int) -> bool: 

31 pass 

32 

33 @abstractmethod 

34 def __str__(self) -> str: 

35 pass 

36 

37 

38class LessThan(Limit): 

39 

40 """ 

41 Limit to be used with a checker to see that a figure is less than the specified value. 

42 """ 

43 

44 def check(self, result_value: int) -> bool: 

45 return result_value < self.value 

46 

47 def __str__(self) -> str: 

48 return f"< {self.value}" 

49 

50 

51class EqualTo(Limit): 

52 

53 """ 

54 Limit to be used with a checker to see that a figure is equal to the specified value. 

55 """ 

56 

57 def check(self, result_value: int) -> bool: 

58 return result_value == self.value 

59 

60 def __str__(self) -> str: 

61 return str(self.value) 

62 

63 

64class BuildResultChecker(ABC): 

65 

66 """ 

67 Base class for build result checkers that check a certain build result value against a limit. 

68 

69 Overload in subclass and implement the ``check`` method according to the resource you want 

70 to check. 

71 """ 

72 

73 def __init__(self, limit: Limit): 

74 """ 

75 Arguments: 

76 limit: The limit that the specified resource shall be checked against. 

77 """ 

78 self.limit = limit 

79 

80 @abstractmethod 

81 def check(self, build_result: BuildResult) -> bool: 

82 """ 

83 Arguments: 

84 build_result: Build result that shall be checked. Should come from a successful build. 

85 

86 Return: 

87 True if check passed, false otherwise. 

88 """ 

89 

90 def _check_result_value(self, name: str, result_value: int) -> bool: 

91 if result_value is not None and self.limit.check(result_value=result_value): 

92 print(f"Result check passed for {name}: {result_value} ({self.limit})") 

93 return True 

94 

95 print(f"Result check failed for {name}. Got {result_value}, expected {self.limit}.") 

96 return False 

97 

98 

99class MaximumLogicLevel(BuildResultChecker): 

100 

101 """ 

102 Check the maximum logic level of a build result against a limit. 

103 """ 

104 

105 name = "Maximum logic level" 

106 

107 def check(self, build_result: BuildResult) -> bool: 

108 value = build_result.maximum_logic_level 

109 if value is None: 

110 raise ValueError("Should only call after successful synthesis") 

111 

112 return self._check_result_value(name="maximum logic level", result_value=value) 

113 

114 

115class SizeChecker(BuildResultChecker): 

116 

117 """ 

118 Check a build result size value against a limit. 

119 

120 Overload and set the correct ``name``, according to the names 

121 in the vendor utilization report. 

122 

123 Note that since this is to be used by netlist builds it checks the synthesized size, not 

124 the implemented one, even if available. 

125 """ 

126 

127 name: str 

128 

129 def check(self, build_result: BuildResult) -> bool: 

130 if build_result.synthesis_size is None: 

131 raise ValueError("Should only call after successful synthesis") 

132 

133 if self.name not in build_result.synthesis_size: 

134 raise ValueError( 

135 f'Synthesis result size does not contain the requested resource: "{self.name}"' 

136 ) 

137 

138 return self._check_result_value( 

139 name=self.name, result_value=build_result.synthesis_size[self.name] 

140 ) 

141 

142 

143class TotalLuts(SizeChecker): 

144 name = "Total LUTs" 

145 

146 

147class LogicLuts(SizeChecker): 

148 name = "Logic LUTs" 

149 

150 

151class LutRams(SizeChecker): 

152 name = "LUTRAMs" 

153 

154 

155class Srls(SizeChecker): 

156 name = "SRLs" 

157 

158 

159class Ffs(SizeChecker): 

160 name = "FFs" 

161 

162 

163class Ramb36(SizeChecker): 

164 name = "RAMB36" 

165 

166 

167class Ramb18(SizeChecker): 

168 name = "RAMB18" 

169 

170 

171class Uram(SizeChecker): 

172 name = "URAM" 

173 

174 

175class DspBlocks(SizeChecker): 

176 

177 """ 

178 In Vivado pre-2020.1 the resource was called "DSP48 Blocks" in the utilization report. 

179 After that it is called "DSP Blocks". This class checks for both. 

180 """ 

181 

182 name = "DSP Blocks" 

183 

184 def check(self, build_result: BuildResult) -> bool: 

185 """ 

186 Same as super class, but checks for the legacy name as well as the current name. 

187 """ 

188 assert ( 

189 build_result.synthesis_size is not None 

190 ), "Should only call after successful synthesis" 

191 

192 legacy_name = "DSP48 Blocks" 

193 

194 if legacy_name in build_result.synthesis_size: 

195 value = build_result.synthesis_size[legacy_name] 

196 else: 

197 value = build_result.synthesis_size[self.name] 

198 

199 return self._check_result_value(name=self.name, result_value=value)