Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

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

3# 

4# This file is part of the tsfpga project. 

5# https://tsfpga.com 

6# https://gitlab.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9import copy 

10import hashlib 

11import datetime 

12import re 

13from shutil import copy2 

14 

15from pathlib import Path 

16 

17from tsfpga.git_utils import git_commands_are_available, get_git_commit 

18from tsfpga.svn_utils import svn_commands_are_available, get_svn_revision_information 

19from tsfpga.system_utils import create_directory, create_file, read_file 

20from . import __version__ 

21from .constant import Constant 

22from .register import Register 

23from .register_array import RegisterArray 

24from .register_c_generator import RegisterCGenerator 

25from .register_cpp_generator import RegisterCppGenerator 

26from .register_html_generator import RegisterHtmlGenerator 

27from .register_python_generator import RegisterPythonGenerator 

28from .register_vhdl_generator import RegisterVhdlGenerator 

29 

30 

31class RegisterList: 

32 

33 """ 

34 Used to handle the registers of a module. Also known as a register map. 

35 """ 

36 

37 def __init__(self, name, source_definition_file): 

38 """ 

39 Arguments: 

40 name (str): The name of this register list. Typically the name of the module that uses 

41 it. 

42 source_definition_file (`pathlib.Path`): The TOML source file that defined this 

43 register list. Will be displayed in generated source code and documentation 

44 for traceability. 

45 """ 

46 self.name = name 

47 self.source_definition_file = source_definition_file 

48 

49 self.register_objects = [] 

50 self.constants = [] 

51 

52 @classmethod 

53 def from_default_registers(cls, name, source_definition_file, default_registers): 

54 """ 

55 Factory method. Create a RegisterList object from a plain list of registers. 

56 

57 Arguments: 

58 name (str): The name of this register list. 

59 source_definition_file (`pathlib.Path`): The source file that defined this 

60 register list. Will be displayed in generated source code and documentation 

61 for traceability. 

62 

63 Can be set to ``None`` if this information does not make sense in the current 

64 use case. 

65 default_registers (list(.Register)): These registers will be inserted in the 

66 register list. 

67 """ 

68 register_list = cls(name=name, source_definition_file=source_definition_file) 

69 register_list.register_objects = copy.deepcopy(default_registers) 

70 return register_list 

71 

72 def append_register(self, name, mode, description): 

73 """ 

74 Append a register to this list. 

75 

76 Arguments: 

77 name (str): The name of the register. 

78 mode (str): A valid register mode. 

79 description (str): Textual register description. 

80 Return: 

81 :class:`.Register`: The register object that was created. 

82 """ 

83 if self.register_objects: 

84 index = self.register_objects[-1].index + 1 

85 else: 

86 index = 0 

87 

88 register = Register(name, index, mode, description) 

89 self.register_objects.append(register) 

90 

91 return register 

92 

93 def append_register_array(self, name, length, description): 

94 """ 

95 Append a register array to this list. 

96 

97 Arguments: 

98 name (str): The name of the register array. 

99 length (int): The number of times the register sequence shall be repeated. 

100 Return: 

101 :class:`.RegisterArray`: The register array object that was created. 

102 """ 

103 if self.register_objects: 

104 base_index = self.register_objects[-1].index + 1 

105 else: 

106 base_index = 0 

107 register_array = RegisterArray( 

108 name=name, base_index=base_index, length=length, description=description 

109 ) 

110 

111 self.register_objects.append(register_array) 

112 return register_array 

113 

114 def get_register(self, name): 

115 """ 

116 Get a register from this list. Will only find single registers, not registers in a 

117 register array. Will raise exception if no register matches. 

118 

119 Arguments: 

120 name (str): The name of the register. 

121 Return: 

122 :class:`.Register`: The register. 

123 """ 

124 for register_object in self.register_objects: 

125 if isinstance(register_object, Register) and register_object.name == name: 

126 return register_object 

127 

128 raise ValueError(f'Could not find register "{name}" within register list "{self.name}"') 

129 

130 def get_register_array(self, name): 

131 """ 

132 Get a register array from this list. Will raise exception if no register array matches. 

133 

134 Arguments: 

135 name (str): The name of the register array. 

136 Return: 

137 :class:`.RegisterArray`: The register array. 

138 """ 

139 for register_object in self.register_objects: 

140 if isinstance(register_object, RegisterArray) and register_object.name == name: 

141 return register_object 

142 

143 raise ValueError( 

144 f'Could not find register array "{name}" within register list "{self.name}"' 

145 ) 

146 

147 def get_register_index( 

148 self, register_name, register_array_name=None, register_array_index=None 

149 ): 

150 """ 

151 Get the zero-based index within the register list for the specified register. 

152 

153 Arguments: 

154 register_name (str): The name of the register. 

155 register_array_name (str): If the register is within a register array the name 

156 of the array must be specified. 

157 register_array_name (str): If the register is within a register array the array 

158 iteration index must be specified. 

159 

160 Return: 

161 int: The index. 

162 """ 

163 if register_array_name is None and register_array_index is None: 

164 # Target is plain register 

165 register = self.get_register(register_name) 

166 

167 return register.index 

168 

169 # Target is in register array 

170 register_array = self.get_register_array(register_array_name) 

171 register_array_start_index = register_array.get_start_index(register_array_index) 

172 

173 register = register_array.get_register(register_name) 

174 register_index = register.index 

175 

176 return register_array_start_index + register_index 

177 

178 def add_constant(self, name, value, description=None): 

179 """ 

180 Add a constant. Will be available in the generated packages and headers. 

181 

182 Arguments: 

183 name (str): The name of the constant. 

184 length (int): The constant value (signed). 

185 description (str): Textual description for the constant. 

186 Return: 

187 :class:`.Constant`: The constant object that was created. 

188 """ 

189 constant = Constant(name, value, description) 

190 self.constants.append(constant) 

191 return constant 

192 

193 def get_constant(self, name): 

194 """ 

195 Get a constant from this list. Will raise exception if no constant matches. 

196 

197 Arguments: 

198 name (str): The name of the constant. 

199 Return: 

200 :class:`.Constant`: The constant. 

201 """ 

202 for constant in self.constants: 

203 if constant.name == name: 

204 return constant 

205 

206 raise ValueError(f'Could not find constant "{name}" within register list "{self.name}"') 

207 

208 def create_vhdl_package(self, output_path): 

209 """ 

210 Create a VHDL package file with register and field definitions. 

211 

212 This function assumes that the ``output_path`` folder already exists. This assumption makes 

213 it slightly faster than the other functions that use ``create_file()``. Necessary since this 

214 one is often used in real time (before simulations, etc..) and not in one-off scenarios 

215 like the others (when making a release). 

216 

217 In order to save time, there is a mechanism to only generate the VHDL file when necessary. 

218 A hash of this register list object will be written to the file along with all the register 

219 definitions. This hash will be inspected and compared, and the VHDL file will only be 

220 generated again if something has changed. 

221 

222 Arguments: 

223 output_path (`pathlib.Path`): Result will be placed here. 

224 """ 

225 vhd_file = output_path / (self.name + "_regs_pkg.vhd") 

226 

227 self_hash = self._hash() 

228 if self._should_create_vhdl_package(vhd_file, self_hash): 

229 self._create_vhdl_package(vhd_file, self_hash) 

230 

231 def _should_create_vhdl_package(self, vhd_file, self_hash): 

232 if not vhd_file.exists(): 

233 return True 

234 if (self_hash, __version__) != self._find_hash_and_version_of_existing_vhdl_package( 

235 vhd_file 

236 ): 

237 return True 

238 return False 

239 

240 @staticmethod 

241 def _find_hash_and_version_of_existing_vhdl_package(vhd_file): 

242 """ 

243 Returns `None` if nothing found, otherwise the matching strings in a tuple. 

244 """ 

245 regexp = re.compile( 

246 r"\n-- Register hash ([0-9a-f]+), generator version (\d+\.\d+\.\d+)\.\n" 

247 ) 

248 existing_file_content = read_file(vhd_file) 

249 match = regexp.search(existing_file_content) 

250 if match is None: 

251 return None 

252 return match.group(1), match.group(2) 

253 

254 def _create_vhdl_package(self, vhd_file, self_hash): 

255 print(f"Creating VHDL register package {vhd_file}") 

256 # Add a header line with the hash 

257 generated_info = self.generated_source_info() + [ 

258 f"Register hash {self_hash}, generator version {__version__}." 

259 ] 

260 register_vhdl_generator = RegisterVhdlGenerator(self.name, generated_info) 

261 with vhd_file.open("w") as file_handle: 

262 file_handle.write( 

263 register_vhdl_generator.get_package(self.register_objects, self.constants) 

264 ) 

265 

266 def create_c_header(self, output_path): 

267 """ 

268 Create a C header file with register and field definitions. 

269 

270 Arguments: 

271 output_path (`pathlib.Path`): Result will be placed here. 

272 """ 

273 output_file = output_path / (self.name + "_regs.h") 

274 register_c_generator = RegisterCGenerator(self.name, self.generated_source_info()) 

275 create_file( 

276 output_file, register_c_generator.get_header(self.register_objects, self.constants) 

277 ) 

278 

279 def create_cpp_interface(self, output_path): 

280 """ 

281 Create a C++ class interface header file, with register and field definitions. The 

282 interface header contains only virtual methods. 

283 

284 Arguments: 

285 output_path (`pathlib.Path`): Result will be placed here. 

286 """ 

287 output_file = output_path / ("i_" + self.name + ".h") 

288 register_cpp_generator = RegisterCppGenerator(self.name, self.generated_source_info()) 

289 create_file( 

290 output_file, register_cpp_generator.get_interface(self.register_objects, self.constants) 

291 ) 

292 

293 def create_cpp_header(self, output_path): 

294 """ 

295 Create a C++ class header file. 

296 

297 Arguments: 

298 output_path (`pathlib.Path`): Result will be placed here. 

299 """ 

300 output_file = output_path / (self.name + ".h") 

301 register_cpp_generator = RegisterCppGenerator(self.name, self.generated_source_info()) 

302 create_file(output_file, register_cpp_generator.get_header(self.register_objects)) 

303 

304 def create_cpp_implementation(self, output_path): 

305 """ 

306 Create a C++ class implementation file. 

307 

308 Arguments: 

309 output_path (`pathlib.Path`): Result will be placed here. 

310 """ 

311 output_file = output_path / (self.name + ".cpp") 

312 register_cpp_generator = RegisterCppGenerator(self.name, self.generated_source_info()) 

313 create_file(output_file, register_cpp_generator.get_implementation(self.register_objects)) 

314 

315 def create_html_page(self, output_path): 

316 """ 

317 Create a documentation HTML page with register and field information. Will include the 

318 tables created by :meth:`.create_html_register_table` and 

319 :meth:`.create_html_constant_table`. 

320 

321 Arguments: 

322 output_path (`pathlib.Path`): Result will be placed here. 

323 """ 

324 register_html_generator = RegisterHtmlGenerator(self.name, self.generated_source_info()) 

325 

326 html_file = output_path / (self.name + "_regs.html") 

327 create_file( 

328 html_file, register_html_generator.get_page(self.register_objects, self.constants) 

329 ) 

330 

331 stylesheet = register_html_generator.get_page_style() 

332 stylesheet_file = output_path / "regs_style.css" 

333 if (not stylesheet_file.exists()) or read_file(stylesheet_file) != stylesheet: 

334 # Create the file only once. This mechanism could be made more smart, but at the moment 

335 # there is no use case. Perhaps there should be a separate stylesheet for each 

336 # HTML file? 

337 create_file(stylesheet_file, stylesheet) 

338 

339 def create_html_register_table(self, output_path): 

340 """ 

341 Create documentation HTML table with register and field information. 

342 

343 Arguments: 

344 output_path (`pathlib.Path`): Result will be placed here. 

345 """ 

346 output_file = output_path / (self.name + "_register_table.html") 

347 register_html_generator = RegisterHtmlGenerator(self.name, self.generated_source_info()) 

348 create_file(output_file, register_html_generator.get_register_table(self.register_objects)) 

349 

350 def create_html_constant_table(self, output_path): 

351 """ 

352 Create documentation HTML table with constant information. 

353 

354 Arguments: 

355 output_path (`pathlib.Path`): Result will be placed here. 

356 """ 

357 output_file = output_path / (self.name + "_constant_table.html") 

358 register_html_generator = RegisterHtmlGenerator(self.name, self.generated_source_info()) 

359 create_file(output_file, register_html_generator.get_constant_table(self.constants)) 

360 

361 def create_python_class(self, output_path): 

362 """ 

363 Save a python class with all register and constant information. 

364 

365 Arguments: 

366 output_path (`pathlib.Path`): Result will be placed here. 

367 """ 

368 register_python_generator = RegisterPythonGenerator(self.name, self.generated_source_info()) 

369 register_python_generator.create_class(register_list=self, output_folder=output_path) 

370 

371 def copy_source_definition(self, output_path): 

372 """ 

373 Copy the source file that created this register list. If no source file is set, nothing will 

374 be copied. 

375 

376 Arguments: 

377 output_path (`pathlib.Path`): Result will be placed here. 

378 """ 

379 if self.source_definition_file is not None: 

380 create_directory(output_path, empty=False) 

381 copy2(self.source_definition_file, output_path) 

382 

383 @staticmethod 

384 def generated_info(): 

385 """ 

386 Return: 

387 list(str): Line(s) informing the user that a file is automatically generated. 

388 """ 

389 return ["This file is automatically generated by tsfpga."] 

390 

391 def generated_source_info(self): 

392 """ 

393 Return: 

394 list(str): Line(s) informing the user that a file is automatically generated, containing 

395 info about the source of the generated register information. 

396 """ 

397 time_info = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") 

398 

399 file_info = "" 

400 if self.source_definition_file is not None: 

401 file_info = f" from file {self.source_definition_file.name}" 

402 

403 commit_info = "" 

404 if git_commands_are_available(directory=Path(".")): 

405 commit_info = f" at commit {get_git_commit(directory=Path('.'))}" 

406 elif svn_commands_are_available(): 

407 commit_info = f" at revision {get_svn_revision_information()}" 

408 

409 info = f"Generated {time_info}{file_info}{commit_info}." 

410 

411 return self.generated_info() + [info] 

412 

413 def _hash(self): 

414 """ 

415 Get a hash of this object representation. SHA1 is the fastest method according to e.g. 

416 http://atodorov.org/blog/2013/02/05/performance-test-md5-sha1-sha256-sha512/ 

417 Result is a lowercase hexadecimal string. 

418 """ 

419 return hashlib.sha1(repr(self).encode()).hexdigest() 

420 

421 def __repr__(self): 

422 return f"""{self.__class__.__name__}(\ 

423name={self.name},\ 

424source_definition_file={repr(self.source_definition_file)},\ 

425register_objects={','.join([repr(register_object) for register_object in self.register_objects])},\ 

426constants={','.join([repr(constant) for constant in self.constants])},\ 

427)"""