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

92 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-21 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 

9from abc import ABC, abstractmethod 

10 

11from .build_result import BuildResult 

12 

13 

14class Limit(ABC): 

15 """ 

16 Base class for limit checks. 

17 Inherit and implement the check in subclass. 

18 """ 

19 

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

21 """ 

22 Arguments: 

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

24 """ 

25 self.value = value 

26 

27 @abstractmethod 

28 def check(self, result_value: float) -> bool: 

29 pass 

30 

31 @abstractmethod 

32 def __str__(self) -> str: 

33 pass 

34 

35 

36class LessThan(Limit): 

37 """ 

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

39 """ 

40 

41 def check(self, result_value: float) -> bool: 

42 return result_value < self.value 

43 

44 def __str__(self) -> str: 

45 return f"< {self.value}" 

46 

47 

48class EqualTo(Limit): 

49 """ 

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

51 """ 

52 

53 def check(self, result_value: float) -> bool: 

54 return result_value == self.value 

55 

56 def __str__(self) -> str: 

57 return str(self.value) 

58 

59 

60class GreaterThan(Limit): 

61 """ 

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

63 """ 

64 

65 def check(self, result_value: float) -> bool: 

66 return result_value > self.value 

67 

68 def __str__(self) -> str: 

69 return f"> {self.value}" 

70 

71 

72class BuildResultChecker(ABC): 

73 """ 

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

75 

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

77 to check. 

78 """ 

79 

80 def __init__(self, limit: Limit) -> None: 

81 """ 

82 Arguments: 

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

84 """ 

85 self.limit = limit 

86 

87 @abstractmethod 

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

89 """ 

90 Arguments: 

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

92 

93 Return: 

94 True if check passed, false otherwise. 

95 """ 

96 

97 def _check_result_value(self, name: str, result_value: float) -> bool: 

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

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

100 return True 

101 

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

103 return False 

104 

105 

106class MaximumLogicLevel(BuildResultChecker): 

107 """ 

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

109 """ 

110 

111 name = "Maximum logic level" 

112 

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

114 value = build_result.maximum_logic_level 

115 if value is None: 

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

117 

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

119 

120 

121class SizeChecker(BuildResultChecker): 

122 """ 

123 Check a build result size value against a limit. 

124 

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

126 in the vendor utilization report. 

127 

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

129 the implemented one, even if available. 

130 """ 

131 

132 name: str 

133 

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

135 if build_result.synthesis_size is None: 

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

137 

138 if self.name not in build_result.synthesis_size: 

139 raise ValueError( 

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

141 ) 

142 

143 return self._check_result_value( 

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

145 ) 

146 

147 

148class TotalLuts(SizeChecker): 

149 name = "Total LUTs" 

150 

151 

152class LogicLuts(SizeChecker): 

153 name = "Logic LUTs" 

154 

155 

156class LutRams(SizeChecker): 

157 name = "LUTRAMs" 

158 

159 

160class Srls(SizeChecker): 

161 name = "SRLs" 

162 

163 

164class Ffs(SizeChecker): 

165 name = "FFs" 

166 

167 

168class Ramb36(SizeChecker): 

169 name = "RAMB36" 

170 

171 

172class Ramb18(SizeChecker): 

173 name = "RAMB18" 

174 

175 

176class Ramb(SizeChecker): 

177 """ 

178 Combined checker for RAMB36 and RAMB18 count. 

179 Each RAMB18 counts as half a RAMB36. 

180 """ 

181 

182 name = "RAMB" 

183 

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

185 """ 

186 Similar to super class, but takes out two result values. 

187 """ 

188 ramb36_value = self._get_result_value(build_result=build_result, name=Ramb36.name) 

189 ramb18_value = self._get_result_value(build_result=build_result, name=Ramb18.name) 

190 

191 value = ramb36_value + ramb18_value / 2 

192 

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

194 

195 @staticmethod 

196 def _get_result_value(build_result: BuildResult, name: str) -> int: 

197 if build_result.synthesis_size is None: 

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

199 

200 if name not in build_result.synthesis_size: 

201 raise ValueError( 

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

203 ) 

204 

205 return build_result.synthesis_size[name] 

206 

207 

208class Uram(SizeChecker): 

209 name = "URAM" 

210 

211 

212class DspBlocks(SizeChecker): 

213 """ 

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

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

216 """ 

217 

218 name = "DSP Blocks" 

219 

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

221 """ 

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

223 """ 

224 if build_result.synthesis_size is None: 

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

226 

227 legacy_name = "DSP48 Blocks" 

228 

229 if legacy_name in build_result.synthesis_size: 

230 value = build_result.synthesis_size[legacy_name] 

231 else: 

232 value = build_result.synthesis_size[self.name] 

233 

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