Coverage for hdl_registers/generator/vhdl/record_package.py: 99%

204 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_mode import HardwareAccessDirection, SoftwareAccessDirection 

20 

21# Local folder libraries 

22from .vhdl_generator_common import VhdlGeneratorCommon 

23 

24if TYPE_CHECKING: 

25 # First party libraries 

26 from hdl_registers.register import Register 

27 from hdl_registers.register_array import RegisterArray 

28 

29 

30class VhdlRecordPackageGenerator(VhdlGeneratorCommon): 

31 """ 

32 Generate a VHDL package with register record types containing natively-typed members for 

33 each register field. 

34 See the :ref:`generator_vhdl` article for usage details. 

35 

36 * For each register, plain or in array, a record with natively-typed members for each 

37 register field. 

38 * For each register array, a correctly-ranged array of records for the registers in 

39 that array. 

40 * Combined record with all the registers and register arrays. 

41 One each for registers in the up direction and in the down direction. 

42 * Constants with default values for all of the above types. 

43 * Conversion functions to/from ``std_logic_vector`` representation for all of the 

44 above types. 

45 

46 The generated VHDL file needs also the generated package 

47 from :class:`.VhdlRegisterPackageGenerator`. 

48 """ 

49 

50 __version__ = "1.0.0" 

51 

52 SHORT_DESCRIPTION = "VHDL record package" 

53 

54 @property 

55 def output_file(self) -> Path: 

56 """ 

57 Result will be placed in this file. 

58 """ 

59 return self.output_folder / f"{self.name}_register_record_pkg.vhd" 

60 

61 def create(self, **kwargs: Any) -> Path: 

62 """ 

63 See super class for API details. 

64 

65 Overloaded here because this package file shall only be created if the register list 

66 actually has any registers. 

67 """ 

68 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

69 

70 def get_code(self, **kwargs: Any) -> str: 

71 """ 

72 Get a complete VHDL package with register record types. 

73 """ 

74 package_name = self.output_file.stem 

75 

76 vhdl = f"""\ 

77{self.header} 

78library ieee; 

79use ieee.fixed_pkg.all; 

80use ieee.std_logic_1164.all; 

81use ieee.numeric_std.all; 

82 

83library reg_file; 

84use reg_file.reg_file_pkg.reg_t; 

85 

86use work.{self.name}_regs_pkg.all; 

87 

88 

89package {package_name} is 

90 

91""" 

92 

93 if self.register_list.register_objects: 

94 vhdl += f"""\ 

95{self._register_field_records()}\ 

96{self._register_records()}\ 

97{self._register_was_accessed()}\ 

98""" 

99 

100 vhdl += "end package;\n" 

101 

102 if self.register_list.register_objects: 

103 vhdl += f""" 

104package body {package_name} is 

105 

106{self._register_field_record_conversion_implementations()}\ 

107{self._register_record_conversion_implementations()}\ 

108{self._register_was_accessed_conversion_implementations()}\ 

109end package body; 

110""" 

111 

112 return vhdl 

113 

114 def _register_field_records(self) -> str: 

115 """ 

116 For every register (plain or in array) that has at least one field: 

117 

118 * Record with members for each field that are of the correct native VHDL type. 

119 * Default value constant for the above record. 

120 * Function to convert the above record to SLV. 

121 * Function to convert a register SLV to the above record. 

122 """ 

123 vhdl = """\ 

124 -- ----------------------------------------------------------------------------- 

125 -- Record with correctly-typed members for each field in each register. 

126""" 

127 

128 for register, register_array in self.iterate_registers(): 

129 if not register.fields: 

130 continue 

131 

132 register_name = self.qualified_register_name( 

133 register=register, register_array=register_array 

134 ) 

135 register_description = self.register_description( 

136 register=register, register_array=register_array 

137 ) 

138 

139 record = "" 

140 init = [] 

141 

142 for field in register.fields: 

143 field_name = self.qualified_field_name( 

144 register=register, register_array=register_array, field=field 

145 ) 

146 init.append(f"{field.name} => {field_name}_init") 

147 

148 field_type_name = self.field_type_name( 

149 register=register, register_array=register_array, field=field 

150 ) 

151 record += f" {field.name} : {field_type_name};\n" 

152 

153 init_str = " " + ",\n ".join(init) 

154 

155 vhdl += f"""\ 

156 -- Fields in the {register_description} as a record. 

157 type {register_name}_t is record 

158{record}\ 

159 end record; 

160 -- Default value for the {register_description} as a record. 

161 constant {register_name}_init : {register_name}_t := ( 

162{init_str} 

163 ); 

164 -- Convert a record of the {register_description} to SLV. 

165 function to_slv(data : {register_name}_t) return reg_t; 

166 -- Convert an SLV register value to the record for the {register_description}. 

167 function to_{register_name}(data : reg_t) return {register_name}_t; 

168 

169""" 

170 

171 return vhdl 

172 

173 def _register_records(self) -> str: 

174 """ 

175 Get two records, 

176 * One with all the registers that are in the 'up' direction. 

177 * One with all the registers that are in the 'down' direction. 

178 

179 Along with conversion function declarations to/from SLV. 

180 

181 In order to create the above records, we have to create partial-array records for each 

182 register array. 

183 One record for 'up' and one for 'down', with all registers in the array that are in 

184 that direction. 

185 """ 

186 vhdl = "" 

187 

188 direction = HardwareAccessDirection.UP 

189 

190 if self.has_any_hardware_accessible_register(direction=direction): 

191 vhdl += self._array_field_records(direction=direction) 

192 vhdl += self._get_register_record(direction=direction) 

193 vhdl += f"""\ 

194 -- Convert record with everything in the '{direction.name.lower()}' direction to SLV \ 

195register list. 

196 function to_slv(data : {self.name}_regs_{direction.name.lower()}_t) return {self.name}_regs_t; 

197 

198""" 

199 

200 direction = HardwareAccessDirection.DOWN 

201 

202 if self.has_any_hardware_accessible_register(direction=direction): 

203 vhdl += self._array_field_records(direction=direction) 

204 vhdl += self._get_register_record(direction=direction) 

205 vhdl += f"""\ 

206 -- Convert SLV register list to record with everything in the \ 

207'{direction.name.lower()}' direction. 

208 function to_{self.name}_regs_{direction.name.lower()}(data : {self.name}_regs_t) \ 

209return {self.name}_regs_{direction.name.lower()}_t; 

210 

211""" 

212 

213 return vhdl 

214 

215 def _array_field_records(self, direction: "HardwareAccessDirection") -> str: 

216 """ 

217 For every register array that has at least one register in the specified direction: 

218 

219 * Record with members for each register in the array that is in the specified direction. 

220 * Default value constant for the above record. 

221 * VHDL vector type of the above record, ranged per the range of the register array. 

222 

223 This function assumes that the register map has registers in the given direction. 

224 """ 

225 vhdl = "" 

226 

227 for array in self.iterate_hardware_accessible_register_arrays(direction=direction): 

228 array_name = self.qualified_register_array_name(register_array=array) 

229 vhdl += f"""\ 

230 -- Registers of the '{array.name}' array that are in the '{direction.name.lower()}' direction. 

231 type {array_name}_{direction.name.lower()}_t is record 

232""" 

233 

234 vhdl_array_init = [] 

235 for register in self.iterate_hardware_accessible_array_registers( 

236 register_array=array, direction=direction 

237 ): 

238 vhdl += self._record_member_declaration_for_register( 

239 register=register, register_array=array 

240 ) 

241 

242 register_name = self.qualified_register_name( 

243 register=register, register_array=array 

244 ) 

245 init = f"{register_name}_init" if register.fields else "(others => '0')" 

246 vhdl_array_init.append(f"{register.name} => {init}") 

247 

248 init = " " + ",\n ".join(vhdl_array_init) 

249 vhdl += f"""\ 

250 end record; 

251 -- Default value of the above record. 

252 constant {array_name}_{direction.name.lower()}_init : {array_name}_{direction.name.lower()}_t := ( 

253{init} 

254 ); 

255 -- VHDL array of the above record, ranged per the length of the '{array.name}' \ 

256register array. 

257 type {array_name}_{direction.name.lower()}_vec_t is array (0 to {array.length - 1}) of \ 

258{array_name}_{direction.name.lower()}_t; 

259 

260""" 

261 

262 heading = f"""\ 

263 -- ----------------------------------------------------------------------------- 

264 -- Below is a record with correctly typed and ranged members for all registers, register arrays 

265 -- and fields that are in the '{direction.name.lower()}' direction. 

266""" 

267 if vhdl: 

268 heading += f"""\ 

269 -- But first, records for the registers of each register array the are in \ 

270the '{direction.name.lower()}' direction. 

271""" 

272 

273 return f"{heading}{vhdl}" 

274 

275 def _get_register_record(self, direction: "HardwareAccessDirection") -> str: 

276 """ 

277 Get the record that contains all registers and arrays in the specified direction. 

278 Also default value constant for this record. 

279 

280 This function assumes that the register map has registers in the given direction. 

281 """ 

282 record_init = [] 

283 vhdl = f"""\ 

284 -- Record with everything in the '{direction.name.lower()}' direction. 

285 type {self.name}_regs_{direction.name.lower()}_t is record 

286""" 

287 

288 for array in self.iterate_hardware_accessible_register_arrays(direction=direction): 

289 array_name = self.qualified_register_array_name(register_array=array) 

290 

291 vhdl += f" {array.name} : {array_name}_{direction.name.lower()}_vec_t;\n" 

292 record_init.append( 

293 f"{array.name} => (others => {array_name}_{direction.name.lower()}_init)" 

294 ) 

295 

296 for register in self.iterate_hardware_accessible_plain_registers(direction=direction): 

297 vhdl += self._record_member_declaration_for_register(register=register) 

298 

299 if register.fields: 

300 register_name = self.qualified_register_name(register=register) 

301 record_init.append(f"{register.name} => {register_name}_init") 

302 else: 

303 record_init.append(f"{register.name} => (others => '0')") 

304 

305 init = " " + ",\n ".join(record_init) 

306 

307 return f"""\ 

308{vhdl}\ 

309 end record; 

310 -- Default value of the above record. 

311 constant {self.name}_regs_{direction.name.lower()}_init : \ 

312{self.name}_regs_{direction.name.lower()}_t := ( 

313{init} 

314 ); 

315""" 

316 

317 def _record_member_declaration_for_register( 

318 self, register: "Register", register_array: Optional["RegisterArray"] = None 

319 ) -> str: 

320 """ 

321 Get the record member declaration line for a register that shall be part of the record. 

322 """ 

323 register_name = self.qualified_register_name( 

324 register=register, register_array=register_array 

325 ) 

326 

327 if register.fields: 

328 return f" {register.name} : {register_name}_t;\n" 

329 

330 return f" {register.name} : reg_t;\n" 

331 

332 def _register_was_accessed(self) -> str: 

333 """ 

334 Get record for 'reg_was_read' and 'reg_was_written' ports. 

335 Should include only the registers that are actually readable/writeable. 

336 """ 

337 vhdl = "" 

338 

339 for direction in SoftwareAccessDirection: 

340 if self.has_any_software_accessible_register(direction=direction): 

341 vhdl += self._register_was_accessed_record(direction=direction) 

342 

343 return vhdl 

344 

345 def _register_was_accessed_record(self, direction: "SoftwareAccessDirection") -> str: 

346 """ 

347 Get the record for 'reg_was_read' or 'reg_was_written'. 

348 """ 

349 vhdl = f"""\ 

350 -- --------------------------------------------------------------------------- 

351 -- Below is a record with a status bit for each {direction.value.name_adjective} register in the \ 

352register map. 

353 -- It can be used for the 'reg_was_{direction.value.name_past}' port of a register file wrapper. 

354""" 

355 

356 for array in self.iterate_software_accessible_register_arrays(direction=direction): 

357 array_name = self.qualified_register_array_name(register_array=array) 

358 vhdl += f"""\ 

359 -- One status bit for each {direction.value.name_adjective} register in the '{array.name}' \ 

360register array. 

361 type {array_name}_was_{direction.value.name_past}_t is record 

362""" 

363 

364 for register in self.iterate_software_accessible_array_registers( 

365 register_array=array, direction=direction 

366 ): 

367 vhdl += f" {register.name} : std_ulogic;\n" 

368 

369 vhdl += f"""\ 

370 end record; 

371 -- Default value of the above record. 

372 constant {array_name}_was_{direction.value.name_past}_init : \ 

373{array_name}_was_{direction.value.name_past}_t := (others => '0'); 

374 -- Vector of the above record, ranged per the length of the '{array.name}' \ 

375register array. 

376 type {array_name}_was_{direction.value.name_past}_vec_t is array (0 to {array.length - 1}) \ 

377of {array_name}_was_{direction.value.name_past}_t; 

378 

379""" 

380 

381 vhdl += f"""\ 

382 -- Combined status mask record for all {direction.value.name_adjective} register. 

383 type {self.name}_reg_was_{direction.value.name_past}_t is record 

384""" 

385 

386 array_init = [] 

387 for array in self.iterate_software_accessible_register_arrays(direction=direction): 

388 array_name = self.qualified_register_array_name(register_array=array) 

389 

390 vhdl += f" {array.name} : {array_name}_was_{direction.value.name_past}_vec_t;\n" 

391 array_init.append( 

392 f"{array.name} => (others => {array_name}_was_{direction.value.name_past}_init)" 

393 ) 

394 

395 has_at_least_one_register = False 

396 for register in self.iterate_software_accessible_plain_registers(direction=direction): 

397 vhdl += f" {register.name} : std_ulogic;\n" 

398 has_at_least_one_register = True 

399 

400 init_arrays = (" " + ",\n ".join(array_init)) if array_init else "" 

401 init_registers = " others => '0'" if has_at_least_one_register else "" 

402 separator = ",\n" if (init_arrays and init_registers) else "" 

403 

404 vhdl += f"""\ 

405 end record; 

406 -- Default value for the above record. 

407 constant {self.name}_reg_was_{direction.value.name_past}_init : \ 

408{self.name}_reg_was_{direction.value.name_past}_t := ( 

409{init_arrays}{separator}{init_registers} 

410 ); 

411 -- Convert an SLV 'reg_was_{direction.value.name_past}' from generic register file \ 

412to the record above. 

413 function to_{self.name}_reg_was_{direction.value.name_past}( 

414 data : {self.name}_reg_was_accessed_t 

415 ) return {self.name}_reg_was_{direction.value.name_past}_t; 

416 

417""" 

418 

419 return vhdl 

420 

421 def _register_field_record_conversion_implementations(self) -> str: 

422 """ 

423 Implementation of functions that convert a register record with native field types 

424 to/from SLV. 

425 """ 

426 vhdl = "" 

427 

428 def _get_functions(register: "Register", register_array: Optional["RegisterArray"]) -> str: 

429 register_name = self.qualified_register_name( 

430 register=register, register_array=register_array 

431 ) 

432 

433 to_slv = "" 

434 to_record = "" 

435 

436 for field in register.fields: 

437 field_name = self.qualified_field_name( 

438 register=register, register_array=register_array, field=field 

439 ) 

440 field_to_slv = self.field_to_slv( 

441 field=field, field_name=field_name, value=f"data.{field.name}" 

442 ) 

443 to_slv += f" result({field_name}) := {field_to_slv};\n" 

444 

445 if isinstance(field, Bit): 

446 to_record += f" result.{field.name} := data({field_name});\n" 

447 

448 elif isinstance(field, BitVector): 

449 to_record += f" result.{field.name} := {field_name}_t(data({field_name}));\n" 

450 

451 elif isinstance(field, (Enumeration, Integer)): 

452 to_record += f" result.{field.name} := to_{field_name}(data);\n" 

453 

454 else: 

455 raise TypeError(f'Got unexpected field type: "{field}".') 

456 

457 # Set "don't care" on the bits that have no field, so that a register value comparison 

458 # can be true even if there is junk in the unused bits. 

459 return f"""\ 

460 function to_slv(data : {register_name}_t) return reg_t is 

461 variable result : reg_t := (others => '-'); 

462 begin 

463{to_slv} 

464 return result; 

465 end function; 

466 

467 function to_{register_name}(data : reg_t) return {register_name}_t is 

468 variable result : {register_name}_t := {register_name}_init; 

469 begin 

470{to_record} 

471 return result; 

472 end function; 

473 

474""" 

475 

476 for register, register_array in self.iterate_registers(): 

477 if register.fields: 

478 vhdl += _get_functions(register=register, register_array=register_array) 

479 

480 return vhdl 

481 

482 def _register_record_conversion_implementations(self) -> str: 

483 """ 

484 Conversion function implementations to/from SLV for the records containing all 

485 registers and arrays in 'up'/'down' direction. 

486 """ 

487 vhdl = "" 

488 

489 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.UP): 

490 vhdl += self._register_record_up_to_slv() 

491 

492 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.DOWN): 

493 vhdl += self._get_registers_down_to_record_function() 

494 

495 return vhdl 

496 

497 def _register_record_up_to_slv(self) -> str: 

498 """ 

499 Conversion function implementation for converting a record of all the 'up' registers 

500 to a register SLV list. 

501 

502 This function assumes that the register map has registers in the given direction. 

503 """ 

504 to_slv = "" 

505 

506 for register, register_array in self.iterate_hardware_accessible_registers( 

507 direction=HardwareAccessDirection.UP 

508 ): 

509 register_name = self.qualified_register_name( 

510 register=register, register_array=register_array 

511 ) 

512 

513 if register_array is None: 

514 result = f" result({register_name})" 

515 record = f"data.{register.name}" 

516 

517 if register.fields: 

518 to_slv += f"{result} := to_slv({record});\n" 

519 else: 

520 to_slv += f"{result} := {record};\n" 

521 

522 else: 

523 for array_idx in range(register_array.length): 

524 result = f" result({register_name}({array_idx}))" 

525 record = f"data.{register_array.name}({array_idx}).{register.name}" 

526 

527 if register.fields: 

528 to_slv += f"{result} := to_slv({record});\n" 

529 else: 

530 to_slv += f"{result} := {record};\n" 

531 

532 return f"""\ 

533 function to_slv(data : {self.name}_regs_up_t) return {self.name}_regs_t is 

534 variable result : {self.name}_regs_t := {self.name}_regs_init; 

535 begin 

536{to_slv} 

537 return result; 

538 end function; 

539 

540""" 

541 

542 def _get_registers_down_to_record_function(self) -> str: 

543 """ 

544 Conversion function implementation for converting all the 'down' registers 

545 in a register SLV list to record. 

546 

547 This function assumes that the register map has registers in the given direction. 

548 """ 

549 to_record = "" 

550 

551 for register, register_array in self.iterate_hardware_accessible_registers( 

552 direction=HardwareAccessDirection.DOWN 

553 ): 

554 register_name = self.qualified_register_name( 

555 register=register, register_array=register_array 

556 ) 

557 

558 if register_array is None: 

559 result = f" result.{register.name}" 

560 data = f"data({register_name})" 

561 

562 if register.fields: 

563 to_record += f"{result} := to_{register_name}({data});\n" 

564 else: 

565 to_record += f"{result} := {data};\n" 

566 

567 else: 

568 for array_idx in range(register_array.length): 

569 result = f" result.{register_array.name}({array_idx}).{register.name}" 

570 data = f"data({register_name}({array_idx}))" 

571 

572 if register.fields: 

573 to_record += f"{result} := to_{register_name}({data});\n" 

574 else: 

575 to_record += f"{result} := {data};\n" 

576 

577 return f"""\ 

578 function to_{self.name}_regs_down(data : {self.name}_regs_t) return \ 

579{self.name}_regs_down_t is 

580 variable result : {self.name}_regs_down_t := {self.name}_regs_down_init; 

581 begin 

582{to_record} 

583 return result; 

584 end function; 

585 

586""" 

587 

588 def _register_was_accessed_conversion_implementations(self) -> str: 

589 """ 

590 Get conversion functions from SLV 'reg_was_read'/'reg_was_written' to record types. 

591 """ 

592 vhdl = "" 

593 

594 for direction in SoftwareAccessDirection: 

595 if self.has_any_software_accessible_register(direction=direction): 

596 vhdl += self._register_was_accessed_conversion_implementation(direction=direction) 

597 

598 return vhdl 

599 

600 def _register_was_accessed_conversion_implementation( 

601 self, direction: "SoftwareAccessDirection" 

602 ) -> str: 

603 """ 

604 Get a conversion function from SLV 'reg_was_read'/'reg_was_written' to record type. 

605 """ 

606 vhdl = f"""\ 

607 function to_{self.name}_reg_was_{direction.value.name_past}( 

608 data : {self.name}_reg_was_accessed_t 

609 ) return {self.name}_reg_was_{direction.value.name_past}_t is 

610 variable result : {self.name}_reg_was_{direction.value.name_past}_t := \ 

611{self.name}_reg_was_{direction.value.name_past}_init; 

612 begin 

613""" 

614 

615 for register in self.iterate_software_accessible_plain_registers(direction=direction): 

616 register_name = self.qualified_register_name(register=register) 

617 vhdl += f" result.{register.name} := data({register_name});\n" 

618 

619 for array in self.iterate_register_arrays(): 

620 for register in self.iterate_software_accessible_array_registers( 

621 register_array=array, direction=direction 

622 ): 

623 register_name = self.qualified_register_name( 

624 register=register, register_array=array 

625 ) 

626 

627 for array_index in range(array.length): 

628 vhdl += ( 

629 f" result.{array.name}({array_index}).{register.name} := " 

630 f"data({register_name}(array_index=>{array_index}));\n" 

631 ) 

632 

633 return f"""\ 

634{vhdl} 

635 return result; 

636 end function; 

637 

638"""