Coverage for tsfpga/module_documentation.py: 65%

123 statements  

« 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# -------------------------------------------------------------------------------------------------- 

8 

9from tsfpga.system_utils import create_file, file_is_in_directory, read_file 

10from tsfpga.vhdl_file_documentation import VhdlFileDocumentation 

11 

12 

13class ModuleDocumentation: 

14 

15 """ 

16 Methods for generating a reStructuredText document with module documentation. 

17 The content is extracted from VHDL source file headers. 

18 """ 

19 

20 def __init__(self, module): 

21 """ 

22 Arguments: 

23 module (:class:`.BaseModule`): The module which shall be documented. 

24 """ 

25 self._module = module 

26 

27 def get_overview_rst(self): 

28 """ 

29 Get the contents of the module's ``doc/<name>.rst``, i.e. the module "overview" document. 

30 

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) 

37 

38 return None 

39 

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`. 

47 

48 Arguments: 

49 heading_character (str): Character to use for heading underline. 

50 

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: 

59 

60{heading} 

61{heading_underline} 

62 

63This module has register definitions. 

64Please see :download:`separate HTML page <{self._module.name}_regs.html>` for \ 

65register documentation. 

66""" 

67 

68 return None 

69 

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. 

81 

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

90 

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] 

96 

97 all_builds = self._module.get_build_projects() 

98 

99 rst = "" 

100 

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

106 

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) 

115 

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 ) 

122 

123 return rst 

124 

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. 

129 

130 Arguments: 

131 exclude_module_folders (list(str)): Folder names within the module root that shall be 

132 excluded from documentation. 

133 

134 Returns: 

135 str: An RST document. 

136 """ 

137 heading_character_1 = "=" 

138 heading_character_2 = "-" 

139 heading_character_3 = "_" 

140 

141 heading = f"Module {self._module.name}" 

142 heading_underline = heading_character_1 * len(heading) 

143 

144 overview_rst = self.get_overview_rst() 

145 overview_rst = "" if overview_rst is None else overview_rst 

146 

147 registers_rst = self.get_register_rst(heading_character=heading_character_2) 

148 registers_rst = "" if registers_rst is None else registers_rst 

149 

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 ) 

155 

156 rst = f"""\ 

157 

158.. _module_{self._module.name}: 

159 

160{heading} 

161{heading_underline} 

162 

163This document contains technical documentation for the ``{self._module.name}`` module. 

164 

165{overview_rst} 

166 

167{registers_rst} 

168 

169{submodule_rst} 

170""" 

171 

172 return rst 

173 

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. 

179 

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) 

188 

189 rst = self.get_rst_document(exclude_module_folders=exclude_module_folders) 

190 create_file(output_path / f"{self._module.name}.rst", contents=rst) 

191 

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 ) 

199 

200 module_regs_pkg = self._module.path / f"{self._module.name}_regs_pkg.vhd" 

201 

202 def file_should_be_included(hdl_file): 

203 if file_is_in_directory(hdl_file.path, exclude_folders): 

204 return False 

205 

206 if not hdl_file.is_vhdl: 

207 return False 

208 

209 if hdl_file.path == module_regs_pkg: 

210 return False 

211 

212 return True 

213 

214 vhdl_files = [hdl_file for hdl_file in hdl_files if file_should_be_included(hdl_file)] 

215 

216 # Sort by file name 

217 def sort_key(vhdl_file): 

218 return vhdl_file.path.name 

219 

220 vhdl_files = sorted(vhdl_files, key=sort_key) 

221 

222 return vhdl_files 

223 

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) 

231 

232 file_rst = vhdl_file_documentation.get_header_rst() 

233 file_rst = "" if file_rst is None else file_rst 

234 

235 symbolator_rst = self._get_symbolator_rst(vhdl_file_documentation) 

236 symbolator_rst = "" if symbolator_rst is None else symbolator_rst 

237 

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 ) 

243 

244 entity_name = vhdl_file_path.stem 

245 heading = f"{vhdl_file_path.name}" 

246 heading_underline = heading_character * len(heading) 

247 

248 rst = f""" 

249.. _{self._module.name}.{entity_name}: 

250 

251{heading} 

252{heading_underline} 

253 

254{symbolator_rst} 

255 

256{file_rst} 

257 

258{resource_utilization_rst} 

259""" 

260 

261 return rst 

262 

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

271 

272 indent = " " 

273 rst = ".. symbolator::\n\n" 

274 rst += indent + component.replace("\n", f"\n{indent}") 

275 

276 return rst 

277 

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) 

290 

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) 

296 

297 checkers.append(checker_dict) 

298 

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} 

307 

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. 

312 

313.. list-table:: Resource utilization for {vhdl_file_path.name} netlist builds. 

314 :header-rows: 1 

315 

316""" 

317 

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) 

324 

325 # Fill in the header row 

326 rst += " * - Generics\n" 

327 for checker_name in checker_names: 

328 rst += f" - {checker_name}\n" 

329 

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) 

334 

335 rst += f"""\ 

336 * - {generics_rst}""" 

337 

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

351 

352 rst += f"""\ 

353{leader}(Using wrapper 

354 

355 {netlist_builds[build_idx].top}.vhd)""" 

356 

357 rst += "\n" 

358 

359 for checker_name in checker_names: 

360 checker_value = checkers[build_idx][checker_name] 

361 rst += f" - {checker_value}\n" 

362 

363 return rst