Coverage for hdl_registers/generator/html/register_table.py: 94%
88 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 20:51 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 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 = """\
59<table>
60<thead>
61 <tr>
62 <th>Name</th>
63 <th>Index</th>
64 <th>Address</th>
65 <th>Mode</th>
66 <th>Default value</th>
67 <th>Description</th>
68 </tr>
69</thead>
70<tbody>"""
72 for register_object in self.iterate_register_objects():
73 if isinstance(register_object, Register):
74 html += self._annotate_register(register_object)
75 else:
76 html += self._annotate_register_array(register_object)
78 html += """
79</tbody>
80</table>"""
82 return html
84 @staticmethod
85 def _to_hex_string(value: int, num_nibbles: int = 4) -> str:
86 """
87 Convert an integer value to a hexadecimal string. E.g. "0x1000".
88 """
89 if value < 0:
90 return "-"
92 formatting_string = f"0x{ :0{num_nibbles}X} "
93 return formatting_string.format(value)
95 def _annotate_register_array(self, register_object: "RegisterArray") -> str:
96 description = self._html_translator.translate(register_object.description)
97 html = f"""
98 <tr>
99 <td class="array_header" colspan=5>
100 Register array <strong>{register_object.name}</strong>, \
101repeated {register_object.length} times.
102 Iterator <i>i ∈ [0, {register_object.length - 1}].</i>
103 </td>
104 <td class="array_header">{description}</td>
105 </tr>"""
106 array_index_increment = len(register_object.registers)
107 for register in register_object.registers:
108 register_index = register_object.base_index + register.index
109 html += self._annotate_register(register, register_index, array_index_increment)
111 html += f"""
112 <tr>
113 <td colspan="6" class="array_footer">
114 End register array <strong>{register_object.name}.</strong>
115 </td>
116 </tr>"""
117 return html
119 def _annotate_register(
120 self,
121 register: Register,
122 register_array_index: Optional[int] = None,
123 array_index_increment: Optional[int] = None,
124 ) -> str:
125 if register_array_index is None:
126 address_readable = self._to_hex_string(register.address)
127 index = str(register.address // 4)
128 else:
129 # Should also be set.
130 assert array_index_increment is not None
132 register_address = self._to_hex_string(4 * register_array_index)
133 address_increment = self._to_hex_string(4 * array_index_increment)
134 address_readable = f"{register_address} + i × {address_increment}"
136 index = f"{register_array_index} + i × {array_index_increment}"
138 description = self._html_translator.translate(register.description)
139 html = f"""
140 <tr>
141 <td><strong>{register.name}</strong></td>
142 <td>{index}</td>
143 <td>{address_readable}</td>
144 <td>{register.mode.name}</td>
145 <td>{self._to_hex_string(register.default_value, num_nibbles=1)}</td>
146 <td>{description}</td>
147 </tr>"""
149 for field in register.fields:
150 html += self._annotate_field(field)
152 return html
154 def _annotate_field(self, field: "RegisterField") -> str:
155 description = self._html_translator.translate(field.description)
157 if isinstance(field, Enumeration):
158 description += """\
159 <br />
160 <br />
161 Can be set to the following values:
163 <dl>
164"""
166 for element in field.elements:
167 description += f"""\
168 <dt style="display: list-item; margin-left:1em">
169 <em>{element.name} ({element.value})</em>:
170 </dt>
171"""
173 element_html = self._html_translator.translate(element.description)
174 description += f" <dd>{element_html}</dd>\n"
176 description += " </dl>\n"
178 if isinstance(field, Integer):
179 description += f"""\
180 <br />
181 <br />
182 Valid numeric range: [{field.min_value} – {field.max_value}].
183"""
185 html = f"""
186 <tr>
187 <td> <em>{field.name}</em></td>
188 <td> {self._field_range(field=field)}</td>
189 <td></td>
190 <td></td>
191 <td>{self._field_default_value(field=field)}</td>
192 <td>
193 {description}
194 </td>
195 </tr>"""
197 return html
199 @staticmethod
200 def _field_range(field: "RegisterField") -> str:
201 """
202 Return the bits that this field occupies in a readable format.
203 The way it shall appear in documentation.
204 """
205 if field.width == 1:
206 return f"{field.base_index}"
208 return f"{field.base_index + field.width - 1}:{field.base_index}"
210 @staticmethod
211 def _field_default_value(field: "RegisterField") -> str:
212 """
213 A human-readable string representation of the default value.
214 """
215 if isinstance(field, (Bit, BitVector)):
216 return f"0b{field.default_value}"
218 if isinstance(field, Enumeration):
219 return field.default_value.name
221 if isinstance(field, Integer):
222 return str(field.default_value)
224 raise ValueError(f"Unknown field: {field}")