Coverage for hdl_registers/generator/html/register_table.py: 94%

88 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-07 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 = 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>""" 

72 

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) 

78 

79 html += """ 

80</tbody> 

81</table>""" 

82 

83 return html 

84 

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

92 

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

94 return formatting_string.format(value) 

95 

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

111 

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 

119 

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 

132 

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

136 

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

138 

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

149 

150 for field in register.fields: 

151 html += self._annotate_field(field) 

152 

153 return html 

154 

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

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

157 

158 if isinstance(field, Enumeration): 

159 description += """\ 

160 <br /> 

161 <br /> 

162 Can be set to the following values: 

163 

164 <dl> 

165""" 

166 

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

173 

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

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

176 

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

178 

179 if isinstance(field, Integer): 

180 description += f"""\ 

181 <br /> 

182 <br /> 

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

184""" 

185 

186 html = f""" 

187 <tr> 

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

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

197 

198 return html 

199 

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

208 

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

210 

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

218 

219 if isinstance(field, Enumeration): 

220 return field.default_value.name 

221 

222 if isinstance(field, Integer): 

223 return str(field.default_value) 

224 

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