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

205 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 11:11 +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 """ 

48 

49 __version__ = "1.0.0" 

50 

51 SHORT_DESCRIPTION = "VHDL record package" 

52 

53 @property 

54 def output_file(self) -> Path: 

55 """ 

56 Result will be placed in this file. 

57 """ 

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

59 

60 def create( 

61 self, 

62 **kwargs: Any, # noqa: ANN401 

63 ) -> Path: 

64 """ 

65 See super class for API details. 

66 

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

68 actually has any registers. 

69 """ 

70 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

71 

72 def get_code( 

73 self, 

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

75 ) -> str: 

76 """ 

77 Get a complete VHDL package with register record types. 

78 """ 

79 package_name = self.output_file.stem 

80 

81 vhdl = f"""\ 

82library ieee; 

83use ieee.fixed_pkg.all; 

84use ieee.std_logic_1164.all; 

85use ieee.numeric_std.all; 

86 

87library register_file; 

88use register_file.register_file_pkg.register_t; 

89 

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

91 

92 

93package {package_name} is 

94 

95""" 

96 

97 if self.register_list.register_objects: 

98 vhdl += f"""\ 

99{self._register_field_records()}\ 

100{self._register_records()}\ 

101{self._register_was_accessed()}\ 

102""" 

103 

104 vhdl += "end package;\n" 

105 

106 if self.register_list.register_objects: 

107 vhdl += f""" 

108package body {package_name} is 

109 

110{self._register_field_record_conversion_implementations()}\ 

111{self._register_record_conversion_implementations()}\ 

112{self._register_was_accessed_conversion_implementations()}\ 

113end package body; 

114""" 

115 

116 return vhdl 

117 

118 def _register_field_records(self) -> str: 

119 """ 

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

121 

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

123 * Default value constant for the above record. 

124 * Function to convert the above record to SLV. 

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

126 """ 

127 vhdl = """\ 

128 -- ----------------------------------------------------------------------------- 

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

130""" 

131 

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

133 if not register.fields: 

134 continue 

135 

136 register_name = self.qualified_register_name( 

137 register=register, register_array=register_array 

138 ) 

139 register_description = self.register_description( 

140 register=register, register_array=register_array 

141 ) 

142 

143 record = "" 

144 init = [] 

145 

146 for field in register.fields: 

147 field_name = self.qualified_field_name( 

148 register=register, register_array=register_array, field=field 

149 ) 

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

151 

152 field_type_name = self.field_type_name( 

153 register=register, register_array=register_array, field=field 

154 ) 

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

156 

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

158 

159 vhdl += f"""\ 

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

161 type {register_name}_t is record 

162{record}\ 

163 end record; 

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

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

166{init_str} 

167 ); 

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

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

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

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

172 

173""" 

174 

175 return vhdl 

176 

177 def _register_records(self) -> str: 

178 """ 

179 Get two records, 

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

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

182 

183 Along with conversion function declarations to/from SLV. 

184 

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

186 register array. 

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

188 that direction. 

189 """ 

190 vhdl = "" 

191 

192 direction = HardwareAccessDirection.UP 

193 

194 if self.has_any_hardware_accessible_register(direction=direction): 

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

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

197 vhdl += f"""\ 

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

199register list. 

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

201 

202""" 

203 

204 direction = HardwareAccessDirection.DOWN 

205 

206 if self.has_any_hardware_accessible_register(direction=direction): 

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

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

209 vhdl += f"""\ 

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

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

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

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

214 

215""" 

216 

217 return vhdl 

218 

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

220 """ 

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

222 

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

224 * Default value constant for the above record. 

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

226 

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

228 """ 

229 vhdl = "" 

230 

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

232 array_name = self.qualified_register_array_name(register_array=array) 

233 vhdl += f"""\ 

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

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

236""" 

237 

238 vhdl_array_init = [] 

239 for register in self.iterate_hardware_accessible_array_registers( 

240 register_array=array, direction=direction 

241 ): 

242 vhdl += self._record_member_declaration_for_register( 

243 register=register, register_array=array 

244 ) 

245 

246 register_name = self.qualified_register_name( 

247 register=register, register_array=array 

248 ) 

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

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

251 

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

253 vhdl += f"""\ 

254 end record; 

255 -- Default value of the above record. 

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

257{init} 

258 ); 

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

260register array. 

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

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

263 

264""" 

265 

266 heading = f"""\ 

267 -- ----------------------------------------------------------------------------- 

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

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

270""" 

271 if vhdl: 

272 heading += f"""\ 

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

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

275""" 

276 

277 return f"{heading}{vhdl}" 

278 

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

280 """ 

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

282 Also default value constant for this record. 

283 

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

285 """ 

286 record_init = [] 

287 vhdl = f"""\ 

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

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

290""" 

291 

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

293 array_name = self.qualified_register_array_name(register_array=array) 

294 

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

296 record_init.append( 

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

298 ) 

299 

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

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

302 

303 if register.fields: 

304 register_name = self.qualified_register_name(register=register) 

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

306 else: 

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

308 

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

310 

311 return f"""\ 

312{vhdl}\ 

313 end record; 

314 -- Default value of the above record. 

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

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

317{init} 

318 ); 

319""" 

320 

321 def _record_member_declaration_for_register( 

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

323 ) -> str: 

324 """ 

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

326 """ 

327 register_name = self.qualified_register_name( 

328 register=register, register_array=register_array 

329 ) 

330 

331 if register.fields: 

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

333 

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

335 

336 def _register_was_accessed(self) -> str: 

337 """ 

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

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

340 """ 

341 vhdl = "" 

342 

343 for direction in SoftwareAccessDirection: 

344 if self.has_any_software_accessible_register(direction=direction): 

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

346 

347 return vhdl 

348 

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

350 """ 

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

352 """ 

353 vhdl = f"""\ 

354 -- --------------------------------------------------------------------------- 

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

356register list. 

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

358""" 

359 

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

361 array_name = self.qualified_register_array_name(register_array=array) 

362 vhdl += f"""\ 

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

364register array. 

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

366""" 

367 

368 for register in self.iterate_software_accessible_array_registers( 

369 register_array=array, direction=direction 

370 ): 

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

372 

373 vhdl += f"""\ 

374 end record; 

375 -- Default value of the above record. 

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

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

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

379register array. 

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

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

382 

383""" 

384 

385 vhdl += f"""\ 

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

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

388""" 

389 

390 array_init = [] 

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

392 array_name = self.qualified_register_array_name(register_array=array) 

393 

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

395 array_init.append( 

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

397 ) 

398 

399 has_at_least_one_register = False 

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

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

402 has_at_least_one_register = True 

403 

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

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

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

407 

408 vhdl += f"""\ 

409 end record; 

410 -- Default value for the above record. 

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

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

413{init_arrays}{separator}{init_registers} 

414 ); 

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

416to the record above. 

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

418 data : {self.name}_reg_was_accessed_t 

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

420 

421""" 

422 

423 return vhdl 

424 

425 def _register_field_record_conversion_implementations(self) -> str: 

426 """ 

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

428 to/from SLV. 

429 """ 

430 vhdl = "" 

431 

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

433 register_name = self.qualified_register_name( 

434 register=register, register_array=register_array 

435 ) 

436 

437 to_slv = "" 

438 to_record = "" 

439 

440 for field in register.fields: 

441 field_name = self.qualified_field_name( 

442 register=register, register_array=register_array, field=field 

443 ) 

444 field_to_slv = self.field_to_slv( 

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

446 ) 

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

448 

449 if isinstance(field, Bit): 

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

451 

452 elif isinstance(field, BitVector): 

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

454 

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

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

457 

458 else: 

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

460 

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

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

463 return f"""\ 

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

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

466 begin 

467{to_slv} 

468 return result; 

469 end function; 

470 

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

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

473 begin 

474{to_record} 

475 return result; 

476 end function; 

477 

478""" 

479 

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

481 if register.fields: 

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

483 

484 return vhdl 

485 

486 def _register_record_conversion_implementations(self) -> str: 

487 """ 

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

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

490 """ 

491 vhdl = "" 

492 

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

494 vhdl += self._register_record_up_to_slv() 

495 

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

497 vhdl += self._get_registers_down_to_record_function() 

498 

499 return vhdl 

500 

501 def _register_record_up_to_slv(self) -> str: 

502 """ 

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

504 to a register SLV list. 

505 

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

507 """ 

508 to_slv = "" 

509 

510 for register, register_array in self.iterate_hardware_accessible_registers( 

511 direction=HardwareAccessDirection.UP 

512 ): 

513 register_name = self.qualified_register_name( 

514 register=register, register_array=register_array 

515 ) 

516 

517 if register_array is None: 

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

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

520 

521 if register.fields: 

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

523 else: 

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

525 

526 else: 

527 for array_idx in range(register_array.length): 

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

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

530 

531 if register.fields: 

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

533 else: 

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

535 

536 return f"""\ 

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

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

539 begin 

540{to_slv} 

541 return result; 

542 end function; 

543 

544""" 

545 

546 def _get_registers_down_to_record_function(self) -> str: 

547 """ 

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

549 in a register SLV list to record. 

550 

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

552 """ 

553 to_record = "" 

554 

555 for register, register_array in self.iterate_hardware_accessible_registers( 

556 direction=HardwareAccessDirection.DOWN 

557 ): 

558 register_name = self.qualified_register_name( 

559 register=register, register_array=register_array 

560 ) 

561 

562 if register_array is None: 

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

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

565 

566 if register.fields: 

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

568 else: 

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

570 

571 else: 

572 for array_idx in range(register_array.length): 

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

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

575 

576 if register.fields: 

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

578 else: 

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

580 

581 return f"""\ 

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

583{self.name}_regs_down_t is 

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

585 begin 

586{to_record} 

587 return result; 

588 end function; 

589 

590""" 

591 

592 def _register_was_accessed_conversion_implementations(self) -> str: 

593 """ 

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

595 """ 

596 vhdl = "" 

597 

598 for direction in SoftwareAccessDirection: 

599 if self.has_any_software_accessible_register(direction=direction): 

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

601 

602 return vhdl 

603 

604 def _register_was_accessed_conversion_implementation( 

605 self, direction: SoftwareAccessDirection 

606 ) -> str: 

607 """ 

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

609 """ 

610 vhdl = f"""\ 

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

612 data : {self.name}_reg_was_accessed_t 

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

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

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

616 begin 

617""" 

618 

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

620 register_name = self.qualified_register_name(register=register) 

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

622 

623 for array in self.iterate_register_arrays(): 

624 for register in self.iterate_software_accessible_array_registers( 

625 register_array=array, direction=direction 

626 ): 

627 register_name = self.qualified_register_name( 

628 register=register, register_array=array 

629 ) 

630 

631 for array_index in range(array.length): 

632 vhdl += ( 

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

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

635 ) 

636 

637 return f"""\ 

638{vhdl} 

639 return result; 

640 end function; 

641 

642"""