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
« 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# --------------------------------------------------------------------------------------------------
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 BuildResultChecker(ABC):
64 """
65 Base class for build result checkers that check a certain build result value against a limit.
67 Overload in subclass and implement the ``check`` method according to the resource you want
68 to check.
69 """
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
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.
84 Return:
85 True if check passed, false otherwise.
86 """
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
93 print(f"Result check failed for {name}. Got {result_value}, expected {self.limit}.")
94 return False
97class MaximumLogicLevel(BuildResultChecker):
98 """
99 Check the maximum logic level of a build result against a limit.
100 """
102 name = "Maximum logic level"
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")
109 return self._check_result_value(name="maximum logic level", result_value=value)
112class SizeChecker(BuildResultChecker):
113 """
114 Check a build result size value against a limit.
116 Overload and set the correct ``name``, according to the names
117 in the vendor utilization report.
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 """
123 name: str
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")
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 )
134 return self._check_result_value(
135 name=self.name, result_value=build_result.synthesis_size[self.name]
136 )
139class TotalLuts(SizeChecker):
140 name = "Total LUTs"
143class LogicLuts(SizeChecker):
144 name = "Logic LUTs"
147class LutRams(SizeChecker):
148 name = "LUTRAMs"
151class Srls(SizeChecker):
152 name = "SRLs"
155class Ffs(SizeChecker):
156 name = "FFs"
159class Ramb36(SizeChecker):
160 name = "RAMB36"
163class Ramb18(SizeChecker):
164 name = "RAMB18"
167class Ramb(SizeChecker):
168 """
169 Combined checker for RAMB36 and RAMB18 count.
170 Each RAMB18 counts as half a RAMB36.
171 """
173 name = "RAMB"
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)
182 value = ramb36_value + ramb18_value / 2
184 return self._check_result_value(name=self.name, result_value=value)
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")
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 )
196 return build_result.synthesis_size[name]
199class Uram(SizeChecker):
200 name = "URAM"
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 """
209 name = "DSP Blocks"
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")
218 legacy_name = "DSP48 Blocks"
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]
225 return self._check_result_value(name=self.name, result_value=value)