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
« 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# --------------------------------------------------------------------------------------------------
9# Standard libraries
10from abc import ABC, abstractmethod
11from typing import Union
13# Local folder libraries
14from .build_result import BuildResult
17class Limit(ABC):
18 """
19 Base class for limit checks.
20 Inherit and implement the check in subclass.
21 """
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
30 @abstractmethod
31 def check(self, result_value: Union[int, float]) -> bool:
32 pass
34 @abstractmethod
35 def __str__(self) -> str:
36 pass
39class LessThan(Limit):
40 """
41 Limit to be used with a checker to see that a figure is less than the specified value.
42 """
44 def check(self, result_value: Union[int, float]) -> bool:
45 return result_value < self.value
47 def __str__(self) -> str:
48 return f"< {self.value}"
51class EqualTo(Limit):
52 """
53 Limit to be used with a checker to see that a figure is equal to the specified value.
54 """
56 def check(self, result_value: Union[int, float]) -> bool:
57 return result_value == self.value
59 def __str__(self) -> str:
60 return str(self.value)
63class GreaterThan(Limit):
64 """
65 Limit to be used with a checker to see that a figure is greater than the specified value.
66 """
68 def check(self, result_value: Union[int, float]) -> bool:
69 return result_value > self.value
71 def __str__(self) -> str:
72 return f"> {self.value}"
75class BuildResultChecker(ABC):
76 """
77 Base class for build result checkers that check a certain build result value against a limit.
79 Overload in subclass and implement the ``check`` method according to the resource you want
80 to check.
81 """
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
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.
96 Return:
97 True if check passed, false otherwise.
98 """
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
105 print(f"Result check failed for {name}. Got {result_value}, expected {self.limit}.")
106 return False
109class MaximumLogicLevel(BuildResultChecker):
110 """
111 Check the maximum logic level of a build result against a limit.
112 """
114 name = "Maximum logic level"
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")
121 return self._check_result_value(name="maximum logic level", result_value=value)
124class SizeChecker(BuildResultChecker):
125 """
126 Check a build result size value against a limit.
128 Overload and set the correct ``name``, according to the names
129 in the vendor utilization report.
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 """
135 name: str
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")
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 )
146 return self._check_result_value(
147 name=self.name, result_value=build_result.synthesis_size[self.name]
148 )
151class TotalLuts(SizeChecker):
152 name = "Total LUTs"
155class LogicLuts(SizeChecker):
156 name = "Logic LUTs"
159class LutRams(SizeChecker):
160 name = "LUTRAMs"
163class Srls(SizeChecker):
164 name = "SRLs"
167class Ffs(SizeChecker):
168 name = "FFs"
171class Ramb36(SizeChecker):
172 name = "RAMB36"
175class Ramb18(SizeChecker):
176 name = "RAMB18"
179class Ramb(SizeChecker):
180 """
181 Combined checker for RAMB36 and RAMB18 count.
182 Each RAMB18 counts as half a RAMB36.
183 """
185 name = "RAMB"
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)
194 value = ramb36_value + ramb18_value / 2
196 return self._check_result_value(name=self.name, result_value=value)
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")
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 )
208 return build_result.synthesis_size[name]
211class Uram(SizeChecker):
212 name = "URAM"
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 """
221 name = "DSP Blocks"
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")
230 legacy_name = "DSP48 Blocks"
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]
237 return self._check_result_value(name=self.name, result_value=value)