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

93 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 20:52 +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 

11from typing import Union 

12 

13# Local folder libraries 

14from .build_result import BuildResult 

15 

16 

17class Limit(ABC): 

18 """ 

19 Base class for limit checks. 

20 Inherit and implement the check in subclass. 

21 """ 

22 

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

24 """ 

25 Arguments: 

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

27 """ 

28 self.value = value 

29 

30 @abstractmethod 

31 def check(self, result_value: Union[int, float]) -> bool: 

32 pass 

33 

34 @abstractmethod 

35 def __str__(self) -> str: 

36 pass 

37 

38 

39class LessThan(Limit): 

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: Union[int, float]) -> 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 Limit to be used with a checker to see that a figure is equal to the specified value. 

54 """ 

55 

56 def check(self, result_value: Union[int, float]) -> bool: 

57 return result_value == self.value 

58 

59 def __str__(self) -> str: 

60 return str(self.value) 

61 

62 

63class GreaterThan(Limit): 

64 """ 

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

66 """ 

67 

68 def check(self, result_value: Union[int, float]) -> bool: 

69 return result_value > self.value 

70 

71 def __str__(self) -> str: 

72 return f"> {self.value}" 

73 

74 

75class BuildResultChecker(ABC): 

76 """ 

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

78 

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

80 to check. 

81 """ 

82 

83 def __init__(self, limit: Limit): 

84 """ 

85 Arguments: 

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

87 """ 

88 self.limit = limit 

89 

90 @abstractmethod 

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

92 """ 

93 Arguments: 

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

95 

96 Return: 

97 True if check passed, false otherwise. 

98 """ 

99 

100 def _check_result_value(self, name: str, result_value: Union[int, float]) -> bool: 

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

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

103 return True 

104 

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

106 return False 

107 

108 

109class MaximumLogicLevel(BuildResultChecker): 

110 """ 

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

112 """ 

113 

114 name = "Maximum logic level" 

115 

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

117 value = build_result.maximum_logic_level 

118 if value is None: 

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

120 

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

122 

123 

124class SizeChecker(BuildResultChecker): 

125 """ 

126 Check a build result size value against a limit. 

127 

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

129 in the vendor utilization report. 

130 

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

132 the implemented one, even if available. 

133 """ 

134 

135 name: str 

136 

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

138 if build_result.synthesis_size is None: 

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

140 

141 if self.name not in build_result.synthesis_size: 

142 raise ValueError( 

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

144 ) 

145 

146 return self._check_result_value( 

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

148 ) 

149 

150 

151class TotalLuts(SizeChecker): 

152 name = "Total LUTs" 

153 

154 

155class LogicLuts(SizeChecker): 

156 name = "Logic LUTs" 

157 

158 

159class LutRams(SizeChecker): 

160 name = "LUTRAMs" 

161 

162 

163class Srls(SizeChecker): 

164 name = "SRLs" 

165 

166 

167class Ffs(SizeChecker): 

168 name = "FFs" 

169 

170 

171class Ramb36(SizeChecker): 

172 name = "RAMB36" 

173 

174 

175class Ramb18(SizeChecker): 

176 name = "RAMB18" 

177 

178 

179class Ramb(SizeChecker): 

180 """ 

181 Combined checker for RAMB36 and RAMB18 count. 

182 Each RAMB18 counts as half a RAMB36. 

183 """ 

184 

185 name = "RAMB" 

186 

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

188 """ 

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

190 """ 

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

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

193 

194 value = ramb36_value + ramb18_value / 2 

195 

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

197 

198 @staticmethod 

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

200 if build_result.synthesis_size is None: 

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

202 

203 if name not in build_result.synthesis_size: 

204 raise ValueError( 

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

206 ) 

207 

208 return build_result.synthesis_size[name] 

209 

210 

211class Uram(SizeChecker): 

212 name = "URAM" 

213 

214 

215class DspBlocks(SizeChecker): 

216 """ 

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

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

219 """ 

220 

221 name = "DSP Blocks" 

222 

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

224 """ 

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

226 """ 

227 if build_result.synthesis_size is None: 

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

229 

230 legacy_name = "DSP48 Blocks" 

231 

232 if legacy_name in build_result.synthesis_size: 

233 value = build_result.synthesis_size[legacy_name] 

234 else: 

235 value = build_result.synthesis_size[self.name] 

236 

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