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

204 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-28 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"""\ 

77library ieee; 

78use ieee.fixed_pkg.all; 

79use ieee.std_logic_1164.all; 

80use ieee.numeric_std.all; 

81 

82library register_file; 

83use register_file.register_file_pkg.register_t; 

84 

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

86 

87 

88package {package_name} is 

89 

90""" 

91 

92 if self.register_list.register_objects: 

93 vhdl += f"""\ 

94{self._register_field_records()}\ 

95{self._register_records()}\ 

96{self._register_was_accessed()}\ 

97""" 

98 

99 vhdl += "end package;\n" 

100 

101 if self.register_list.register_objects: 

102 vhdl += f""" 

103package body {package_name} is 

104 

105{self._register_field_record_conversion_implementations()}\ 

106{self._register_record_conversion_implementations()}\ 

107{self._register_was_accessed_conversion_implementations()}\ 

108end package body; 

109""" 

110 

111 return vhdl 

112 

113 def _register_field_records(self) -> str: 

114 """ 

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

116 

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

118 * Default value constant for the above record. 

119 * Function to convert the above record to SLV. 

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

121 """ 

122 vhdl = """\ 

123 -- ----------------------------------------------------------------------------- 

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

125""" 

126 

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

128 if not register.fields: 

129 continue 

130 

131 register_name = self.qualified_register_name( 

132 register=register, register_array=register_array 

133 ) 

134 register_description = self.register_description( 

135 register=register, register_array=register_array 

136 ) 

137 

138 record = "" 

139 init = [] 

140 

141 for field in register.fields: 

142 field_name = self.qualified_field_name( 

143 register=register, register_array=register_array, field=field 

144 ) 

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

146 

147 field_type_name = self.field_type_name( 

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

149 ) 

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

151 

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

153 

154 vhdl += f"""\ 

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

156 type {register_name}_t is record 

157{record}\ 

158 end record; 

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

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

161{init_str} 

162 ); 

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

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

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

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

167 

168""" 

169 

170 return vhdl 

171 

172 def _register_records(self) -> str: 

173 """ 

174 Get two records, 

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

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

177 

178 Along with conversion function declarations to/from SLV. 

179 

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

181 register array. 

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

183 that direction. 

184 """ 

185 vhdl = "" 

186 

187 direction = HardwareAccessDirection.UP 

188 

189 if self.has_any_hardware_accessible_register(direction=direction): 

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

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

192 vhdl += f"""\ 

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

194register list. 

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

196 

197""" 

198 

199 direction = HardwareAccessDirection.DOWN 

200 

201 if self.has_any_hardware_accessible_register(direction=direction): 

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

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

204 vhdl += f"""\ 

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

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

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

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

209 

210""" 

211 

212 return vhdl 

213 

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

215 """ 

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

217 

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

219 * Default value constant for the above record. 

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

221 

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

223 """ 

224 vhdl = "" 

225 

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

227 array_name = self.qualified_register_array_name(register_array=array) 

228 vhdl += f"""\ 

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

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

231""" 

232 

233 vhdl_array_init = [] 

234 for register in self.iterate_hardware_accessible_array_registers( 

235 register_array=array, direction=direction 

236 ): 

237 vhdl += self._record_member_declaration_for_register( 

238 register=register, register_array=array 

239 ) 

240 

241 register_name = self.qualified_register_name( 

242 register=register, register_array=array 

243 ) 

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

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

246 

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

248 vhdl += f"""\ 

249 end record; 

250 -- Default value of the above record. 

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

252{init} 

253 ); 

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

255register array. 

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

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

258 

259""" 

260 

261 heading = f"""\ 

262 -- ----------------------------------------------------------------------------- 

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

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

265""" 

266 if vhdl: 

267 heading += f"""\ 

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

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

270""" 

271 

272 return f"{heading}{vhdl}" 

273 

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

275 """ 

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

277 Also default value constant for this record. 

278 

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

280 """ 

281 record_init = [] 

282 vhdl = f"""\ 

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

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

285""" 

286 

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

288 array_name = self.qualified_register_array_name(register_array=array) 

289 

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

291 record_init.append( 

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

293 ) 

294 

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

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

297 

298 if register.fields: 

299 register_name = self.qualified_register_name(register=register) 

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

301 else: 

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

303 

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

305 

306 return f"""\ 

307{vhdl}\ 

308 end record; 

309 -- Default value of the above record. 

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

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

312{init} 

313 ); 

314""" 

315 

316 def _record_member_declaration_for_register( 

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

318 ) -> str: 

319 """ 

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

321 """ 

322 register_name = self.qualified_register_name( 

323 register=register, register_array=register_array 

324 ) 

325 

326 if register.fields: 

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

328 

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

330 

331 def _register_was_accessed(self) -> str: 

332 """ 

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

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

335 """ 

336 vhdl = "" 

337 

338 for direction in SoftwareAccessDirection: 

339 if self.has_any_software_accessible_register(direction=direction): 

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

341 

342 return vhdl 

343 

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

345 """ 

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

347 """ 

348 vhdl = f"""\ 

349 -- --------------------------------------------------------------------------- 

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

351register list. 

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

353""" 

354 

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

356 array_name = self.qualified_register_array_name(register_array=array) 

357 vhdl += f"""\ 

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

359register array. 

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

361""" 

362 

363 for register in self.iterate_software_accessible_array_registers( 

364 register_array=array, direction=direction 

365 ): 

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

367 

368 vhdl += f"""\ 

369 end record; 

370 -- Default value of the above record. 

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

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

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

374register array. 

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

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

377 

378""" 

379 

380 vhdl += f"""\ 

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

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

383""" 

384 

385 array_init = [] 

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

387 array_name = self.qualified_register_array_name(register_array=array) 

388 

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

390 array_init.append( 

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

392 ) 

393 

394 has_at_least_one_register = False 

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

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

397 has_at_least_one_register = True 

398 

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

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

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

402 

403 vhdl += f"""\ 

404 end record; 

405 -- Default value for the above record. 

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

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

408{init_arrays}{separator}{init_registers} 

409 ); 

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

411to the record above. 

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

413 data : {self.name}_reg_was_accessed_t 

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

415 

416""" 

417 

418 return vhdl 

419 

420 def _register_field_record_conversion_implementations(self) -> str: 

421 """ 

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

423 to/from SLV. 

424 """ 

425 vhdl = "" 

426 

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

428 register_name = self.qualified_register_name( 

429 register=register, register_array=register_array 

430 ) 

431 

432 to_slv = "" 

433 to_record = "" 

434 

435 for field in register.fields: 

436 field_name = self.qualified_field_name( 

437 register=register, register_array=register_array, field=field 

438 ) 

439 field_to_slv = self.field_to_slv( 

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

441 ) 

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

443 

444 if isinstance(field, Bit): 

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

446 

447 elif isinstance(field, BitVector): 

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

449 

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

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

452 

453 else: 

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

455 

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

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

458 return f"""\ 

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

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

461 begin 

462{to_slv} 

463 return result; 

464 end function; 

465 

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

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

468 begin 

469{to_record} 

470 return result; 

471 end function; 

472 

473""" 

474 

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

476 if register.fields: 

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

478 

479 return vhdl 

480 

481 def _register_record_conversion_implementations(self) -> str: 

482 """ 

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

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

485 """ 

486 vhdl = "" 

487 

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

489 vhdl += self._register_record_up_to_slv() 

490 

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

492 vhdl += self._get_registers_down_to_record_function() 

493 

494 return vhdl 

495 

496 def _register_record_up_to_slv(self) -> str: 

497 """ 

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

499 to a register SLV list. 

500 

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

502 """ 

503 to_slv = "" 

504 

505 for register, register_array in self.iterate_hardware_accessible_registers( 

506 direction=HardwareAccessDirection.UP 

507 ): 

508 register_name = self.qualified_register_name( 

509 register=register, register_array=register_array 

510 ) 

511 

512 if register_array is None: 

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

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

515 

516 if register.fields: 

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

518 else: 

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

520 

521 else: 

522 for array_idx in range(register_array.length): 

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

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

525 

526 if register.fields: 

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

528 else: 

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

530 

531 return f"""\ 

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

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

534 begin 

535{to_slv} 

536 return result; 

537 end function; 

538 

539""" 

540 

541 def _get_registers_down_to_record_function(self) -> str: 

542 """ 

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

544 in a register SLV list to record. 

545 

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

547 """ 

548 to_record = "" 

549 

550 for register, register_array in self.iterate_hardware_accessible_registers( 

551 direction=HardwareAccessDirection.DOWN 

552 ): 

553 register_name = self.qualified_register_name( 

554 register=register, register_array=register_array 

555 ) 

556 

557 if register_array is None: 

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

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

560 

561 if register.fields: 

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

563 else: 

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

565 

566 else: 

567 for array_idx in range(register_array.length): 

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

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

570 

571 if register.fields: 

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

573 else: 

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

575 

576 return f"""\ 

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

578{self.name}_regs_down_t is 

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

580 begin 

581{to_record} 

582 return result; 

583 end function; 

584 

585""" 

586 

587 def _register_was_accessed_conversion_implementations(self) -> str: 

588 """ 

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

590 """ 

591 vhdl = "" 

592 

593 for direction in SoftwareAccessDirection: 

594 if self.has_any_software_accessible_register(direction=direction): 

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

596 

597 return vhdl 

598 

599 def _register_was_accessed_conversion_implementation( 

600 self, direction: "SoftwareAccessDirection" 

601 ) -> str: 

602 """ 

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

604 """ 

605 vhdl = f"""\ 

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

607 data : {self.name}_reg_was_accessed_t 

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

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

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

611 begin 

612""" 

613 

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

615 register_name = self.qualified_register_name(register=register) 

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

617 

618 for array in self.iterate_register_arrays(): 

619 for register in self.iterate_software_accessible_array_registers( 

620 register_array=array, direction=direction 

621 ): 

622 register_name = self.qualified_register_name( 

623 register=register, register_array=array 

624 ) 

625 

626 for array_index in range(array.length): 

627 vhdl += ( 

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

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

630 ) 

631 

632 return f"""\ 

633{vhdl} 

634 return result; 

635 end function; 

636 

637"""