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
« 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# --------------------------------------------------------------------------------------------------
9from abc import ABC, abstractmethod
11from .build_result import BuildResult
14class Limit(ABC):
15 """
16 Base class for limit checks.
17 Inherit and implement the check in subclass.
18 """
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
27 @abstractmethod
28 def check(self, result_value: float) -> bool:
29 pass
31 @abstractmethod
32 def __str__(self) -> str:
33 pass
36class LessThan(Limit):
37 """
38 Limit to be used with a checker to see that a figure is less than the specified value.
39 """
41 def check(self, result_value: float) -> bool:
42 return result_value < self.value
44 def __str__(self) -> str:
45 return f"< {self.value}"
48class EqualTo(Limit):
49 """
50 Limit to be used with a checker to see that a figure is equal to the specified value.
51 """
53 def check(self, result_value: float) -> bool:
54 return result_value == self.value
56 def __str__(self) -> str:
57 return str(self.value)
60class GreaterThan(Limit):
61 """
62 Limit to be used with a checker to see that a figure is greater than the specified value.
63 """
65 def check(self, result_value: float) -> bool:
66 return result_value > self.value
68 def __str__(self) -> str:
69 return f"> {self.value}"
72class BuildResultChecker(ABC):
73 """
74 Base class for build result checkers that check a certain build result value against a limit.
76 Overload in subclass and implement the ``check`` method according to the resource you want
77 to check.
78 """
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
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.
93 Return:
94 True if check passed, false otherwise.
95 """
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
102 print(f"Result check failed for {name}. Got {result_value}, expected {self.limit}.")
103 return False
106class MaximumLogicLevel(BuildResultChecker):
107 """
108 Check the maximum logic level of a build result against a limit.
109 """
111 name = "Maximum logic level"
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")
118 return self._check_result_value(name="maximum logic level", result_value=value)
121class SizeChecker(BuildResultChecker):
122 """
123 Check a build result size value against a limit.
125 Overload and set the correct ``name``, according to the names
126 in the vendor utilization report.
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 """
132 name: str
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")
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 )
143 return self._check_result_value(
144 name=self.name, result_value=build_result.synthesis_size[self.name]
145 )
148class TotalLuts(SizeChecker):
149 name = "Total LUTs"
152class LogicLuts(SizeChecker):
153 name = "Logic LUTs"
156class LutRams(SizeChecker):
157 name = "LUTRAMs"
160class Srls(SizeChecker):
161 name = "SRLs"
164class Ffs(SizeChecker):
165 name = "FFs"
168class Ramb36(SizeChecker):
169 name = "RAMB36"
172class Ramb18(SizeChecker):
173 name = "RAMB18"
176class Ramb(SizeChecker):
177 """
178 Combined checker for RAMB36 and RAMB18 count.
179 Each RAMB18 counts as half a RAMB36.
180 """
182 name = "RAMB"
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)
191 value = ramb36_value + ramb18_value / 2
193 return self._check_result_value(name=self.name, result_value=value)
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")
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 )
205 return build_result.synthesis_size[name]
208class Uram(SizeChecker):
209 name = "URAM"
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 """
218 name = "DSP Blocks"
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")
227 legacy_name = "DSP48 Blocks"
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]
234 return self._check_result_value(name=self.name, result_value=value)