Coverage for hdl_registers/register_html_generator.py: 99%

105 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-29 22:03 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the hdl_registers project, a HDL register generator fast enough to be run 

5# in real time. 

6# https://hdl-registers.com 

7# https://gitlab.com/hdl_registers/hdl_registers 

8# -------------------------------------------------------------------------------------------------- 

9 

10# Local folder libraries 

11from .html_translator import HtmlTranslator 

12from .register import REGISTER_MODES, Register 

13 

14 

15class RegisterHtmlGenerator: 

16 """ 

17 Generate a HTML page with register information. 

18 """ 

19 

20 def __init__(self, module_name, generated_info): 

21 """ 

22 Arguments: 

23 module_name (str): The name of the register map. 

24 generated_info (list(str)): Will be placed in the file headers. 

25 """ 

26 self.module_name = module_name 

27 self.generated_info = generated_info 

28 self._html_translator = HtmlTranslator() 

29 

30 def get_register_table(self, register_objects): 

31 """ 

32 Get a HTML table with register infomation. Can be included in other documents. 

33 

34 Arguments: 

35 register_objects (list): Register arrays and registers to be included. 

36 

37 Returns: 

38 str: HTML code. 

39 """ 

40 if not register_objects: 

41 return "" 

42 

43 html = self._file_header() 

44 html += self._get_register_table(register_objects) 

45 return html 

46 

47 def get_constant_table(self, constants): 

48 """ 

49 Get a HTML table with constant infomation. Can be included in other documents. 

50 

51 Arguments: 

52 constants (list(Constant)): Constants to be included. 

53 

54 Returns: 

55 str: HTML code. 

56 """ 

57 if not constants: 

58 return "" 

59 

60 html = self._file_header() 

61 html += self._get_constant_table(constants) 

62 return html 

63 

64 def get_page(self, register_objects, constants): 

65 """ 

66 Get a complete HTML page with register and constant infomation. 

67 

68 Arguments: 

69 register_objects (list): Register arrays and registers to be included. 

70 constants (list(Constant)): Constants to be included. 

71 

72 Returns: 

73 str: HTML code. 

74 """ 

75 title = f"Documentation of {self.module_name} registers" 

76 html = f"""\ 

77{self._file_header()} 

78 

79<!DOCTYPE html> 

80<html> 

81<head> 

82 <title>{title}</title> 

83 <!-- Include the style both inline and as a link to a separate CSS file. --> 

84 <!-- Some tools, e.g. Jenkins, will not render with an inline styleesheet. --> 

85 <!-- For other tools, e.g. page inclusion in sphinx, the style must be in the file. --> 

86 <link rel="stylesheet" href="regs_style.css"> 

87 <style> 

88 {self.get_page_style()} 

89 </style> 

90</head> 

91<body> 

92 <h1>{title}</h1> 

93 <p>This document is a specification for the register interface of the FPGA module \ 

94<b>{self.module_name}</b>.</p> 

95 <p>{' '.join(self.generated_info)}</p> 

96 <h2>Register modes</h2> 

97 <p>The following register modes are available.</p> 

98{self._get_mode_descriptions()} 

99""" 

100 

101 html += " <h2>Registers</h2>\n" 

102 if register_objects: 

103 html += f""" 

104 <p>The following registers make up the register map.</p> 

105{self._get_register_table(register_objects)} 

106""" 

107 else: 

108 html += " <p>This module does not have any registers.</p>" 

109 

110 html += " <h2>Constants</h2>\n" 

111 if constants: 

112 html += f""" 

113 <p>The following constants are part of the register interface.</p> 

114{self._get_constant_table(constants)}""" 

115 else: 

116 html += " <p>This module does not have any constants.</p>" 

117 

118 html += """ 

119</body> 

120</html>""" 

121 

122 return html 

123 

124 @staticmethod 

125 def get_page_style(table_style=None, font_style=None, extra_style=""): 

126 """ 

127 Get a CSS style for the register pages. Shall be saved to a file called ``regs_style.css``. 

128 

129 Returns: 

130 str: CSS code. 

131 """ 

132 if font_style is None: 

133 font_style = """ 

134html * { 

135 font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; 

136}""" 

137 

138 if table_style is None: 

139 table_style = """ 

140table { 

141 border-collapse: collapse; 

142} 

143td, th { 

144 border-width: 1px; 

145 border-style: solid; 

146 border-color: #ddd; 

147 padding: 8px; 

148} 

149td.array_header { 

150 border-top-width: 10px; 

151 border-top-color: #4cacaf; 

152} 

153td.array_footer { 

154 border-bottom-width: 10px; 

155 border-bottom-color: #4cacaf; 

156} 

157tr:nth-child(even) { 

158 background-color: #f2f2f2; 

159} 

160th { 

161 padding-top: 12px; 

162 padding-bottom: 12px; 

163 text-align: left; 

164 background-color: #4CAF50; 

165 color: white; 

166}""" 

167 

168 style = f""" 

169{font_style} 

170{table_style} 

171{extra_style}""" 

172 return style 

173 

174 @staticmethod 

175 def _comment(comment): 

176 return f"<!-- {comment} -->\n" 

177 

178 def _file_header(self): 

179 return "".join([self._comment(header_line) for header_line in self.generated_info]) 

180 

181 @staticmethod 

182 def _to_hex_string(value, num_nibbles=4): 

183 """ 

184 Convert an integer value to a hexadecimal string. E.g. "0x1000". 

185 """ 

186 if value < 0: 

187 return "-" 

188 

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

190 return formatting_string.format(value) 

191 

192 def _annotate_register_array(self, register_object): 

193 description = self._html_translator.translate(register_object.description) 

194 html = f""" 

195 <tr> 

196 <td class="array_header" colspan=5> 

197 Register array <strong>{register_object.name}</strong>, \ 

198repeated {register_object.length} times. 

199 Iterator <i>i &isin; [0, {register_object.length - 1}].</i> 

200 </td> 

201 <td class="array_header">{description}</td> 

202 </tr>""" 

203 array_index_increment = len(register_object.registers) 

204 for register in register_object.registers: 

205 register_index = register_object.base_index + register.index 

206 html += self._annotate_register(register, register_index, array_index_increment) 

207 

208 html += f""" 

209 <tr> 

210 <td colspan="6" class="array_footer"> 

211 End register array <strong>{register_object.name}.</strong> 

212 </td> 

213 </tr>""" 

214 return html 

215 

216 def _annotate_register(self, register, register_array_index=None, array_index_increment=None): 

217 if register_array_index is None: 

218 address_readable = self._to_hex_string(register.address) 

219 index = register.address // 4 

220 else: 

221 register_address = self._to_hex_string(4 * register_array_index) 

222 address_increment = self._to_hex_string(4 * array_index_increment) 

223 address_readable = f"{register_address} + i &times; {address_increment}" 

224 

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

226 

227 description = self._html_translator.translate(register.description) 

228 html = f""" 

229 <tr> 

230 <td><strong>{register.name}</strong></td> 

231 <td>{index}</td> 

232 <td>{address_readable}</td> 

233 <td>{REGISTER_MODES[register.mode].mode_readable}</td> 

234 <td>{self._to_hex_string(register.default_value, num_nibbles=1)}</td> 

235 <td>{description}</td> 

236 </tr>""" 

237 

238 for field in register.fields: 

239 html += self._annotate_field(field) 

240 

241 return html 

242 

243 def _annotate_field(self, field): 

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

245 html = f""" 

246 <tr> 

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

248 <td>&nbsp;&nbsp;{field.range}</td> 

249 <td></td> 

250 <td></td> 

251 <td>{field.default_value_str}</td> 

252 <td>{description}</td> 

253 </tr>""" 

254 

255 return html 

256 

257 def _get_register_table(self, register_objects): 

258 html = """ 

259<table> 

260<thead> 

261 <tr> 

262 <th>Name</th> 

263 <th>Index</th> 

264 <th>Address</th> 

265 <th>Mode</th> 

266 <th>Default value</th> 

267 <th>Description</th> 

268 </tr> 

269</thead> 

270<tbody>""" 

271 

272 for register_object in register_objects: 

273 if isinstance(register_object, Register): 

274 html += self._annotate_register(register_object) 

275 else: 

276 html += self._annotate_register_array(register_object) 

277 

278 html += """ 

279</tbody> 

280</table>""" 

281 

282 return html 

283 

284 def _format_hex_constant(self, constant): 

285 if constant.is_integer: 

286 return self._to_hex_string(value=constant.value, num_nibbles=8) 

287 

288 # No hex formatting available for the other types 

289 if constant.is_boolean or constant.is_float: 

290 return "-" 

291 

292 raise ValueError(f"Got unexpected constant type. {constant}") 

293 

294 def _get_constant_table(self, constants): 

295 html = """ 

296<table> 

297<thead> 

298 <tr> 

299 <th>Name</th> 

300 <th>Value</th> 

301 <th>Value (hexadecimal)</th> 

302 <th>Description</th> 

303 </tr> 

304</thead> 

305<tbody>""" 

306 

307 for constant in constants: 

308 description = self._html_translator.translate(constant.description) 

309 html += f""" 

310 <tr> 

311 <td><strong>{constant.name}</strong></td> 

312 <td>{constant.value}</td> 

313 <td>{self._format_hex_constant(constant=constant)}</td> 

314 <td>{description}</td> 

315 </tr>""" 

316 

317 html += """ 

318</tbody> 

319</table>""" 

320 return html 

321 

322 @staticmethod 

323 def _get_mode_descriptions(): 

324 html = """ 

325<table> 

326<thead> 

327 <tr> 

328 <th>Mode</th> 

329 <th>Description</th> 

330 </tr> 

331</thead> 

332<tbody>""" 

333 

334 for mode in REGISTER_MODES.values(): 

335 html += f""" 

336<tr> 

337 <td>{mode.mode_readable}</td> 

338 <td>{mode.description}</td> 

339</tr> 

340""" 

341 html += """ 

342</tbody> 

343</table>""" 

344 return html