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

205 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-04 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 

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 direction = HardwareAccessDirection.UP 

194 

195 if self.has_any_hardware_accessible_register(direction=direction): 

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

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

198 vhdl += f"""\ 

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

200register list. 

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

202 

203""" 

204 

205 direction = HardwareAccessDirection.DOWN 

206 

207 if self.has_any_hardware_accessible_register(direction=direction): 

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

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

210 vhdl += f"""\ 

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

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

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

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

215 

216""" 

217 

218 return vhdl 

219 

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

221 """ 

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

223 

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

225 * Default value constant for the above record. 

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

227 

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

229 """ 

230 vhdl = "" 

231 

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

233 array_name = self.qualified_register_array_name(register_array=array) 

234 vhdl += f"""\ 

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

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

237""" 

238 

239 vhdl_array_init = [] 

240 for register in self.iterate_hardware_accessible_array_registers( 

241 register_array=array, direction=direction 

242 ): 

243 vhdl += self._record_member_declaration_for_register( 

244 register=register, register_array=array 

245 ) 

246 

247 register_name = self.qualified_register_name( 

248 register=register, register_array=array 

249 ) 

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

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

252 

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

254 vhdl += f"""\ 

255 end record; 

256 -- Default value of the above record. 

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

258{init} 

259 ); 

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

261register array. 

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

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

264 

265""" 

266 

267 heading = f"""\ 

268 -- ----------------------------------------------------------------------------- 

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

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

271""" 

272 if vhdl: 

273 heading += f"""\ 

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

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

276""" 

277 

278 return f"{heading}{vhdl}" 

279 

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

281 """ 

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

283 Also default value constant for this record. 

284 

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

286 """ 

287 record_init = [] 

288 vhdl = f"""\ 

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

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

291""" 

292 

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

294 array_name = self.qualified_register_array_name(register_array=array) 

295 

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

297 record_init.append( 

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

299 ) 

300 

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

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

303 

304 if register.fields: 

305 register_name = self.qualified_register_name(register=register) 

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

307 else: 

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

309 

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

311 

312 return f"""\ 

313{vhdl}\ 

314 end record; 

315 -- Default value of the above record. 

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

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

318{init} 

319 ); 

320""" 

321 

322 def _record_member_declaration_for_register( 

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

324 ) -> str: 

325 """ 

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

327 """ 

328 register_name = self.qualified_register_name( 

329 register=register, register_array=register_array 

330 ) 

331 

332 if register.fields: 

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

334 

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

336 

337 def _register_was_accessed(self) -> str: 

338 """ 

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

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

341 """ 

342 vhdl = "" 

343 

344 for direction in SoftwareAccessDirection: 

345 if self.has_any_software_accessible_register(direction=direction): 

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

347 

348 return vhdl 

349 

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

351 """ 

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

353 """ 

354 vhdl = f"""\ 

355 -- --------------------------------------------------------------------------- 

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

357register list. 

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

359""" 

360 

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

362 array_name = self.qualified_register_array_name(register_array=array) 

363 vhdl += f"""\ 

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

365register array. 

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

367""" 

368 

369 for register in self.iterate_software_accessible_array_registers( 

370 register_array=array, direction=direction 

371 ): 

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

373 

374 vhdl += f"""\ 

375 end record; 

376 -- Default value of the above record. 

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

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

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

380register array. 

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

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

383 

384""" 

385 

386 vhdl += f"""\ 

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

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

389""" 

390 

391 array_init = [] 

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

393 array_name = self.qualified_register_array_name(register_array=array) 

394 

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

396 array_init.append( 

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

398 ) 

399 

400 has_at_least_one_register = False 

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

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

403 has_at_least_one_register = True 

404 

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

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

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

408 

409 vhdl += f"""\ 

410 end record; 

411 -- Default value for the above record. 

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

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

414{init_arrays}{separator}{init_registers} 

415 ); 

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

417to the record above. 

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

419 data : {self.name}_reg_was_accessed_t 

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

421 

422""" 

423 

424 return vhdl 

425 

426 def _register_field_record_conversion_implementations(self) -> str: 

427 """ 

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

429 to/from SLV. 

430 """ 

431 vhdl = "" 

432 

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

434 register_name = self.qualified_register_name( 

435 register=register, register_array=register_array 

436 ) 

437 

438 to_slv = "" 

439 to_record = "" 

440 

441 for field in register.fields: 

442 field_name = self.qualified_field_name( 

443 register=register, register_array=register_array, field=field 

444 ) 

445 field_to_slv = self.field_to_slv( 

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

447 ) 

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

449 

450 if isinstance(field, Bit): 

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

452 

453 elif isinstance(field, BitVector): 

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

455 

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

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

458 

459 else: 

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

461 

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

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

464 return f"""\ 

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

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

467 begin 

468{to_slv} 

469 return result; 

470 end function; 

471 

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

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

474 begin 

475{to_record} 

476 return result; 

477 end function; 

478 

479""" 

480 

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

482 if register.fields: 

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

484 

485 return vhdl 

486 

487 def _register_record_conversion_implementations(self) -> str: 

488 """ 

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

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

491 """ 

492 vhdl = "" 

493 

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

495 vhdl += self._register_record_up_to_slv() 

496 

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

498 vhdl += self._get_registers_down_to_record_function() 

499 

500 return vhdl 

501 

502 def _register_record_up_to_slv(self) -> str: 

503 """ 

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

505 to a register SLV list. 

506 

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

508 """ 

509 to_slv = "" 

510 

511 for register, register_array in self.iterate_hardware_accessible_registers( 

512 direction=HardwareAccessDirection.UP 

513 ): 

514 register_name = self.qualified_register_name( 

515 register=register, register_array=register_array 

516 ) 

517 

518 if register_array is None: 

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

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

521 

522 if register.fields: 

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

524 else: 

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

526 

527 else: 

528 for array_idx in range(register_array.length): 

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

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

531 

532 if register.fields: 

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

534 else: 

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

536 

537 return f"""\ 

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

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

540 begin 

541{to_slv} 

542 return result; 

543 end function; 

544 

545""" 

546 

547 def _get_registers_down_to_record_function(self) -> str: 

548 """ 

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

550 in a register SLV list to record. 

551 

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

553 """ 

554 to_record = "" 

555 

556 for register, register_array in self.iterate_hardware_accessible_registers( 

557 direction=HardwareAccessDirection.DOWN 

558 ): 

559 register_name = self.qualified_register_name( 

560 register=register, register_array=register_array 

561 ) 

562 

563 if register_array is None: 

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

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

566 

567 if register.fields: 

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

569 else: 

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

571 

572 else: 

573 for array_idx in range(register_array.length): 

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

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

576 

577 if register.fields: 

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

579 else: 

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

581 

582 return f"""\ 

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

584{self.name}_regs_down_t is 

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

586 begin 

587{to_record} 

588 return result; 

589 end function; 

590 

591""" 

592 

593 def _register_was_accessed_conversion_implementations(self) -> str: 

594 """ 

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

596 """ 

597 vhdl = "" 

598 

599 for direction in SoftwareAccessDirection: 

600 if self.has_any_software_accessible_register(direction=direction): 

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

602 

603 return vhdl 

604 

605 def _register_was_accessed_conversion_implementation( 

606 self, direction: SoftwareAccessDirection 

607 ) -> str: 

608 """ 

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

610 """ 

611 vhdl = f"""\ 

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

613 data : {self.name}_reg_was_accessed_t 

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

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

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

617 begin 

618""" 

619 

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

621 register_name = self.qualified_register_name(register=register) 

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

623 

624 for array in self.iterate_register_arrays(): 

625 for register in self.iterate_software_accessible_array_registers( 

626 register_array=array, direction=direction 

627 ): 

628 register_name = self.qualified_register_name( 

629 register=register, register_array=array 

630 ) 

631 

632 for array_index in range(array.length): 

633 vhdl += ( 

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

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

636 ) 

637 

638 return f"""\ 

639{vhdl} 

640 return result; 

641 end function; 

642 

643"""