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

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

9 

10# Standard libraries 

11from pathlib import Path 

12from typing import TYPE_CHECKING, Any, Optional 

13 

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 

20 

21# Local folder libraries 

22from .html_generator_common import HtmlGeneratorCommon 

23from .html_translator import HtmlTranslator 

24 

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 

30 

31 

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

37 

38 __version__ = "1.0.0" 

39 

40 SHORT_DESCRIPTION = "HTML register table" 

41 

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" 

48 

49 def __init__(self, register_list: "RegisterList", output_folder: Path): 

50 super().__init__(register_list=register_list, output_folder=output_folder) 

51 

52 self._html_translator = HtmlTranslator() 

53 

54 def get_code(self, **kwargs: Any) -> str: 

55 if not self.register_list.register_objects: 

56 return "" 

57 

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

71 

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) 

77 

78 html += """ 

79</tbody> 

80</table>""" 

81 

82 return html 

83 

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

91 

92 formatting_string = f"0x{ :0{num_nibbles}X} " 

93 return formatting_string.format(value) 

94 

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 &isin; [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) 

110 

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 

118 

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 

131 

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 &times; {address_increment}" 

135 

136 index = f"{register_array_index} + i &times; {array_index_increment}" 

137 

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

148 

149 for field in register.fields: 

150 html += self._annotate_field(field) 

151 

152 return html 

153 

154 def _annotate_field(self, field: "RegisterField") -> str: 

155 description = self._html_translator.translate(field.description) 

156 

157 if isinstance(field, Enumeration): 

158 description += """\ 

159 <br /> 

160 <br /> 

161 Can be set to the following values: 

162 

163 <dl> 

164""" 

165 

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

172 

173 element_html = self._html_translator.translate(element.description) 

174 description += f" <dd>{element_html}</dd>\n" 

175 

176 description += " </dl>\n" 

177 

178 if isinstance(field, Integer): 

179 description += f"""\ 

180 <br /> 

181 <br /> 

182 Valid numeric range: [{field.min_value} &ndash; {field.max_value}]. 

183""" 

184 

185 html = f""" 

186 <tr> 

187 <td>&nbsp;&nbsp;<em>{field.name}</em></td> 

188 <td>&nbsp;&nbsp;{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>""" 

196 

197 return html 

198 

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

207 

208 return f"{field.base_index + field.width - 1}:{field.base_index}" 

209 

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

217 

218 if isinstance(field, Enumeration): 

219 return field.default_value.name 

220 

221 if isinstance(field, Integer): 

222 return str(field.default_value) 

223 

224 raise ValueError(f"Unknown field: {field}")