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

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 

9from __future__ import annotations 

10 

11from .logic_level_distribution_parser import LogicLevelDistributionParser 

12 

13 

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 """ 

33 

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 

43 

44 self.synthesis_run_name = synthesis_run_name 

45 self.implementation_run_name: str | None = None 

46 

47 self.synthesis_size: dict[str, int] | None = None 

48 self.implementation_size: dict[str, int] | None = None 

49 

50 self.logic_level_distribution: str | None = None 

51 

52 self.maximum_synthesis_frequency_hz: float | None = None 

53 

54 def size_summary(self) -> str | None: 

55 """ 

56 Return a string with a formatted message of the size. 

57 

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 

64 

65 if self.implementation_size: 

66 build_step = "implementation" 

67 size = self.implementation_size 

68 

69 elif self.synthesis_size: 

70 build_step = "synthesis" 

71 size = self.synthesis_size 

72 

73 else: 

74 return None 

75 

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) 

79 

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}" 

84 

85 return result 

86 

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 

95 

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 ) 

101 

102 if self.logic_level_distribution: 

103 result += f"\nLogic level distribution:\n{self.logic_level_distribution}" 

104 

105 return result 

106 

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. 

113 

114 Return: 

115 The maximum logic level. 

116 """ 

117 if not self.logic_level_distribution: 

118 return None 

119 

120 return LogicLevelDistributionParser.get_maximum_logic_level( 

121 table=self.logic_level_distribution 

122 ) 

123 

124 

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 " 

132 

133 sign = "" 

134 if value < 0: 

135 value = -value 

136 sign = "-" 

137 

138 exponent = 0 

139 

140 while value < 1: 

141 value *= 1000 

142 exponent -= 1 

143 while value >= 1000: 

144 value /= 1000 

145 exponent += 1 

146 

147 prefix = "" if exponent == 0 else "yzafpnum*kMGTPEZY"[exponent + 8] 

148 

149 return f"{sign}{value:.2f} {prefix}" 

150 

151 

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("_", " ")