Coverage for tsfpga/module_documentation.py: 65%
123 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project.
5# https://tsfpga.com
6# https://gitlab.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9from tsfpga.system_utils import create_file, file_is_in_directory, read_file
10from tsfpga.vhdl_file_documentation import VhdlFileDocumentation
13class ModuleDocumentation:
15 """
16 Methods for generating a reStructuredText document with module documentation.
17 The content is extracted from VHDL source file headers.
18 """
20 def __init__(self, module):
21 """
22 Arguments:
23 module (:class:`.BaseModule`): The module which shall be documented.
24 """
25 self._module = module
27 def get_overview_rst(self):
28 """
29 Get the contents of the module's ``doc/<name>.rst``, i.e. the module "overview" document.
31 Return:
32 str: Module overview RST. ``None`` if file does not exist.
33 """
34 overview_rst_file = self._module.path / "doc" / f"{self._module.name}.rst"
35 if overview_rst_file.exists():
36 return read_file(overview_rst_file)
38 return None
40 def get_register_rst(self, heading_character):
41 """
42 Get an RST snippet with a link to the module's register documentation, if available.
43 Note that this will create an RST ``:download:`` statement to the register .html page.
44 When building, the ``.html`` file must be present in the same directory as the
45 ``.rst`` file.
46 This is done automatically by :meth:`.create_rst_document`.
48 Arguments:
49 heading_character (str): Character to use for heading underline.
51 Return:
52 str: RST snippet with link to register HTML. ``None`` if module does not have registers.
53 """
54 if self._module.registers is not None:
55 heading = "Register interface"
56 heading_underline = heading_character * len(heading)
57 return f"""\
58.. _{self._module.name}.register_interface:
60{heading}
61{heading_underline}
63This module has register definitions.
64Please see :download:`separate HTML page <{self._module.name}_regs.html>` for \
65register documentation.
66"""
68 return None
70 def get_submodule_rst(
71 self,
72 heading_character,
73 heading_character_2,
74 exclude_files=None,
75 exclude_module_folders=None,
76 ):
77 """
78 Get RST code with documentation of the different sub-modules (files) of the module.
79 Contains documentation that is extracted from the file headers, as well as a
80 symbolator symbol of the entity.
82 Arguments:
83 heading_character (str): Character to use for heading underline.
84 heading_character_2 (str): Character to use for next level of heading underline.
85 exclude_files (set(pathlib.Path)): Files that shall be excluded from the documentation.
86 exclude_module_folders (list(str)): Folder names within the module root that shall be
87 excluded from documentation. For example, if you chosen module structure places
88 only netlist build wrappers in the "rtl/" folder within modules, and you do not
89 want them included in the documentation, then pass the argument ["rtl"].
91 Return:
92 str: RST code with sub-module documentation.
93 """
94 exclude_module_folders = [] if exclude_module_folders is None else exclude_module_folders
95 exclude_module_paths = [self._module.path / name for name in exclude_module_folders]
97 all_builds = self._module.get_build_projects()
99 rst = ""
101 for hdl_file in self._get_vhdl_files(
102 exclude_files=exclude_files, exclude_folders=exclude_module_paths
103 ):
104 vhdl_file_path = hdl_file.path
105 netlist_build_base_name = f"{self._module.library_name}.{vhdl_file_path.stem}"
107 netlist_builds = []
108 # Include all netlist builds whose project name matches this file
109 for project in all_builds:
110 if project.is_netlist_build and (
111 project.name == netlist_build_base_name
112 or project.name.startswith(f"{netlist_build_base_name}.")
113 ):
114 netlist_builds.append(project)
116 rst += self._get_vhdl_file_rst(
117 vhdl_file_path=vhdl_file_path,
118 heading_character=heading_character,
119 heading_character_2=heading_character_2,
120 netlist_builds=netlist_builds,
121 )
123 return rst
125 def get_rst_document(self, exclude_module_folders=None):
126 """
127 Get a complete RST document with the content of :meth:`.get_overview_rst`,
128 :meth:`.get_register_rst`, and :meth:`.get_submodule_rst`, as well as a top level heading.
130 Arguments:
131 exclude_module_folders (list(str)): Folder names within the module root that shall be
132 excluded from documentation.
134 Returns:
135 str: An RST document.
136 """
137 heading_character_1 = "="
138 heading_character_2 = "-"
139 heading_character_3 = "_"
141 heading = f"Module {self._module.name}"
142 heading_underline = heading_character_1 * len(heading)
144 overview_rst = self.get_overview_rst()
145 overview_rst = "" if overview_rst is None else overview_rst
147 registers_rst = self.get_register_rst(heading_character=heading_character_2)
148 registers_rst = "" if registers_rst is None else registers_rst
150 submodule_rst = self.get_submodule_rst(
151 heading_character=heading_character_2,
152 heading_character_2=heading_character_3,
153 exclude_module_folders=exclude_module_folders,
154 )
156 rst = f"""\
158.. _module_{self._module.name}:
160{heading}
161{heading_underline}
163This document contains technical documentation for the ``{self._module.name}`` module.
165{overview_rst}
167{registers_rst}
169{submodule_rst}
170"""
172 return rst
174 def create_rst_document(self, output_path, exclude_module_folders=None):
175 """
176 Create an ``.rst`` file in ``output_path`` with the content from :meth:`.get_rst_document`.
177 If the module has registers, the HTML page will also be generated in ``output_path``, so
178 that e.g. sphinx can be run directly.
180 Arguments:
181 output_path (pathlib.Path): Document will be placed here.
182 exclude_module_folders (list(str)): Folder names within the module root that shall be
183 excluded from documentation.
184 """
185 register_list = self._module.registers
186 if register_list is not None:
187 register_list.create_html_page(output_path)
189 rst = self.get_rst_document(exclude_module_folders=exclude_module_folders)
190 create_file(output_path / f"{self._module.name}.rst", contents=rst)
192 def _get_vhdl_files(self, exclude_files, exclude_folders):
193 """
194 Get VHDL files that shall be included in the documentation, in order.
195 """
196 hdl_files = self._module.get_simulation_files(
197 files_avoid=exclude_files, include_tests=False
198 )
200 module_regs_pkg = self._module.path / f"{self._module.name}_regs_pkg.vhd"
202 def file_should_be_included(hdl_file):
203 if file_is_in_directory(hdl_file.path, exclude_folders):
204 return False
206 if not hdl_file.is_vhdl:
207 return False
209 if hdl_file.path == module_regs_pkg:
210 return False
212 return True
214 vhdl_files = [hdl_file for hdl_file in hdl_files if file_should_be_included(hdl_file)]
216 # Sort by file name
217 def sort_key(vhdl_file):
218 return vhdl_file.path.name
220 vhdl_files = sorted(vhdl_files, key=sort_key)
222 return vhdl_files
224 def _get_vhdl_file_rst(
225 self, vhdl_file_path, heading_character, heading_character_2, netlist_builds
226 ):
227 """
228 Get reStructuredText documentation for a VHDL file.
229 """
230 vhdl_file_documentation = VhdlFileDocumentation(vhdl_file_path)
232 file_rst = vhdl_file_documentation.get_header_rst()
233 file_rst = "" if file_rst is None else file_rst
235 symbolator_rst = self._get_symbolator_rst(vhdl_file_documentation)
236 symbolator_rst = "" if symbolator_rst is None else symbolator_rst
238 resource_utilization_rst = self._get_resource_utilization_rst(
239 vhdl_file_path=vhdl_file_path,
240 heading_character=heading_character_2,
241 netlist_builds=netlist_builds,
242 )
244 entity_name = vhdl_file_path.stem
245 heading = f"{vhdl_file_path.name}"
246 heading_underline = heading_character * len(heading)
248 rst = f"""
249.. _{self._module.name}.{entity_name}:
251{heading}
252{heading_underline}
254{symbolator_rst}
256{file_rst}
258{resource_utilization_rst}
259"""
261 return rst
263 @staticmethod
264 def _get_symbolator_rst(vhdl_file_documentation):
265 """
266 Get RST for rendering a symbolator component.
267 """
268 component = vhdl_file_documentation.get_symbolator_component()
269 if component is None:
270 return ""
272 indent = " "
273 rst = ".. symbolator::\n\n"
274 rst += indent + component.replace("\n", f"\n{indent}")
276 return rst
278 def _get_resource_utilization_rst(
279 self,
280 vhdl_file_path,
281 heading_character,
282 netlist_builds,
283 ): # pylint: disable=too-many-locals,too-many-branches
284 # First, loop over all netlist builds for this module and assemble information
285 generics = []
286 checkers = []
287 for netlist_build in netlist_builds:
288 if netlist_build.build_result_checkers:
289 generics.append(netlist_build.static_generics)
291 # Create a dictionary for each build, that maps "Checker name": "value"
292 checker_dict = {}
293 for checker in netlist_build.build_result_checkers:
294 # Casting the limit to string yields e.g. "< 4", "4" or "> 4"
295 checker_dict[checker.name] = str(checker.limit)
297 checkers.append(checker_dict)
299 # Make RST of the information
300 rst = ""
301 if generics:
302 heading = "Resource utilization"
303 heading_underline = heading_character * len(heading)
304 rst = f"""
305{heading}
306{heading_underline}
308This entity has :ref:`netlist builds <tsfpga:netlist_build>` set up with
309:ref:`automatic size checkers <tsfpga:build_result_checkers>` in ``module_{self._module.name}.py``.
310The following table lists the resource utilization for the entity, depending on
311generic configuration.
313.. list-table:: Resource utilization for {vhdl_file_path.name} netlist builds.
314 :header-rows: 1
316"""
318 # Make a list of the unique checker names. Use list rather than set to preserve order.
319 checker_names = []
320 for build_checkers in checkers:
321 for checker_name in build_checkers:
322 if checker_name not in checker_names:
323 checker_names.append(checker_name)
325 # Fill in the header row
326 rst += " * - Generics\n"
327 for checker_name in checker_names:
328 rst += f" - {checker_name}\n"
330 # Make one row for each netlist build
331 for build_idx, generic_dict in enumerate(generics):
332 generic_strings = [f"{name} = {value}" for name, value in generic_dict.items()]
333 generics_rst = "\n\n ".join(generic_strings)
335 rst += f"""\
336 * - {generics_rst}"""
338 # If the "top" of the project is different than this file name, we assume that it
339 # is a wrapper. Add a note to the table about this. This occurs e.g. in the reg_file
340 # and fifo modules.
341 if netlist_builds[build_idx].top != vhdl_file_path.stem:
342 if generic_strings:
343 # If there is already something in the generic column, this note shall be
344 # placed on a new line.
345 leader = "\n\n "
346 else:
347 # Otherwise, i.e. if the netlist build has no generics set,
348 # the note shall be placed as the first thing. This is the case with
349 # two builds in the reg_file module.
350 leader = ""
352 rst += f"""\
353{leader}(Using wrapper
355 {netlist_builds[build_idx].top}.vhd)"""
357 rst += "\n"
359 for checker_name in checker_names:
360 checker_value = checkers[build_idx][checker_name]
361 rst += f" - {checker_value}\n"
363 return rst