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

211 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-20 22:43 +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 

10from __future__ import annotations 

11 

12from typing import TYPE_CHECKING, Any 

13 

14from hdl_registers.field.bit import Bit 

15from hdl_registers.field.bit_vector import BitVector 

16from hdl_registers.field.enumeration import Enumeration 

17from hdl_registers.field.integer import Integer 

18from hdl_registers.register_mode import HardwareAccessDirection, SoftwareAccessDirection 

19 

20from .vhdl_generator_common import VhdlGeneratorCommon 

21 

22if TYPE_CHECKING: 

23 from pathlib import Path 

24 

25 from hdl_registers.register import Register 

26 from hdl_registers.register_array import RegisterArray 

27 

28 

29class VhdlRecordPackageGenerator(VhdlGeneratorCommon): 

30 """ 

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

32 each register field. 

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

34 

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

36 register field. 

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

38 that array. 

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

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

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

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

43 above types. 

44 

45 The generated VHDL file needs also the generated package 

46 from :class:`.VhdlRegisterPackageGenerator`. 

47 See also :ref:`vhdl_dependencies` for further dependencies. 

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( 

62 self, 

63 **kwargs: Any, # noqa: ANN401 

64 ) -> Path: 

65 """ 

66 See super class for API details. 

67 

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

69 actually has any registers. 

70 """ 

71 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

72 

73 def get_code( 

74 self, 

75 **kwargs: Any, # noqa: ANN401, ARG002 

76 ) -> str: 

77 """ 

78 Get a complete VHDL package with register record types. 

79 """ 

80 package_name = self.output_file.stem 

81 

82 vhdl = f"""\ 

83library ieee; 

84use ieee.fixed_pkg.all; 

85use ieee.std_logic_1164.all; 

86use ieee.numeric_std.all; 

87 

88library register_file; 

89use register_file.register_file_pkg.register_t; 

90 

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

92 

93 

94package {package_name} is 

95 

96""" 

97 

98 if self.register_list.register_objects: 

99 vhdl += f"""\ 

100{self._register_field_records()}\ 

101{self._register_records()}\ 

102{self._register_was_accessed()}\ 

103""" 

104 

105 vhdl += "end package;\n" 

106 

107 if self.register_list.register_objects: 

108 vhdl += f""" 

109package body {package_name} is 

110 

111{self._register_field_record_conversion_implementations()}\ 

112{self._register_record_conversion_implementations()}\ 

113{self._register_was_accessed_conversion_implementations()}\ 

114end package body; 

115""" 

116 

117 return vhdl 

118 

119 def _register_field_records(self) -> str: 

120 """ 

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

122 

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

124 * Default value constant for the above record. 

125 * Function to convert the above record to SLV. 

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

127 """ 

128 vhdl = """\ 

129 -- ----------------------------------------------------------------------------- 

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

131""" 

132 

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

134 if not register.fields: 

135 continue 

136 

137 register_name = self.qualified_register_name( 

138 register=register, register_array=register_array 

139 ) 

140 register_description = self.register_description( 

141 register=register, register_array=register_array 

142 ) 

143 

144 record = "" 

145 init = [] 

146 

147 for field in register.fields: 

148 field_name = self.qualified_field_name( 

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

150 ) 

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

152 

153 field_type_name = self.field_type_name( 

154 register=register, register_array=register_array, field=field 

155 ) 

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

157 

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

159 

160 vhdl += f"""\ 

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

162 type {register_name}_t is record 

163{record}\ 

164 end record; 

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

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

167{init_str} 

168 ); 

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

170 function to_slv(data : {register_name}_t) return register_t; 

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

172 function to_{register_name}(data : register_t) return {register_name}_t; 

173 

174""" 

175 

176 return vhdl 

177 

178 def _register_records(self) -> str: 

179 """ 

180 Get two records, 

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

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

183 

184 Along with conversion function declarations to/from SLV. 

185 

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

187 register array. 

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

189 that direction. 

190 """ 

191 vhdl = "" 

192 

193 vhdl += self._array_records(direction=None) 

194 vhdl += self._register_record(direction=None) 

195 vhdl += f"""\ 

196 -- Convert record to SLV register list. 

197 function to_slv(data : {self.name}_registers_t) return {self.name}_regs_t; 

198 

199""" 

200 

201 direction = HardwareAccessDirection.UP 

202 

203 if self.has_any_hardware_accessible_register(direction=direction): 

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

205 vhdl += self._register_record(direction=direction) 

206 vhdl += f"""\ 

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

208register list. 

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

210 

211""" 

212 

213 direction = HardwareAccessDirection.DOWN 

214 

215 if self.has_any_hardware_accessible_register(direction=direction): 

216 vhdl += self._array_records(direction=direction) 

217 vhdl += self._register_record(direction=direction) 

218 vhdl += f"""\ 

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

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

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

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

223 

224""" 

225 

226 return vhdl 

227 

228 def _array_records(self, direction: HardwareAccessDirection | None) -> str: 

229 """ 

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

231 

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

233 * Default value constant for the above record. 

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

235 

236 This function assumes that the register last has registers in the given direction. 

237 """ 

238 comment_suffix = ( 

239 f" that are in the '{direction.name.lower()}' direction" if direction else "" 

240 ) 

241 type_suffix = f"_{direction.name.lower()}" if direction else "" 

242 vhdl = "" 

243 

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

245 array_name = self.qualified_register_array_name(register_array=array) 

246 vhdl += f"""\ 

247 -- Registers of the '{array.name}' array{comment_suffix}. 

248 type {array_name}{type_suffix}_t is record 

249""" 

250 

251 vhdl_array_init = [] 

252 for register in self.iterate_hardware_accessible_array_registers( 

253 register_array=array, direction=direction 

254 ): 

255 vhdl += self._record_member_declaration_for_register( 

256 register=register, register_array=array 

257 ) 

258 

259 register_name = self.qualified_register_name( 

260 register=register, register_array=array 

261 ) 

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

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

264 

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

266 vhdl += f"""\ 

267 end record; 

268 -- Default value of the above record. 

269 constant {array_name}{type_suffix}_init : {array_name}{type_suffix}_t := ( 

270{init} 

271 ); 

272 -- VHDL array of the above record. 

273 type {array_name}{type_suffix}_vec_t is array ({array_name}_range) of {array_name}{type_suffix}_t; 

274 

275""" 

276 

277 heading = f"""\ 

278 -- ----------------------------------------------------------------------------- 

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

280 -- and fields{comment_suffix}. 

281""" 

282 if vhdl: 

283 heading += f"""\ 

284 -- But first, records for the registers of each register array{comment_suffix}. 

285""" 

286 

287 return f"{heading}{vhdl}" 

288 

289 def _register_record(self, direction: HardwareAccessDirection | None) -> str: 

290 """ 

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

292 Also default value constant for this record. 

293 

294 This function assumes that the register list has registers in the given direction. 

295 """ 

296 comment_suffix = f" in the '{direction.name.lower()}' direction" if direction else "" 

297 type_suffix = f"_{direction.name.lower()}" if direction else "" 

298 regs_type_suffix = f"regs_{direction.name.lower()}" if direction else "registers" 

299 

300 record_init = [] 

301 vhdl = f"""\ 

302 -- Record with everything{comment_suffix}. 

303 type {self.name}_{regs_type_suffix}_t is record 

304""" 

305 

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

307 array_name = self.qualified_register_array_name(register_array=array) 

308 

309 vhdl += f" {array.name} : {array_name}{type_suffix}_vec_t;\n" 

310 record_init.append(f"{array.name} => (others => {array_name}{type_suffix}_init)") 

311 

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

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

314 

315 if register.fields: 

316 register_name = self.qualified_register_name(register=register) 

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

318 else: 

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

320 

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

322 

323 return f"""\ 

324{vhdl}\ 

325 end record; 

326 -- Default value of the above record. 

327 constant {self.name}_{regs_type_suffix}_init : {self.name}_{regs_type_suffix}_t := ( 

328{init} 

329 ); 

330""" 

331 

332 def _record_member_declaration_for_register( 

333 self, register: Register, register_array: RegisterArray | None = None 

334 ) -> str: 

335 """ 

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

337 """ 

338 register_name = self.qualified_register_name( 

339 register=register, register_array=register_array 

340 ) 

341 

342 if register.fields: 

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

344 

345 return f" {register.name} : register_t;\n" 

346 

347 def _register_was_accessed(self) -> str: 

348 """ 

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

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

351 """ 

352 vhdl = "" 

353 

354 for direction in SoftwareAccessDirection: 

355 if self.has_any_software_accessible_register(direction=direction): 

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

357 

358 return vhdl 

359 

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

361 """ 

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

363 """ 

364 vhdl = f"""\ 

365 -- --------------------------------------------------------------------------- 

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

367register list. 

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

369""" 

370 

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

372 array_name = self.qualified_register_array_name(register_array=array) 

373 vhdl += f"""\ 

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

375register array. 

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

377""" 

378 

379 for register in self.iterate_software_accessible_array_registers( 

380 register_array=array, direction=direction 

381 ): 

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

383 

384 vhdl += f"""\ 

385 end record; 

386 -- Default value of the above record. 

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

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

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

390register array. 

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

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

393 

394""" 

395 

396 vhdl += f"""\ 

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

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

399""" 

400 

401 array_init = [] 

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

403 array_name = self.qualified_register_array_name(register_array=array) 

404 

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

406 array_init.append( 

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

408 ) 

409 

410 has_at_least_one_register = False 

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

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

413 has_at_least_one_register = True 

414 

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

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

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

418 

419 vhdl += f"""\ 

420 end record; 

421 -- Default value for the above record. 

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

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

424{init_arrays}{separator}{init_registers} 

425 ); 

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

427to the record above. 

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

429 data : {self.name}_reg_was_accessed_t 

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

431 

432""" 

433 

434 return vhdl 

435 

436 def _register_field_record_conversion_implementations(self) -> str: 

437 """ 

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

439 to/from SLV. 

440 """ 

441 vhdl = "" 

442 

443 def _get_functions(register: Register, register_array: RegisterArray | None) -> str: 

444 register_name = self.qualified_register_name( 

445 register=register, register_array=register_array 

446 ) 

447 

448 to_slv = "" 

449 to_record = "" 

450 

451 for field in register.fields: 

452 field_name = self.qualified_field_name( 

453 register=register, register_array=register_array, field=field 

454 ) 

455 field_to_slv = self.field_to_slv( 

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

457 ) 

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

459 

460 if isinstance(field, Bit): 

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

462 

463 elif isinstance(field, BitVector): 

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

465 

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

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

468 

469 else: 

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

471 

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

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

474 return f"""\ 

475 function to_slv(data : {register_name}_t) return register_t is 

476 variable result : register_t := (others => '-'); 

477 begin 

478{to_slv} 

479 return result; 

480 end function; 

481 

482 function to_{register_name}(data : register_t) return {register_name}_t is 

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

484 begin 

485{to_record} 

486 return result; 

487 end function; 

488 

489""" 

490 

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

492 if register.fields: 

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

494 

495 return vhdl 

496 

497 def _register_record_conversion_implementations(self) -> str: 

498 """ 

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

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

501 """ 

502 vhdl = "" 

503 

504 for direction in [None, HardwareAccessDirection.UP]: 

505 if self.has_any_hardware_accessible_register(direction=direction): 

506 vhdl += self._register_record_to_slv(direction=direction) 

507 

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

509 vhdl += self._get_registers_down_to_record_function() 

510 

511 return vhdl 

512 

513 def _register_record_to_slv(self, direction: HardwareAccessDirection | None) -> str: 

514 """ 

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

516 to a register SLV list. 

517 

518 This function assumes that the register list has registers in the given direction. 

519 """ 

520 to_slv = "" 

521 

522 for register, register_array in self.iterate_hardware_accessible_registers( 

523 direction=direction 

524 ): 

525 register_name = self.qualified_register_name( 

526 register=register, register_array=register_array 

527 ) 

528 

529 if register_array is None: 

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

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

532 

533 if register.fields: 

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

535 else: 

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

537 

538 else: 

539 for array_idx in range(register_array.length): 

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

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

542 

543 if register.fields: 

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

545 else: 

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

547 

548 type_suffix = f"regs_{direction.name}" if direction else "registers" 

549 return f"""\ 

550 function to_slv(data : {self.name}_{type_suffix}_t) return {self.name}_regs_t is 

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

552 begin 

553{to_slv} 

554 return result; 

555 end function; 

556 

557""" 

558 

559 def _get_registers_down_to_record_function(self) -> str: 

560 """ 

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

562 in a register SLV list to record. 

563 

564 This function assumes that the register list has registers in the given direction. 

565 """ 

566 to_record = "" 

567 

568 for register, register_array in self.iterate_hardware_accessible_registers( 

569 direction=HardwareAccessDirection.DOWN 

570 ): 

571 register_name = self.qualified_register_name( 

572 register=register, register_array=register_array 

573 ) 

574 

575 if register_array is None: 

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

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

578 

579 if register.fields: 

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

581 else: 

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

583 

584 else: 

585 for array_idx in range(register_array.length): 

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

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

588 

589 if register.fields: 

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

591 else: 

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

593 

594 return f"""\ 

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

596{self.name}_regs_down_t is 

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

598 begin 

599{to_record} 

600 return result; 

601 end function; 

602 

603""" 

604 

605 def _register_was_accessed_conversion_implementations(self) -> str: 

606 """ 

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

608 """ 

609 vhdl = "" 

610 

611 for direction in SoftwareAccessDirection: 

612 if self.has_any_software_accessible_register(direction=direction): 

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

614 

615 return vhdl 

616 

617 def _register_was_accessed_conversion_implementation( 

618 self, direction: SoftwareAccessDirection 

619 ) -> str: 

620 """ 

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

622 """ 

623 vhdl = f"""\ 

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

625 data : {self.name}_reg_was_accessed_t 

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

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

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

629 begin 

630""" 

631 

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

633 register_name = self.qualified_register_name(register=register) 

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

635 

636 for array in self.iterate_register_arrays(): 

637 for register in self.iterate_software_accessible_array_registers( 

638 register_array=array, direction=direction 

639 ): 

640 register_name = self.qualified_register_name( 

641 register=register, register_array=array 

642 ) 

643 

644 for array_index in range(array.length): 

645 vhdl += ( 

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

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

648 ) 

649 

650 return f"""\ 

651{vhdl} 

652 return result; 

653 end function; 

654 

655"""