Coverage for hdl_registers/register_html_generator.py: 99%
105 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-29 22:03 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-29 22:03 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the hdl_registers project, a HDL register generator fast enough to be run
5# in real time.
6# https://hdl-registers.com
7# https://gitlab.com/hdl_registers/hdl_registers
8# --------------------------------------------------------------------------------------------------
10# Local folder libraries
11from .html_translator import HtmlTranslator
12from .register import REGISTER_MODES, Register
15class RegisterHtmlGenerator:
16 """
17 Generate a HTML page with register information.
18 """
20 def __init__(self, module_name, generated_info):
21 """
22 Arguments:
23 module_name (str): The name of the register map.
24 generated_info (list(str)): Will be placed in the file headers.
25 """
26 self.module_name = module_name
27 self.generated_info = generated_info
28 self._html_translator = HtmlTranslator()
30 def get_register_table(self, register_objects):
31 """
32 Get a HTML table with register infomation. Can be included in other documents.
34 Arguments:
35 register_objects (list): Register arrays and registers to be included.
37 Returns:
38 str: HTML code.
39 """
40 if not register_objects:
41 return ""
43 html = self._file_header()
44 html += self._get_register_table(register_objects)
45 return html
47 def get_constant_table(self, constants):
48 """
49 Get a HTML table with constant infomation. Can be included in other documents.
51 Arguments:
52 constants (list(Constant)): Constants to be included.
54 Returns:
55 str: HTML code.
56 """
57 if not constants:
58 return ""
60 html = self._file_header()
61 html += self._get_constant_table(constants)
62 return html
64 def get_page(self, register_objects, constants):
65 """
66 Get a complete HTML page with register and constant infomation.
68 Arguments:
69 register_objects (list): Register arrays and registers to be included.
70 constants (list(Constant)): Constants to be included.
72 Returns:
73 str: HTML code.
74 """
75 title = f"Documentation of {self.module_name} registers"
76 html = f"""\
77{self._file_header()}
79<!DOCTYPE html>
80<html>
81<head>
82 <title>{title}</title>
83 <!-- Include the style both inline and as a link to a separate CSS file. -->
84 <!-- Some tools, e.g. Jenkins, will not render with an inline styleesheet. -->
85 <!-- For other tools, e.g. page inclusion in sphinx, the style must be in the file. -->
86 <link rel="stylesheet" href="regs_style.css">
87 <style>
88 {self.get_page_style()}
89 </style>
90</head>
91<body>
92 <h1>{title}</h1>
93 <p>This document is a specification for the register interface of the FPGA module \
94<b>{self.module_name}</b>.</p>
95 <p>{' '.join(self.generated_info)}</p>
96 <h2>Register modes</h2>
97 <p>The following register modes are available.</p>
98{self._get_mode_descriptions()}
99"""
101 html += " <h2>Registers</h2>\n"
102 if register_objects:
103 html += f"""
104 <p>The following registers make up the register map.</p>
105{self._get_register_table(register_objects)}
106"""
107 else:
108 html += " <p>This module does not have any registers.</p>"
110 html += " <h2>Constants</h2>\n"
111 if constants:
112 html += f"""
113 <p>The following constants are part of the register interface.</p>
114{self._get_constant_table(constants)}"""
115 else:
116 html += " <p>This module does not have any constants.</p>"
118 html += """
119</body>
120</html>"""
122 return html
124 @staticmethod
125 def get_page_style(table_style=None, font_style=None, extra_style=""):
126 """
127 Get a CSS style for the register pages. Shall be saved to a file called ``regs_style.css``.
129 Returns:
130 str: CSS code.
131 """
132 if font_style is None:
133 font_style = """
134html * {
135 font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
136}"""
138 if table_style is None:
139 table_style = """
140table {
141 border-collapse: collapse;
142}
143td, th {
144 border-width: 1px;
145 border-style: solid;
146 border-color: #ddd;
147 padding: 8px;
148}
149td.array_header {
150 border-top-width: 10px;
151 border-top-color: #4cacaf;
152}
153td.array_footer {
154 border-bottom-width: 10px;
155 border-bottom-color: #4cacaf;
156}
157tr:nth-child(even) {
158 background-color: #f2f2f2;
159}
160th {
161 padding-top: 12px;
162 padding-bottom: 12px;
163 text-align: left;
164 background-color: #4CAF50;
165 color: white;
166}"""
168 style = f"""
169{font_style}
170{table_style}
171{extra_style}"""
172 return style
174 @staticmethod
175 def _comment(comment):
176 return f"<!-- {comment} -->\n"
178 def _file_header(self):
179 return "".join([self._comment(header_line) for header_line in self.generated_info])
181 @staticmethod
182 def _to_hex_string(value, num_nibbles=4):
183 """
184 Convert an integer value to a hexadecimal string. E.g. "0x1000".
185 """
186 if value < 0:
187 return "-"
189 formatting_string = f"0x{{:0{num_nibbles}X}}"
190 return formatting_string.format(value)
192 def _annotate_register_array(self, register_object):
193 description = self._html_translator.translate(register_object.description)
194 html = f"""
195 <tr>
196 <td class="array_header" colspan=5>
197 Register array <strong>{register_object.name}</strong>, \
198repeated {register_object.length} times.
199 Iterator <i>i ∈ [0, {register_object.length - 1}].</i>
200 </td>
201 <td class="array_header">{description}</td>
202 </tr>"""
203 array_index_increment = len(register_object.registers)
204 for register in register_object.registers:
205 register_index = register_object.base_index + register.index
206 html += self._annotate_register(register, register_index, array_index_increment)
208 html += f"""
209 <tr>
210 <td colspan="6" class="array_footer">
211 End register array <strong>{register_object.name}.</strong>
212 </td>
213 </tr>"""
214 return html
216 def _annotate_register(self, register, register_array_index=None, array_index_increment=None):
217 if register_array_index is None:
218 address_readable = self._to_hex_string(register.address)
219 index = register.address // 4
220 else:
221 register_address = self._to_hex_string(4 * register_array_index)
222 address_increment = self._to_hex_string(4 * array_index_increment)
223 address_readable = f"{register_address} + i × {address_increment}"
225 index = f"{register_array_index} + i × {array_index_increment}"
227 description = self._html_translator.translate(register.description)
228 html = f"""
229 <tr>
230 <td><strong>{register.name}</strong></td>
231 <td>{index}</td>
232 <td>{address_readable}</td>
233 <td>{REGISTER_MODES[register.mode].mode_readable}</td>
234 <td>{self._to_hex_string(register.default_value, num_nibbles=1)}</td>
235 <td>{description}</td>
236 </tr>"""
238 for field in register.fields:
239 html += self._annotate_field(field)
241 return html
243 def _annotate_field(self, field):
244 description = self._html_translator.translate(field.description)
245 html = f"""
246 <tr>
247 <td> <em>{field.name}</em></td>
248 <td> {field.range}</td>
249 <td></td>
250 <td></td>
251 <td>{field.default_value_str}</td>
252 <td>{description}</td>
253 </tr>"""
255 return html
257 def _get_register_table(self, register_objects):
258 html = """
259<table>
260<thead>
261 <tr>
262 <th>Name</th>
263 <th>Index</th>
264 <th>Address</th>
265 <th>Mode</th>
266 <th>Default value</th>
267 <th>Description</th>
268 </tr>
269</thead>
270<tbody>"""
272 for register_object in register_objects:
273 if isinstance(register_object, Register):
274 html += self._annotate_register(register_object)
275 else:
276 html += self._annotate_register_array(register_object)
278 html += """
279</tbody>
280</table>"""
282 return html
284 def _format_hex_constant(self, constant):
285 if constant.is_integer:
286 return self._to_hex_string(value=constant.value, num_nibbles=8)
288 # No hex formatting available for the other types
289 if constant.is_boolean or constant.is_float:
290 return "-"
292 raise ValueError(f"Got unexpected constant type. {constant}")
294 def _get_constant_table(self, constants):
295 html = """
296<table>
297<thead>
298 <tr>
299 <th>Name</th>
300 <th>Value</th>
301 <th>Value (hexadecimal)</th>
302 <th>Description</th>
303 </tr>
304</thead>
305<tbody>"""
307 for constant in constants:
308 description = self._html_translator.translate(constant.description)
309 html += f"""
310 <tr>
311 <td><strong>{constant.name}</strong></td>
312 <td>{constant.value}</td>
313 <td>{self._format_hex_constant(constant=constant)}</td>
314 <td>{description}</td>
315 </tr>"""
317 html += """
318</tbody>
319</table>"""
320 return html
322 @staticmethod
323 def _get_mode_descriptions():
324 html = """
325<table>
326<thead>
327 <tr>
328 <th>Mode</th>
329 <th>Description</th>
330 </tr>
331</thead>
332<tbody>"""
334 for mode in REGISTER_MODES.values():
335 html += f"""
336<tr>
337 <td>{mode.mode_readable}</td>
338 <td>{mode.description}</td>
339</tr>
340"""
341 html += """
342</tbody>
343</table>"""
344 return html