Coverage for tsfpga/build_result.py: 65%

43 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-09 00:40 +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 

11 

12class BuildResult: 

13 """ 

14 The result of a build, in a backend-agnostic form. 

15 Each build backend has its own subclass, which is what is actually returned by the build 

16 methods: :class:`.VivadoBuildResult` and :class:`.YosysBuildResult`. 

17 

18 Attributes: 

19 name (`str`): The name of the build. 

20 success (`bool`): True if the build and all pre- and post hooks succeeded. 

21 synthesis_size (`dict`): A dictionary with the utilization of primitives for the 

22 synthesized design. 

23 Will be ``None`` if synthesis failed or did not run. 

24 """ 

25 

26 def __init__(self, name: str) -> None: 

27 """ 

28 Arguments: 

29 name: The name of the build. 

30 """ 

31 self.name = name 

32 self.success: bool = True 

33 

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

35 

36 def _get_size_to_report(self) -> tuple[str, dict[str, int]] | None: 

37 """ 

38 Return: 

39 The name of the build step, and the size that shall be reported for it. 

40 ``None`` if no size is set. 

41 """ 

42 if self.synthesis_size: 

43 return "synthesis", self.synthesis_size 

44 

45 return None 

46 

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

48 """ 

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

50 

51 Return: 

52 A human-readable message of the latest size. 

53 ``None`` if no size is set. 

54 """ 

55 size_to_report = self._get_size_to_report() 

56 if size_to_report is None: 

57 return None 

58 

59 build_step, size = size_to_report 

60 

61 values = [(key, _to_thousands_separated_string(value)) for key, value in size.items()] 

62 max_key_length = max(len(key) for key, _ in values) 

63 max_value_length = max(len(value) for _, value in values) 

64 

65 result = f"Size of {self.name} after {build_step}:" 

66 for key, value in values: 

67 pad = " " * (max_key_length - len(key) + max_value_length - len(value)) 

68 result += f"\n - {key}: {pad}{value}" 

69 

70 return result 

71 

72 def report(self) -> str | None: 

73 """ 

74 Return a report of the build result. Includes all metrics and information that has been 

75 extracted from the build tool's reports. 

76 """ 

77 return self.size_summary() 

78 

79 

80def _to_engineering_string(value: float) -> str: 

81 """ 

82 Returns float/int value formatted with an SI prefix, for printing with a unit. 

83 For example, ``1.5625e8`` becomes ``156.25 M``. 

84 """ 

85 if value == 0: 

86 return "0 " 

87 

88 sign = "" 

89 if value < 0: 

90 value = -value 

91 sign = "-" 

92 

93 exponent = 0 

94 

95 while value < 1: 

96 value *= 1000 

97 exponent -= 1 

98 while value >= 1000: 

99 value /= 1000 

100 exponent += 1 

101 

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

103 

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

105 

106 

107def _to_thousands_separated_string(value: int) -> str: 

108 """ 

109 Returns an integer formatted with thousand separators, for printing. 

110 For example, ``156250000`` becomes ``156 250 000``. 

111 """ 

112 return f"{value:_}".replace("_", " ")