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
« 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# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import re
12from collections import OrderedDict
13from typing import ClassVar
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+)$")
21# Not a real hardware primitive. Yosys debug/scope bookkeeping cell that shall be ignored.
22_IGNORED_CELLS = frozenset(["$scopeinfo"])
25class YosysUtilizationParser:
26 """
27 Used for parsing the resource utilization report produced by the Yosys ``stat`` command.
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 """
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]] = {}
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.
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)
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 )
65 return result
67 @staticmethod
68 def _parse_cell_counts(report: str) -> dict[str, int]:
69 result: OrderedDict[str, int] = OrderedDict()
70 in_cell_list = False
72 for line in report.splitlines():
73 stripped_line = line.strip()
75 if _CELLS_HEADER_RE.match(stripped_line):
76 in_cell_list = True
77 continue
79 if not in_cell_list:
80 continue
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
87 count, name = match.groups()
88 if name in _IGNORED_CELLS:
89 continue
91 result[name] = result.get(name, 0) + int(count)
93 return result
96class YosysXilinxUtilizationParser(YosysUtilizationParser):
97 """
98 Utilization parser for a design synthesized with the Yosys ``synth_xilinx`` command.
99 """
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 }
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.
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 """
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 }
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 """
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 }