Coverage for hdl_registers/generator/html/register_table.py: 93%
88 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +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# --------------------------------------------------------------------------------------------------
10from __future__ import annotations
12from typing import TYPE_CHECKING, Any
14from hdl_registers.field.bit import Bit
15from hdl_registers.field.bit_vector import BitVector
16from hdl_registers.field.enumeration import Enumeration
17from hdl_registers.field.integer import Integer
18from hdl_registers.register import Register
20from .html_generator_common import HtmlGeneratorCommon
21from .html_translator import HtmlTranslator
23if TYPE_CHECKING:
24 from pathlib import Path
26 from hdl_registers.field.register_field import RegisterField
27 from hdl_registers.register_array import RegisterArray
28 from hdl_registers.register_list import RegisterList
31class HtmlRegisterTableGenerator(HtmlGeneratorCommon):
32 """
33 Generate HTML code with register information in a table.
34 See the :ref:`generator_html` article for usage details.
35 """
37 __version__ = "1.0.0"
39 SHORT_DESCRIPTION = "HTML register table"
41 @property
42 def output_file(self) -> Path:
43 """
44 Result will be placed in this file.
45 """
46 return self.output_folder / f"{self.name}_register_table.html"
48 def __init__(self, register_list: RegisterList, output_folder: Path) -> None:
49 super().__init__(register_list=register_list, output_folder=output_folder)
51 self._html_translator = HtmlTranslator()
53 def get_code(
54 self,
55 **kwargs: Any, # noqa: ANN401, ARG002
56 ) -> str:
57 if not self.register_list.register_objects:
58 return ""
60 html = """\
61<table>
62<thead>
63 <tr>
64 <th>Name</th>
65 <th>Index</th>
66 <th>Address</th>
67 <th>Mode</th>
68 <th>Default value</th>
69 <th>Description</th>
70 </tr>
71</thead>
72<tbody>"""
74 for register_object in self.iterate_register_objects():
75 if isinstance(register_object, Register):
76 html += self._annotate_register(register_object)
77 else:
78 html += self._annotate_register_array(register_object)
80 html += """
81</tbody>
82</table>"""
84 return html
86 @staticmethod
87 def _to_hex_string(value: int, num_nibbles: int = 4) -> str:
88 """
89 Convert an integer value to a hexadecimal string. E.g. "0x1000".
90 """
91 if value < 0:
92 return "-"
94 formatting_string = f"0x{ :0{num_nibbles}X} "
95 return formatting_string.format(value)
97 def _annotate_register_array(self, register_object: RegisterArray) -> str:
98 description = self._html_translator.translate(register_object.description)
99 html = f"""
100 <tr>
101 <td class="array_header" colspan=5>
102 <p>
103 Register array <strong>{register_object.name}</strong>, \
104repeated {register_object.length} times.
105 Iterator <span class="formula">i ∈ [0, {register_object.length - 1}]</span>.
106 </p>
107 </td>
108 <td class="array_header">
109{description}
110 </td>
111 </tr>"""
112 array_index_increment = len(register_object.registers)
113 for register in register_object.registers:
114 register_index = register_object.base_index + register.index
115 html += self._annotate_register(register, register_index, array_index_increment)
117 html += f"""
118 <tr>
119 <td colspan="6" class="array_footer">
120 <p>End register array <strong>{register_object.name}.</strong></p>
121 </td>
122 </tr>"""
123 return html
125 def _annotate_register(
126 self,
127 register: Register,
128 register_array_index: int | None = None,
129 array_index_increment: int | None = None,
130 ) -> str:
131 if register_array_index is None:
132 address_readable = self._to_hex_string(register.address)
133 index = str(register.address // 4)
134 else:
135 register_address = self._to_hex_string(4 * register_array_index)
136 address_increment = self._to_hex_string(4 * array_index_increment)
137 address_readable = f"{register_address} + i × {address_increment}"
139 index = f"{register_array_index} + i × {array_index_increment}"
141 description = self._html_translator.translate(register.description)
142 html = f"""
143 <tr>
144 <td><p><strong>{register.name}</strong></p></td>
145 <td><p>{index}</p></td>
146 <td><p>{address_readable}</p></td>
147 <td><p>{register.mode.name}</p></td>
148 <td><p>{self._to_hex_string(register.default_value, num_nibbles=1)}</p></td>
149 <td>
150{description}
151 </td>
152 </tr>"""
154 for field in register.fields:
155 html += self._annotate_field(field)
157 return html
159 def _annotate_field(self, field: RegisterField) -> str:
160 description = field.description
162 if isinstance(field, Enumeration):
163 description += """
165Can be set to the following values:
167"""
168 for element in field.elements:
169 # Indent so multi-line description is part of the bullet point.
170 element_description = element.description.strip().replace("\n", "\n ")
171 description += f"""\
172* ``{element.name}`` ({element.value}): {element_description}
173"""
175 description = self._html_translator.translate(description)
177 if isinstance(field, Integer):
178 description += f"""\
179<p>Valid numeric range: [{field.min_value} – {field.max_value}].</p>
180"""
182 return f"""
183 <tr>
184 <td><p> <em>{field.name}</em></p></td>
185 <td><p> {self._field_range(field=field)}</p></td>
186 <td><p></p></td>
187 <td><p></p></td>
188 <td>{self._get_field_default_value(field=field)}</td>
189 <td>
190{description}
191 </td>
192 </tr>"""
194 @staticmethod
195 def _field_range(field: RegisterField) -> str:
196 """
197 Return the bits that this field occupies in a readable format.
198 The way it shall appear in documentation.
199 """
200 if field.width == 1:
201 return f"{field.base_index}"
203 return f"{field.base_index + field.width - 1}:{field.base_index}"
205 def _get_field_default_value(self, field: RegisterField) -> str:
206 """
207 A human-readable string representation of the default value.
208 """
209 return self._html_translator.translate(self._field_default_value(field=field))
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 f"``{field.default_value.name}``"
222 if isinstance(field, Integer):
223 return str(field.default_value)
225 raise ValueError(f"Unknown field: {field}")