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

88 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-07 11:31 +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 BuildResultChecker(ABC): 

64 """ 

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

66 

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

68 to check. 

69 """ 

70 

71 def __init__(self, limit: Limit): 

72 """ 

73 Arguments: 

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

75 """ 

76 self.limit = limit 

77 

78 @abstractmethod 

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

80 """ 

81 Arguments: 

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

83 

84 Return: 

85 True if check passed, false otherwise. 

86 """ 

87 

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

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

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

91 return True 

92 

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

94 return False 

95 

96 

97class MaximumLogicLevel(BuildResultChecker): 

98 """ 

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

100 """ 

101 

102 name = "Maximum logic level" 

103 

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

105 value = build_result.maximum_logic_level 

106 if value is None: 

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

108 

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

110 

111 

112class SizeChecker(BuildResultChecker): 

113 """ 

114 Check a build result size value against a limit. 

115 

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

117 in the vendor utilization report. 

118 

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

120 the implemented one, even if available. 

121 """ 

122 

123 name: str 

124 

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

126 if build_result.synthesis_size is None: 

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

128 

129 if self.name not in build_result.synthesis_size: 

130 raise ValueError( 

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

132 ) 

133 

134 return self._check_result_value( 

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

136 ) 

137 

138 

139class TotalLuts(SizeChecker): 

140 name = "Total LUTs" 

141 

142 

143class LogicLuts(SizeChecker): 

144 name = "Logic LUTs" 

145 

146 

147class LutRams(SizeChecker): 

148 name = "LUTRAMs" 

149 

150 

151class Srls(SizeChecker): 

152 name = "SRLs" 

153 

154 

155class Ffs(SizeChecker): 

156 name = "FFs" 

157 

158 

159class Ramb36(SizeChecker): 

160 name = "RAMB36" 

161 

162 

163class Ramb18(SizeChecker): 

164 name = "RAMB18" 

165 

166 

167class Ramb(SizeChecker): 

168 """ 

169 Combined checker for RAMB36 and RAMB18 count. 

170 Each RAMB18 counts as half a RAMB36. 

171 """ 

172 

173 name = "RAMB" 

174 

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

176 """ 

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

178 """ 

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

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

181 

182 value = ramb36_value + ramb18_value / 2 

183 

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

185 

186 @staticmethod 

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

188 if build_result.synthesis_size is None: 

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

190 

191 if name not in build_result.synthesis_size: 

192 raise ValueError( 

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

194 ) 

195 

196 return build_result.synthesis_size[name] 

197 

198 

199class Uram(SizeChecker): 

200 name = "URAM" 

201 

202 

203class DspBlocks(SizeChecker): 

204 """ 

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

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

207 """ 

208 

209 name = "DSP Blocks" 

210 

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

212 """ 

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

214 """ 

215 if build_result.synthesis_size is None: 

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

217 

218 legacy_name = "DSP48 Blocks" 

219 

220 if legacy_name in build_result.synthesis_size: 

221 value = build_result.synthesis_size[legacy_name] 

222 else: 

223 value = build_result.synthesis_size[self.name] 

224 

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