Coverage for tsfpga/vivado/build_result.py: 74%
62 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-17 20:51 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-17 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 __future__ import annotations
11from .logic_level_distribution_parser import LogicLevelDistributionParser
14class BuildResult:
15 """
16 Attributes:
17 name (`str`): The name of the build.
18 success (`bool`): True if the build and all pre- and post hooks succeeded.
19 synthesis_run_name (`str`): The name of the Vivado synthesis run that produced this result
20 (e.g. ``synth_2``).
21 implementation_run_name (`str`): The name of the Vivado implementation run that produced
22 this result (e.g. ``impl_2``).
23 synthesis_size (`dict`): A dictionary with the utilization of primitives for the
24 synthesized design.
25 Will be ``None`` if synthesis failed or did not run.
26 implementation_size (`dict`): A dictionary with the utilization of primitives for
27 the implemented design.
28 Will be ``None`` if implementation failed or did not run.
29 logic_level_distribution (str): A table with logic level distribution as reported by Vivado.
30 Will be ``None`` for non-netlist builds.
31 Will be ``None`` if synthesis failed or did not run.
32 """
34 def __init__(self, name: str, synthesis_run_name: str) -> None:
35 """
36 Arguments:
37 name: The name of the build.
38 synthesis_run_name: The name of the Vivado run that produced this result
39 (e.g. ``synth_2``).
40 """
41 self.name = name
42 self.success: bool = True
44 self.synthesis_run_name = synthesis_run_name
45 self.implementation_run_name: str | None = None
47 self.synthesis_size: dict[str, int] | None = None
48 self.implementation_size: dict[str, int] | None = None
50 self.logic_level_distribution: str | None = None
52 self.maximum_synthesis_frequency_hz: float | None = None
54 def size_summary(self) -> str | None:
55 """
56 Return a string with a formatted message of the size.
58 Return:
59 A human-readable message of the latest size.
60 ``None`` if no size is set.
61 """
62 build_step = None
63 size = None
65 if self.implementation_size:
66 build_step = "implementation"
67 size = self.implementation_size
69 elif self.synthesis_size:
70 build_step = "synthesis"
71 size = self.synthesis_size
73 else:
74 return None
76 values = [(key, _to_thousands_separated_string(value)) for key, value in size.items()]
77 max_key_length = max(len(key) for key, _ in values)
78 max_value_length = max(len(value) for _, value in values)
80 result = f"Size of {self.name} after {build_step}:"
81 for key, value in values:
82 pad = " " * (max_key_length - len(key) + max_value_length - len(value))
83 result += f"\n - {key}: {pad}{value}"
85 return result
87 def report(self) -> str | None:
88 """
89 Return a report of the build result. Includes all metrics and information that has been
90 extracted from the Vivado reports.
91 """
92 result = self.size_summary()
93 if result is None:
94 return None
96 if self.maximum_synthesis_frequency_hz:
97 result += (
98 f"\nMaximum synthesis frequency estimate: "
99 f"{_to_engineering_string(value=self.maximum_synthesis_frequency_hz)}Hz"
100 )
102 if self.logic_level_distribution:
103 result += f"\nLogic level distribution:\n{self.logic_level_distribution}"
105 return result
107 @property
108 def maximum_logic_level(self) -> None | int:
109 """
110 The maximum level in the the :attr:`.BuildResult.logic_level_distribution`.
111 Will be ``None`` for non-netlist builds.
112 Will be ``None`` if synthesis failed or did not run.
114 Return:
115 The maximum logic level.
116 """
117 if not self.logic_level_distribution:
118 return None
120 return LogicLevelDistributionParser.get_maximum_logic_level(
121 table=self.logic_level_distribution
122 )
125def _to_engineering_string(value: float) -> str:
126 """
127 Returns float/int value formatted with an SI prefix, for printing with a unit.
128 For example, ``1.5625e8`` becomes ``156.25 M``.
129 """
130 if value == 0:
131 return "0 "
133 sign = ""
134 if value < 0:
135 value = -value
136 sign = "-"
138 exponent = 0
140 while value < 1:
141 value *= 1000
142 exponent -= 1
143 while value >= 1000:
144 value /= 1000
145 exponent += 1
147 prefix = "" if exponent == 0 else "yzafpnum*kMGTPEZY"[exponent + 8]
149 return f"{sign}{value:.2f} {prefix}"
152def _to_thousands_separated_string(value: int) -> str:
153 """
154 Returns an integer formatted with thousand separators, for printing.
155 For example, ``156250000`` becomes ``156 250 000``.
156 """
157 return f"{value:_}".replace("_", " ")