Coverage for hdl_registers/generator/vhdl/simulation/read_write_package.py: 96%

141 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-29 06:41 +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_vector import BitVector 

15from hdl_registers.field.numerical_interpretation import Signed, Unsigned 

16 

17from .vhdl_simulation_generator_common import VhdlSimulationGeneratorCommon 

18 

19if TYPE_CHECKING: 

20 from pathlib import Path 

21 

22 from hdl_registers.field.register_field import RegisterField 

23 from hdl_registers.register import Register 

24 from hdl_registers.register_array import RegisterArray 

25 

26 

27class VhdlSimulationReadWritePackageGenerator(VhdlSimulationGeneratorCommon): 

28 """ 

29 Generate VHDL code with register read/write procedures that simplify simulation. 

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

31 

32 * For each readable register, procedures that read the register value. 

33 Value can be read as: 

34 

35 1. bit vector, 

36 

37 2. integer, or 

38 

39 3. native VHDL record type as given by :class:`.VhdlRecordPackageGenerator`. 

40 

41 * For each field in each readable register, a procedure that reads the natively-typed value of 

42 the field. 

43 

44 * For each writeable register, a procedure that writes the register value. 

45 Value can be written as: 

46 

47 1. bit vector, or 

48 

49 2. native VHDL record type as given by :class:`.VhdlRecordPackageGenerator`. 

50 

51 * For each field in each writeable register, a procedure that writes a given 

52 natively-typed field value. 

53 

54 Uses VUnit Verification Component calls to create bus read/write operations. 

55 

56 The generated VHDL file needs also the generated packages from 

57 :class:`.VhdlRegisterPackageGenerator` and :class:`.VhdlRecordPackageGenerator`. 

58 See :ref:`vhdl_dependencies` for further dependencies. 

59 """ 

60 

61 __version__ = "1.1.1" 

62 

63 SHORT_DESCRIPTION = "VHDL simulation read/write package" 

64 

65 @property 

66 def output_file(self) -> Path: 

67 """ 

68 Result will be placed in this file. 

69 """ 

70 return self.output_folder / f"{self.name}_register_read_write_pkg.vhd" 

71 

72 def create( 

73 self, 

74 **kwargs: Any, # noqa: ANN401 

75 ) -> Path: 

76 """ 

77 See super class for API details. 

78 

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

80 actually has any registers. 

81 """ 

82 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

83 

84 def get_code( 

85 self, 

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

87 ) -> str: 

88 """ 

89 Get a package with methods for reading/writing registers. 

90 """ 

91 package_name = self.output_file.stem 

92 

93 return f"""\ 

94library ieee; 

95use ieee.numeric_std.all; 

96use ieee.std_logic_1164.all; 

97 

98library vunit_lib; 

99use vunit_lib.bus_master_pkg.bus_master_t; 

100use vunit_lib.bus_master_pkg.read_bus; 

101use vunit_lib.bus_master_pkg.write_bus; 

102use vunit_lib.com_types_pkg.network_t; 

103 

104library register_file; 

105use register_file.register_file_pkg.register_t; 

106use register_file.register_file_pkg.register_width; 

107use register_file.register_operations_pkg.register_bus_master; 

108 

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

110use work.{self.name}_register_record_pkg.all; 

111 

112 

113package {package_name} is 

114 

115{self._declarations()}\ 

116end package; 

117 

118package body {package_name} is 

119 

120{self._implementations()}\ 

121end package body; 

122""" 

123 

124 def _declarations(self) -> str: 

125 """ 

126 Get procedure declarations for all procedures. 

127 """ 

128 separator = self.get_separator_line(indent=2) 

129 vhdl = "" 

130 

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

132 register_name = self.qualified_register_name( 

133 register=register, register_array=register_array 

134 ) 

135 declarations = [] 

136 

137 if register.mode.software_can_read: 

138 # Read the register as a plain SLV. 

139 signature = self._register_read_write_signature( 

140 is_read_not_write=True, 

141 register=register, 

142 register_array=register_array, 

143 value_type="register_t", 

144 ) 

145 declarations.append(f"{signature};\n") 

146 

147 # Read the register as a plain SLV casted to integer. 

148 signature = self._register_read_write_signature( 

149 is_read_not_write=True, 

150 register=register, 

151 register_array=register_array, 

152 value_type="integer", 

153 ) 

154 declarations.append(f"{signature};\n") 

155 

156 if register.fields: 

157 # Read the register as a record. 

158 signature = self._register_read_write_signature( 

159 is_read_not_write=True, 

160 register=register, 

161 register_array=register_array, 

162 value_type=f"{register_name}_t", 

163 ) 

164 declarations.append(f"{signature};\n") 

165 

166 for field in register.fields: 

167 # Read the field as its native type. 

168 value_type = self.field_type_name( 

169 register=register, register_array=register_array, field=field 

170 ) 

171 signature = self._field_read_write_signature( 

172 is_read_not_write=True, 

173 register=register, 

174 register_array=register_array, 

175 field=field, 

176 value_type=value_type, 

177 ) 

178 declarations.append(f"{signature};\n") 

179 

180 if self._should_be_able_to_access_field_as_integer(field=field): 

181 # Read the field casted to an integer. 

182 signature = self._field_read_write_signature( 

183 is_read_not_write=True, 

184 register=register, 

185 register_array=register_array, 

186 field=field, 

187 value_type="integer", 

188 ) 

189 declarations.append(f"{signature};\n") 

190 

191 if register.mode.software_can_write: 

192 # Write the register as an integer. 

193 signature = self._register_read_write_signature( 

194 is_read_not_write=False, 

195 register=register, 

196 register_array=register_array, 

197 value_type="integer", 

198 ) 

199 declarations.append(f"{signature};\n") 

200 

201 if register.fields: 

202 # Write the register as a record. 

203 signature = self._register_read_write_signature( 

204 is_read_not_write=False, 

205 register=register, 

206 register_array=register_array, 

207 value_type=f"{register_name}_t", 

208 ) 

209 declarations.append(f"{signature};\n") 

210 else: 

211 # Write the register as a plain SLV. 

212 # This one is made available only if there are no fields. 

213 # This is because there can be a signature ambiguity if both are available 

214 # that some compilers can not resolve. 

215 # Namely e.g. value=>(field_name => '1'). 

216 # Where the field is a std_logic. 

217 # GHDL gets confused in this case between using the signature with the record 

218 # or the one with SLV. 

219 signature = self._register_read_write_signature( 

220 is_read_not_write=False, 

221 register=register, 

222 register_array=register_array, 

223 value_type="register_t", 

224 ) 

225 declarations.append(f"{signature};\n") 

226 

227 for field in register.fields: 

228 # Write the field as its native type. 

229 value_type = self.field_type_name( 

230 register=register, register_array=register_array, field=field 

231 ) 

232 signature = self._field_read_write_signature( 

233 is_read_not_write=False, 

234 register=register, 

235 register_array=register_array, 

236 field=field, 

237 value_type=value_type, 

238 ) 

239 declarations.append(f"{signature};\n") 

240 

241 if self._should_be_able_to_access_field_as_integer(field=field): 

242 # Write the field casted to an integer. 

243 signature = self._field_read_write_signature( 

244 is_read_not_write=False, 

245 register=register, 

246 register_array=register_array, 

247 field=field, 

248 value_type="integer", 

249 ) 

250 declarations.append(f"{signature};\n") 

251 

252 vhdl += separator 

253 vhdl += "\n".join(declarations) 

254 vhdl += separator 

255 vhdl += "\n" 

256 

257 return vhdl 

258 

259 def _register_read_write_signature( 

260 self, 

261 is_read_not_write: bool, 

262 register: Register, 

263 register_array: RegisterArray | None, 

264 value_type: str, 

265 ) -> str: 

266 """ 

267 Get signature for a 'read_reg'/'write_reg' procedure. 

268 """ 

269 direction = "read" if is_read_not_write else "write" 

270 value_direction = "out" if is_read_not_write else "in" 

271 

272 register_name = self.qualified_register_name( 

273 register=register, register_array=register_array 

274 ) 

275 register_description = self.register_description( 

276 register=register, register_array=register_array 

277 ) 

278 # If it is not either of these, then it is the native type which shall not have a comment 

279 # since it is the default. 

280 type_comment = ( 

281 " as a plain 'register_t'" 

282 if value_type == "register_t" 

283 else " as an 'integer'" 

284 if value_type == "integer" 

285 else "" 

286 ) 

287 

288 return f"""\ 

289 -- {direction.capitalize()} the {register_description}{type_comment}. 

290 procedure {direction}_{register_name}( 

291 signal net : inout network_t; 

292{self.get_array_index_port(register_array=register_array)}\ 

293 value : {value_direction} {value_type}; 

294 base_address : in unsigned(32 - 1 downto 0) := (others => '0'); 

295 bus_handle : in bus_master_t := register_bus_master 

296 )\ 

297""" 

298 

299 def _field_read_write_signature( 

300 self, 

301 is_read_not_write: bool, 

302 register: Register, 

303 register_array: RegisterArray | None, 

304 field: RegisterField, 

305 value_type: str, 

306 ) -> str: 

307 """ 

308 Get signature for a 'read_field'/'write_field' procedure. 

309 """ 

310 direction = "read" if is_read_not_write else "write" 

311 

312 field_name = self.qualified_field_name( 

313 register=register, register_array=register_array, field=field 

314 ) 

315 field_description = self.field_description( 

316 register=register, field=field, register_array=register_array 

317 ) 

318 # If its not integer, then it is the native type which shall shall not have a comment 

319 # since it is the default. 

320 type_comment = " as an 'integer'" if value_type == "integer" else "" 

321 

322 comment = "" 

323 if not is_read_not_write: 

324 if self.field_setter_should_read_modify_write(register=register): 

325 comment = ( 

326 " -- Will read-modify-write the register to set the field to the " 

327 "supplied 'value'.\n" 

328 ) 

329 else: 

330 comment = ( 

331 " -- Will write the whole register, with the field set to the \n" 

332 " -- supplied 'value' and everything else set to default.\n" 

333 ) 

334 

335 value_direction = "out" if is_read_not_write else "in" 

336 

337 return f"""\ 

338 -- {direction.capitalize()} the {field_description}{type_comment}. 

339{comment}\ 

340 procedure {direction}_{field_name}( 

341 signal net : inout network_t; 

342{self.get_array_index_port(register_array=register_array)}\ 

343 value : {value_direction} {value_type}; 

344 base_address : in unsigned(32 - 1 downto 0) := (others => '0'); 

345 bus_handle : in bus_master_t := register_bus_master 

346 )\ 

347""" 

348 

349 @staticmethod 

350 def _should_be_able_to_access_field_as_integer(field: RegisterField) -> bool: 

351 """ 

352 Return True if the field is of a type where there should be procedures to read/write it 

353 casted as an integer. 

354 """ 

355 return isinstance(field, BitVector) and isinstance( 

356 field.numerical_interpretation, (Signed, Unsigned) 

357 ) 

358 

359 def _implementations(self) -> str: 

360 """ 

361 Get implementations of all procedures. 

362 """ 

363 separator = self.get_separator_line(indent=2) 

364 vhdl = "" 

365 

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

367 register_name = self.qualified_register_name( 

368 register=register, register_array=register_array 

369 ) 

370 implementations = [] 

371 

372 if register.mode.software_can_read: 

373 # Read the register as a plain SLV. 

374 implementations.append( 

375 self._register_read_implementation( 

376 register=register, 

377 register_array=register_array, 

378 value_type="register_t", 

379 value_conversion="reg_value", 

380 ) 

381 ) 

382 

383 # Read the register as a plain SLV casted to integer. 

384 implementations.append( 

385 self._register_read_implementation( 

386 register=register, 

387 register_array=register_array, 

388 value_type="integer", 

389 value_conversion="to_integer(unsigned(reg_value))", 

390 ) 

391 ) 

392 

393 if register.fields: 

394 # Read the register as a record. 

395 implementations.append( 

396 self._register_read_implementation( 

397 register=register, 

398 register_array=register_array, 

399 value_type=f"{register_name}_t", 

400 value_conversion=f"to_{register_name}(reg_value)", 

401 ) 

402 ) 

403 

404 for field in register.fields: 

405 # Read the field as its native type. 

406 value_type = self.field_type_name( 

407 register=register, register_array=register_array, field=field 

408 ) 

409 implementations.append( 

410 self._field_read_implementation( 

411 register=register, 

412 register_array=register_array, 

413 field=field, 

414 value_type=value_type, 

415 value_conversion=f"reg_value.{field.name}", 

416 ) 

417 ) 

418 

419 if self._should_be_able_to_access_field_as_integer(field=field): 

420 # Read the field casted to an integer. 

421 implementations.append( 

422 self._field_read_implementation( 

423 register=register, 

424 register_array=register_array, 

425 field=field, 

426 value_type="integer", 

427 value_conversion=f"to_integer(reg_value.{field.name})", 

428 ) 

429 ) 

430 

431 if register.mode.software_can_write: 

432 # Write the register as an integer. 

433 implementations.append( 

434 self._register_write_implementation( 

435 register=register, 

436 register_array=register_array, 

437 value_type="integer", 

438 value_conversion="std_ulogic_vector(to_unsigned(value, register_width))", 

439 ) 

440 ) 

441 

442 if register.fields: 

443 # Write the register as a record. 

444 implementations.append( 

445 self._register_write_implementation( 

446 register=register, 

447 register_array=register_array, 

448 value_type=f"{register_name}_t", 

449 value_conversion="to_slv(value)", 

450 ) 

451 ) 

452 else: 

453 # Write the register as a plain SLV. 

454 # Only if there are no fields. 

455 # See the signatures method for more info. 

456 implementations.append( 

457 self._register_write_implementation( 

458 register=register, 

459 register_array=register_array, 

460 value_type="register_t", 

461 value_conversion="value", 

462 ) 

463 ) 

464 

465 for field in register.fields: 

466 # Write the field as its native type. 

467 value_type = self.field_type_name( 

468 register=register, register_array=register_array, field=field 

469 ) 

470 implementations.append( 

471 self._field_write_implementation( 

472 register=register, 

473 register_array=register_array, 

474 field=field, 

475 value_type=value_type, 

476 ) 

477 ) 

478 

479 if self._should_be_able_to_access_field_as_integer(field=field): 

480 # Read the field casted to an integer. 

481 implementations.append( 

482 self._field_write_implementation( 

483 register=register, 

484 register_array=register_array, 

485 field=field, 

486 value_type="integer", 

487 ) 

488 ) 

489 

490 vhdl += separator 

491 vhdl += "\n".join(implementations) 

492 vhdl += separator 

493 vhdl += "\n" 

494 

495 return vhdl 

496 

497 def _register_read_implementation( 

498 self, 

499 register: Register, 

500 register_array: RegisterArray | None, 

501 value_type: str, 

502 value_conversion: str, 

503 ) -> str: 

504 """ 

505 Get implementation for a 'read_reg' procedure. 

506 """ 

507 signature = self._register_read_write_signature( 

508 is_read_not_write=True, 

509 register=register, 

510 register_array=register_array, 

511 value_type=value_type, 

512 ) 

513 

514 return f"""\ 

515{signature} is 

516{self.reg_index_constant(register=register, register_array=register_array)}\ 

517{self.reg_address_constant()}\ 

518 variable reg_value : register_t := (others => '0'); 

519 begin 

520 read_bus( 

521 net => net, 

522 bus_handle => bus_handle, 

523 address => std_logic_vector(reg_address), 

524 data => reg_value 

525 ); 

526 value := {value_conversion}; 

527 end procedure; 

528""" 

529 

530 def _register_write_implementation( 

531 self, 

532 register: Register, 

533 register_array: RegisterArray | None, 

534 value_type: str, 

535 value_conversion: str, 

536 ) -> str: 

537 """ 

538 Get implementation for a 'write_reg' procedure. 

539 """ 

540 signature = self._register_read_write_signature( 

541 is_read_not_write=False, 

542 register=register, 

543 register_array=register_array, 

544 value_type=value_type, 

545 ) 

546 

547 return f"""\ 

548{signature} is 

549{self.reg_index_constant(register=register, register_array=register_array)}\ 

550{self.reg_address_constant()}\ 

551 constant reg_value : register_t := {value_conversion}; 

552 begin 

553 write_bus( 

554 net => net, 

555 bus_handle => bus_handle, 

556 address => std_logic_vector(reg_address), 

557 data => reg_value 

558 ); 

559 end procedure; 

560""" 

561 

562 def _field_read_implementation( 

563 self, 

564 register: Register, 

565 register_array: RegisterArray | None, 

566 field: RegisterField, 

567 value_type: str, 

568 value_conversion: str, 

569 ) -> str: 

570 """ 

571 Get implementation for a 'read_field' procedure. 

572 """ 

573 signature = self._field_read_write_signature( 

574 is_read_not_write=True, 

575 register=register, 

576 register_array=register_array, 

577 field=field, 

578 value_type=value_type, 

579 ) 

580 

581 register_name = self.qualified_register_name( 

582 register=register, register_array=register_array 

583 ) 

584 

585 return f"""\ 

586{signature} is 

587 variable reg_value : {register_name}_t := {register_name}_init; 

588 begin 

589 read_{register_name}( 

590 net => net, 

591{self.get_array_index_association(register_array=register_array)}\ 

592 value => reg_value, 

593 base_address => base_address, 

594 bus_handle => bus_handle 

595 ); 

596 value := {value_conversion}; 

597 end procedure; 

598""" 

599 

600 def _field_write_implementation( 

601 self, 

602 register: Register, 

603 register_array: RegisterArray | None, 

604 field: RegisterField, 

605 value_type: str, 

606 ) -> str: 

607 """ 

608 Get implementation for a 'write_field' procedure. 

609 """ 

610 signature = self._field_read_write_signature( 

611 is_read_not_write=False, 

612 register=register, 

613 register_array=register_array, 

614 field=field, 

615 value_type=value_type, 

616 ) 

617 register_name = self.qualified_register_name( 

618 register=register, register_array=register_array 

619 ) 

620 

621 if self.field_setter_should_read_modify_write(register=register): 

622 set_base_value = f"""\ 

623 read_{register_name}( 

624 net => net, 

625{self.get_array_index_association(register_array=register_array)}\ 

626 value => reg_value, 

627 base_address => base_address, 

628 bus_handle => bus_handle 

629 ); 

630""" 

631 else: 

632 set_base_value = "" 

633 

634 if value_type == "integer": 

635 field_name = self.qualified_field_name( 

636 register=register, register_array=register_array, field=field 

637 ) 

638 field_width = f"{field_name}_width" 

639 

640 if isinstance(field, BitVector) and isinstance( 

641 field.numerical_interpretation, Unsigned 

642 ): 

643 field_conversion = f"to_unsigned(value, {field_width})" 

644 elif isinstance(field, BitVector) and isinstance( 

645 field.numerical_interpretation, Signed 

646 ): 

647 field_conversion = f"to_signed(value, {field_width})" 

648 else: 

649 raise ValueError(f"Should not end up here for field: {field}") 

650 else: 

651 field_conversion = "value" 

652 

653 return f"""\ 

654{signature} is 

655 variable reg_value : {register_name}_t := {register_name}_init; 

656 begin 

657{set_base_value}\ 

658 reg_value.{field.name} := {field_conversion}; 

659 

660 write_{register_name}( 

661 net => net, 

662{self.get_array_index_association(register_array=register_array)}\ 

663 value => reg_value, 

664 base_address => base_address, 

665 bus_handle => bus_handle 

666 ); 

667 end procedure; 

668"""