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 import DEFAULT_FILE_ENCODING 

18from tsfpga.git_utils import git_commands_are_available, get_git_commit 

19from tsfpga.svn_utils import svn_commands_are_available, get_svn_revision_information 

20from tsfpga.system_utils import create_directory, create_file, read_file 

21from . import __version__ 

22from .constant import Constant 

23from .register import Register 

24from .register_array import RegisterArray 

25from .register_c_generator import RegisterCGenerator 

26from .register_cpp_generator import RegisterCppGenerator 

27from .register_html_generator import RegisterHtmlGenerator 

28from .register_python_generator import RegisterPythonGenerator 

29from .register_vhdl_generator import RegisterVhdlGenerator 

30 

31 

32class RegisterList: 

33 

34 """ 

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

36 """ 

37 

38 def __init__(self, name, source_definition_file): 

39 """ 

40 Arguments: 

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

42 it. 

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

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

45 for traceability. 

46 """ 

47 self.name = name 

48 self.source_definition_file = source_definition_file 

49 

50 self.register_objects = [] 

51 self.constants = [] 

52 

53 @classmethod 

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

55 """ 

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

57 

58 Arguments: 

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

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

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

62 for traceability. 

63 

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

65 use case. 

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

67 register list. 

68 """ 

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

70 register_list.register_objects = copy.deepcopy(default_registers) 

71 return register_list 

72 

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

74 """ 

75 Append a register to this list. 

76 

77 Arguments: 

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

79 mode (str): A valid register mode. 

80 description (str): Textual register description. 

81 Return: 

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

83 """ 

84 if self.register_objects: 

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

86 else: 

87 index = 0 

88 

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

90 self.register_objects.append(register) 

91 

92 return register 

93 

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

95 """ 

96 Append a register array to this list. 

97 

98 Arguments: 

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

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

101 Return: 

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

103 """ 

104 if self.register_objects: 

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

106 else: 

107 base_index = 0 

108 register_array = RegisterArray( 

109 name=name, base_index=base_index, length=length, description=description 

110 ) 

111 

112 self.register_objects.append(register_array) 

113 return register_array 

114 

115 def get_register(self, name): 

116 """ 

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

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

119 

120 Arguments: 

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

122 Return: 

123 :class:`.Register`: The register. 

124 """ 

125 for register_object in self.register_objects: 

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

127 return register_object 

128 

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

130 

131 def get_register_array(self, name): 

132 """ 

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

134 

135 Arguments: 

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

137 Return: 

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

139 """ 

140 for register_object in self.register_objects: 

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

142 return register_object 

143 

144 raise ValueError( 

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

146 ) 

147 

148 def get_register_index( 

149 self, register_name, register_array_name=None, register_array_index=None 

150 ): 

151 """ 

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

153 

154 Arguments: 

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

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

157 of the array must be specified. 

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

159 iteration index must be specified. 

160 

161 Return: 

162 int: The index. 

163 """ 

164 if register_array_name is None and register_array_index is None: 

165 # Target is plain register 

166 register = self.get_register(register_name) 

167 

168 return register.index 

169 

170 # Target is in register array 

171 register_array = self.get_register_array(register_array_name) 

172 register_array_start_index = register_array.get_start_index(register_array_index) 

173 

174 register = register_array.get_register(register_name) 

175 register_index = register.index 

176 

177 return register_array_start_index + register_index 

178 

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

180 """ 

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

182 

183 Arguments: 

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

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

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

187 Return: 

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

189 """ 

190 constant = Constant(name, value, description) 

191 self.constants.append(constant) 

192 return constant 

193 

194 def get_constant(self, name): 

195 """ 

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

197 

198 Arguments: 

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

200 Return: 

201 :class:`.Constant`: The constant. 

202 """ 

203 for constant in self.constants: 

204 if constant.name == name: 

205 return constant 

206 

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

208 

209 def create_vhdl_package(self, output_path): 

210 """ 

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

212 

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

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

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

216 like the others (when making a release). 

217 

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

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

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

221 generated again if something has changed. 

222 

223 Arguments: 

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

225 """ 

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

227 

228 self_hash = self._hash() 

229 if self._should_create_vhdl_package(vhd_file, self_hash): 

230 self._create_vhdl_package(vhd_file, self_hash) 

231 

232 def _should_create_vhdl_package(self, vhd_file, self_hash): 

233 if not vhd_file.exists(): 

234 return True 

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

236 vhd_file 

237 ): 

238 return True 

239 return False 

240 

241 @staticmethod 

242 def _find_hash_and_version_of_existing_vhdl_package(vhd_file): 

243 """ 

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

245 """ 

246 regexp = re.compile( 

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

248 ) 

249 existing_file_content = read_file(vhd_file) 

250 match = regexp.search(existing_file_content) 

251 if match is None: 

252 return None 

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

254 

255 def _create_vhdl_package(self, vhd_file, self_hash): 

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

257 # Add a header line with the hash 

258 generated_info = self.generated_source_info() + [ 

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

260 ] 

261 register_vhdl_generator = RegisterVhdlGenerator(self.name, generated_info) 

262 with open(vhd_file, "w", encoding=DEFAULT_FILE_ENCODING) as file_handle: 

263 file_handle.write( 

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

265 ) 

266 

267 def create_c_header(self, output_path): 

268 """ 

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

270 

271 Arguments: 

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

273 """ 

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

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

276 create_file( 

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

278 ) 

279 

280 def create_cpp_interface(self, output_path): 

281 """ 

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

283 interface header contains only virtual methods. 

284 

285 Arguments: 

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

287 """ 

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

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

290 create_file( 

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

292 ) 

293 

294 def create_cpp_header(self, output_path): 

295 """ 

296 Create a C++ class header file. 

297 

298 Arguments: 

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

300 """ 

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

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

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

304 

305 def create_cpp_implementation(self, output_path): 

306 """ 

307 Create a C++ class implementation file. 

308 

309 Arguments: 

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

311 """ 

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

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

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

315 

316 def create_html_page(self, output_path): 

317 """ 

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

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

320 :meth:`.create_html_constant_table`. 

321 

322 Arguments: 

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

324 """ 

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

326 

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

328 create_file( 

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

330 ) 

331 

332 stylesheet = register_html_generator.get_page_style() 

333 stylesheet_file = output_path / "regs_style.css" 

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

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

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

337 # HTML file? 

338 create_file(stylesheet_file, stylesheet) 

339 

340 def create_html_register_table(self, output_path): 

341 """ 

342 Create documentation HTML table with register and field information. 

343 

344 Arguments: 

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

346 """ 

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

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

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

350 

351 def create_html_constant_table(self, output_path): 

352 """ 

353 Create documentation HTML table with constant information. 

354 

355 Arguments: 

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

357 """ 

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

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

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

361 

362 def create_python_class(self, output_path): 

363 """ 

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

365 

366 Arguments: 

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

368 """ 

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

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

371 

372 def copy_source_definition(self, output_path): 

373 """ 

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

375 be copied. 

376 

377 Arguments: 

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

379 """ 

380 if self.source_definition_file is not None: 

381 create_directory(output_path, empty=False) 

382 copy2(self.source_definition_file, output_path) 

383 

384 @staticmethod 

385 def generated_info(): 

386 """ 

387 Return: 

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

389 """ 

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

391 

392 def generated_source_info(self): 

393 """ 

394 Return: 

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

396 info about the source of the generated register information. 

397 """ 

398 # Default to the user's current working directory 

399 directory = Path(".") 

400 

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

402 

403 file_info = "" 

404 if self.source_definition_file is not None: 

405 directory = self.source_definition_file.parent 

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

407 

408 commit_info = "" 

409 if git_commands_are_available(directory): 

410 commit_info = f" at commit {get_git_commit(directory)}" 

411 elif svn_commands_are_available(directory): 

412 commit_info = f" at revision {get_svn_revision_information(directory)}" 

413 

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

415 

416 return self.generated_info() + [info] 

417 

418 def _hash(self): 

419 """ 

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

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

422 Result is a lowercase hexadecimal string. 

423 """ 

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

425 

426 def __repr__(self): 

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

428name={self.name},\ 

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

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

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

432)"""