Coverage for hdl_registers/generator/html/register_table.py: 94%
88 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-19 20:51 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-19 20:51 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the hdl-registers project, an HDL register generator fast enough to run
5# in real time.
6# https://hdl-registers.com
7# https://github.com/hdl-registers/hdl-registers
8# --------------------------------------------------------------------------------------------------
10# Standard libraries
11from pathlib import Path
12from typing import TYPE_CHECKING, Any, Optional
14# First party libraries
15from hdl_registers.field.bit import Bit
16from hdl_registers.field.bit_vector import BitVector
17from hdl_registers.field.enumeration import Enumeration
18from hdl_registers.field.integer import Integer
19from hdl_registers.register import Register
21# Local folder libraries
22from .html_generator_common import HtmlGeneratorCommon
23from .html_translator import HtmlTranslator
25if TYPE_CHECKING:
26 # First party libraries
27 from hdl_registers.field.register_field import RegisterField
28 from hdl_registers.register_array import RegisterArray
29 from hdl_registers.register_list import RegisterList
32class HtmlRegisterTableGenerator(HtmlGeneratorCommon):
33 """
34 Generate HTML code with register information in a table.
35 See the :ref:`generator_html` article for usage details.
36 """
38 __version__ = "1.0.0"
40 SHORT_DESCRIPTION = "HTML register table"
42 @property
43 def output_file(self) -> Path:
44 """
45 Result will be placed in this file.
46 """
47 return self.output_folder / f"{self.name}_register_table.html"
49 def __init__(self, register_list: "RegisterList", output_folder: Path):
50 super().__init__(register_list=register_list, output_folder=output_folder)
52 self._html_translator = HtmlTranslator()
54 def get_code(self, **kwargs: Any) -> str:
55 if not self.register_list.register_objects:
56 return ""
58 html = f"""\
59{self.header}
60<table>
61<thead>
62 <tr>
63 <th>Name</th>
64 <th>Index</th>
65 <th>Address</th>
66 <th>Mode</th>
67 <th>Default value</th>
68 <th>Description</th>
69 </tr>
70</thead>
71<tbody>"""
73 for register_object in self.iterate_register_objects():
74 if isinstance(register_object, Register):
75 html += self._annotate_register(register_object)
76 else:
77 html += self._annotate_register_array(register_object)
79 html += """
80</tbody>
81</table>"""
83 return html
85 @staticmethod
86 def _to_hex_string(value: int, num_nibbles: int = 4) -> str:
87 """
88 Convert an integer value to a hexadecimal string. E.g. "0x1000".
89 """
90 if value < 0:
91 return "-"
93 formatting_string = f"0x{ :0{num_nibbles}X} "
94 return formatting_string.format(value)
96 def _annotate_register_array(self, register_object: "RegisterArray") -> str:
97 description = self._html_translator.translate(register_object.description)
98 html = f"""
99 <tr>
100 <td class="array_header" colspan=5>
101 Register array <strong>{register_object.name}</strong>, \
102repeated {register_object.length} times.
103 Iterator <i>i ∈ [0, {register_object.length - 1}].</i>
104 </td>
105 <td class="array_header">{description}</td>
106 </tr>"""
107 array_index_increment = len(register_object.registers)
108 for register in register_object.registers:
109 register_index = register_object.base_index + register.index
110 html += self._annotate_register(register, register_index, array_index_increment)
112 html += f"""
113 <tr>
114 <td colspan="6" class="array_footer">
115 End register array <strong>{register_object.name}.</strong>
116 </td>
117 </tr>"""
118 return html
120 def _annotate_register(
121 self,
122 register: Register,
123 register_array_index: Optional[int] = None,
124 array_index_increment: Optional[int] = None,
125 ) -> str:
126 if register_array_index is None:
127 address_readable = self._to_hex_string(register.address)
128 index = str(register.address // 4)
129 else:
130 # Should also be set.
131 assert array_index_increment is not None
133 register_address = self._to_hex_string(4 * register_array_index)
134 address_increment = self._to_hex_string(4 * array_index_increment)
135 address_readable = f"{register_address} + i × {address_increment}"
137 index = f"{register_array_index} + i × {array_index_increment}"
139 description = self._html_translator.translate(register.description)
140 html = f"""
141 <tr>
142 <td><strong>{register.name}</strong></td>
143 <td>{index}</td>
144 <td>{address_readable}</td>
145 <td>{register.mode.name}</td>
146 <td>{self._to_hex_string(register.default_value, num_nibbles=1)}</td>
147 <td>{description}</td>
148 </tr>"""
150 for field in register.fields:
151 html += self._annotate_field(field)
153 return html
155 def _annotate_field(self, field: "RegisterField") -> str:
156 description = self._html_translator.translate(field.description)
158 if isinstance(field, Enumeration):
159 description += """\
160 <br />
161 <br />
162 Can be set to the following values:
164 <dl>
165"""
167 for element in field.elements:
168 description += f"""\
169 <dt style="display: list-item; margin-left:1em">
170 <em>{element.name} ({element.value})</em>:
171 </dt>
172"""
174 element_html = self._html_translator.translate(element.description)
175 description += f" <dd>{element_html}</dd>\n"
177 description += " </dl>\n"
179 if isinstance(field, Integer):
180 description += f"""\
181 <br />
182 <br />
183 Valid numeric range: [{field.min_value} – {field.max_value}].
184"""
186 html = f"""
187 <tr>
188 <td> <em>{field.name}</em></td>
189 <td> {self._field_range(field=field)}</td>
190 <td></td>
191 <td></td>
192 <td>{self._field_default_value(field=field)}</td>
193 <td>
194 {description}
195 </td>
196 </tr>"""
198 return html
200 @staticmethod
201 def _field_range(field: "RegisterField") -> str:
202 """
203 Return the bits that this field occupies in a readable format.
204 The way it shall appear in documentation.
205 """
206 if field.width == 1:
207 return f"{field.base_index}"
209 return f"{field.base_index + field.width - 1}:{field.base_index}"
211 @staticmethod
212 def _field_default_value(field: "RegisterField") -> str:
213 """
214 A human-readable string representation of the default value.
215 """
216 if isinstance(field, (Bit, BitVector)):
217 return f"0b{field.default_value}"
219 if isinstance(field, Enumeration):
220 return field.default_value.name
222 if isinstance(field, Integer):
223 return str(field.default_value)
225 raise ValueError(f"Unknown field: {field}")