Coverage for tsfpga/yosys/utilization_parser.py: 100%

41 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 

11import re 

12from collections import OrderedDict 

13from typing import ClassVar 

14 

15# The design is always flattened (see 'YosysNetlistBuild._get_synth_command') before this report 

16# is generated, so there will only ever be a single module in the report, and its cell list will 

17# contain the primitive counts for the whole design. 

18_CELLS_HEADER_RE = re.compile(r"^(\d+)\s+cells$") 

19_CELL_COUNT_RE = re.compile(r"^(\d+)\s+(\S+)$") 

20 

21# Not a real hardware primitive. Yosys debug/scope bookkeeping cell that shall be ignored. 

22_IGNORED_CELLS = frozenset(["$scopeinfo"]) 

23 

24 

25class YosysUtilizationParser: 

26 """ 

27 Used for parsing the resource utilization report produced by the Yosys ``stat`` command. 

28 

29 This base class reports only the raw Yosys primitive cell counts, which depend entirely on 

30 which ``synth_*`` command was used. 

31 Use one of the architecture-specific subclasses to also get aggregated, 

32 architecture-independent resource counts (e.g. ``"Total LUTs"``). 

33 """ 

34 

35 #: Mapping of aggregated resource name (e.g. ``"Total LUTs"``) to a regular expression that 

36 #: is matched, using :func:`re.match`, against the raw primitive cell names to decide which 

37 #: cells shall be summed up to get that number. 

38 #: Shall be set by subclasses. 

39 #: The aggregated names are chosen to match the ones used in the Vivado utilization report 

40 #: (see :mod:`.vivado.build_result_checker`) where applicable, so that the same build result 

41 #: checkers can be reused for Yosys builds. 

42 resource_name_patterns: ClassVar[dict[str, str]] = {} 

43 

44 @classmethod 

45 def get_size(cls, report: str) -> dict[str, int]: 

46 """ 

47 Arguments: 

48 report: The text printed to the console (or a log file) by the Yosys ``stat`` 

49 command. 

50 

51 Return: 

52 A dictionary with the resource utilization of the design. 

53 Contains the raw count of each cell primitive used in the design, as well as the 

54 aggregated counts (e.g. ``"Total LUTs"``) given by :attr:`.resource_name_patterns`, 

55 if any. 

56 """ 

57 primitive_counts = cls._parse_cell_counts(report=report) 

58 

59 result: dict[str, int] = dict(primitive_counts) 

60 for aggregate_name, pattern in cls.resource_name_patterns.items(): 

61 result[aggregate_name] = sum( 

62 count for name, count in primitive_counts.items() if re.match(pattern, name) 

63 ) 

64 

65 return result 

66 

67 @staticmethod 

68 def _parse_cell_counts(report: str) -> dict[str, int]: 

69 result: OrderedDict[str, int] = OrderedDict() 

70 in_cell_list = False 

71 

72 for line in report.splitlines(): 

73 stripped_line = line.strip() 

74 

75 if _CELLS_HEADER_RE.match(stripped_line): 

76 in_cell_list = True 

77 continue 

78 

79 if not in_cell_list: 

80 continue 

81 

82 match = _CELL_COUNT_RE.match(stripped_line) 

83 if match is None: 

84 # Blank line, or something else. The cell list has ended. 

85 break 

86 

87 count, name = match.groups() 

88 if name in _IGNORED_CELLS: 

89 continue 

90 

91 result[name] = result.get(name, 0) + int(count) 

92 

93 return result 

94 

95 

96class YosysXilinxUtilizationParser(YosysUtilizationParser): 

97 """ 

98 Utilization parser for a design synthesized with the Yosys ``synth_xilinx`` command. 

99 """ 

100 

101 resource_name_patterns: ClassVar[dict[str, str]] = { 

102 "Total LUTs": "LUT", 

103 "FFs": "FD", 

104 "RAMB36": "RAMB36", 

105 "RAMB18": "RAMB18", 

106 "Block RAMs": "RAMB", 

107 "DSP Blocks": "DSP", 

108 "SRLs": "SRL", 

109 } 

110 

111 

112class YosysIntelUtilizationParser(YosysUtilizationParser): 

113 """ 

114 Utilization parser for a design synthesized with the Yosys ``synth_intel`` command, i.e. 

115 targeting the MAX10, Cyclone IV, Cyclone IV E or Cyclone 10 LP families. 

116 

117 .. Note:: 

118 The ``"DSP Blocks"`` count is only meaningful for the MAX10 family. 

119 Of the families supported by ``synth_intel``, only that one maps multiplications to DSP 

120 cells. The others implement them in soft logic, so their reports contain no DSP cells at 

121 all and the count is always zero. 

122 Observed with Yosys 0.68, on a design with a single 18x18 multiplication. 

123 """ 

124 

125 resource_name_patterns: ClassVar[dict[str, str]] = { 

126 "Total LUTs": r".*_lcell_comb$", 

127 "FFs": r"dffeas$", 

128 "Block RAMs": r"altsyncram$", 

129 # Note that one mapped multiplier yields both a "*_mac_mult" and a "*_mac_out" 

130 # cell. Count only the multiplier, so that one DSP is reported as one. 

131 "DSP Blocks": r".*_mac_mult$", 

132 } 

133 

134 

135class YosysMicrochipUtilizationParser(YosysUtilizationParser): 

136 """ 

137 Utilization parser for a design synthesized with the Yosys ``synth_microchip`` command, i.e. 

138 targeting the PolarFire family. 

139 """ 

140 

141 resource_name_patterns: ClassVar[dict[str, str]] = { 

142 "Total LUTs": r"CFG\d$", 

143 "FFs": r"SLE$", 

144 "Block RAMs": r"RAM(1K20|64[xX]12)$", 

145 "DSP Blocks": r"MACC_PA$", 

146 }