Coverage for hdl_registers/register_html_generator.py: 100%

99 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-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/tsfpga/hdl_registers 

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

9 

10from .register import Register, REGISTER_MODES 

11from .html_translator import HtmlTranslator 

12 

13 

14class RegisterHtmlGenerator: 

15 """ 

16 Generate a HTML page with register information. 

17 """ 

18 

19 def __init__(self, module_name, generated_info): 

20 """ 

21 Arguments: 

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

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

24 """ 

25 self.module_name = module_name 

26 self.generated_info = generated_info 

27 self._html_translator = HtmlTranslator() 

28 

29 def get_register_table(self, register_objects): 

30 """ 

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

32 

33 Arguments: 

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

35 

36 Returns: 

37 str: HTML code. 

38 """ 

39 if not register_objects: 

40 return "" 

41 

42 html = self._file_header() 

43 html += self._get_register_table(register_objects) 

44 return html 

45 

46 def get_constant_table(self, constants): 

47 """ 

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

49 

50 Arguments: 

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

52 

53 Returns: 

54 str: HTML code. 

55 """ 

56 if not constants: 

57 return "" 

58 

59 html = self._file_header() 

60 html += self._get_constant_table(constants) 

61 return html 

62 

63 def get_page(self, register_objects, constants): 

64 """ 

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

66 

67 Arguments: 

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

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

70 

71 Returns: 

72 str: HTML code. 

73 """ 

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

75 html = f"""\ 

76{self._file_header()} 

77 

78<!DOCTYPE html> 

79<html> 

80<head> 

81 <title>{title}</title> 

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

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

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

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

86 <style> 

87 {self.get_page_style()} 

88 </style> 

89</head> 

90<body> 

91 <h1>{title}</h1> 

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

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

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

95 <h2>Register modes</h2> 

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

97{self._get_mode_descriptions()} 

98""" 

99 

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

101 if register_objects: 

102 html += f""" 

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

104{self._get_register_table(register_objects)} 

105""" 

106 else: 

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

108 

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

110 if constants: 

111 html += f""" 

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

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

114 else: 

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

116 

117 html += """ 

118</body> 

119</html>""" 

120 

121 return html 

122 

123 @staticmethod 

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

125 """ 

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

127 

128 Returns: 

129 str: CSS code. 

130 """ 

131 if font_style is None: 

132 font_style = """ 

133html * { 

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

135}""" 

136 

137 if table_style is None: 

138 table_style = """ 

139table { 

140 border-collapse: collapse; 

141} 

142td, th { 

143 border-width: 1px; 

144 border-style: solid; 

145 border-color: #ddd; 

146 padding: 8px; 

147} 

148td.array_header { 

149 border-top-width: 10px; 

150 border-top-color: #4cacaf; 

151} 

152td.array_footer { 

153 border-bottom-width: 10px; 

154 border-bottom-color: #4cacaf; 

155} 

156tr:nth-child(even) { 

157 background-color: #f2f2f2; 

158} 

159th { 

160 padding-top: 12px; 

161 padding-bottom: 12px; 

162 text-align: left; 

163 background-color: #4CAF50; 

164 color: white; 

165}""" 

166 

167 style = f""" 

168{font_style} 

169{table_style} 

170{extra_style}""" 

171 return style 

172 

173 @staticmethod 

174 def _comment(comment): 

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

176 

177 def _file_header(self): 

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

179 

180 @staticmethod 

181 def _to_hex_string(value, num_nibbles=4): 

182 if value < 0: 

183 return "N/A" 

184 

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

186 return formatting_string.format(value) 

187 

188 def _annotate_register_array(self, register_object): 

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

190 html = f""" 

191 <tr> 

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

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

194repeated {register_object.length} times. 

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

196 </td> 

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

198 </tr>""" 

199 array_index_increment = len(register_object.registers) 

200 for register in register_object.registers: 

201 register_index = register_object.base_index + register.index 

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

203 

204 html += f""" 

205 <tr> 

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

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

208 </td> 

209 </tr>""" 

210 return html 

211 

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

213 if register_array_index is None: 

214 address_readable = self._to_hex_string(register.address) 

215 index = register.address // 4 

216 else: 

217 register_address = self._to_hex_string(4 * register_array_index) 

218 address_increment = self._to_hex_string(4 * array_index_increment) 

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

220 

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

222 

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

224 html = f""" 

225 <tr> 

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

227 <td>{index}</td> 

228 <td>{address_readable}</td> 

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

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

231 <td>{description}</td> 

232 </tr>""" 

233 

234 for field in register.fields: 

235 html += self._annotate_field(field) 

236 

237 return html 

238 

239 def _annotate_field(self, field): 

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

241 html = f""" 

242 <tr> 

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

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

245 <td></td> 

246 <td></td> 

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

248 <td>{description}</td> 

249 </tr>""" 

250 

251 return html 

252 

253 def _get_register_table(self, register_objects): 

254 html = """ 

255<table> 

256<thead> 

257 <tr> 

258 <th>Name</th> 

259 <th>Index</th> 

260 <th>Address</th> 

261 <th>Mode</th> 

262 <th>Default value</th> 

263 <th>Description</th> 

264 </tr> 

265</thead> 

266<tbody>""" 

267 

268 for register_object in register_objects: 

269 if isinstance(register_object, Register): 

270 html += self._annotate_register(register_object) 

271 else: 

272 html += self._annotate_register_array(register_object) 

273 

274 html += """ 

275</tbody> 

276</table>""" 

277 

278 return html 

279 

280 def _get_constant_table(self, constants): 

281 html = """ 

282<table> 

283<thead> 

284 <tr> 

285 <th>Name</th> 

286 <th>Value (decimal)</th> 

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

288 <th>Description</th> 

289 </tr> 

290</thead> 

291<tbody>""" 

292 

293 for constant in constants: 

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

295 html += f""" 

296 <tr> 

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

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

299 <td>{self._to_hex_string(constant.value, num_nibbles=8)}</td> 

300 <td>{description}</td> 

301 </tr>""" 

302 

303 html += """ 

304</tbody> 

305</table>""" 

306 return html 

307 

308 @staticmethod 

309 def _get_mode_descriptions(): 

310 html = """ 

311<table> 

312<thead> 

313 <tr> 

314 <th>Mode</th> 

315 <th>Description</th> 

316 </tr> 

317</thead> 

318<tbody>""" 

319 

320 for mode in REGISTER_MODES.values(): 

321 html += f""" 

322<tr> 

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

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

325</tr> 

326""" 

327 html += """ 

328</tbody> 

329</table>""" 

330 return html