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

141 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_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 """ 

59 

60 __version__ = "1.1.0" 

61 

62 SHORT_DESCRIPTION = "VHDL simulation read/write package" 

63 

64 @property 

65 def output_file(self) -> Path: 

66 """ 

67 Result will be placed in this file. 

68 """ 

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

70 

71 def create( 

72 self, 

73 **kwargs: Any, # noqa: ANN401 

74 ) -> Path: 

75 """ 

76 See super class for API details. 

77 

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

79 actually has any registers. 

80 """ 

81 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

82 

83 def get_code( 

84 self, 

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

86 ) -> str: 

87 """ 

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

89 """ 

90 package_name = self.output_file.stem 

91 

92 return f"""\ 

93library ieee; 

94use ieee.numeric_std.all; 

95use ieee.std_logic_1164.all; 

96 

97library vunit_lib; 

98use vunit_lib.bus_master_pkg.bus_master_t; 

99use vunit_lib.bus_master_pkg.read_bus; 

100use vunit_lib.bus_master_pkg.write_bus; 

101use vunit_lib.com_types_pkg.network_t; 

102 

103library common; 

104use common.addr_pkg.addr_t; 

105use common.addr_pkg.addr_width; 

106 

107library register_file; 

108use register_file.register_file_pkg.register_t; 

109use register_file.register_file_pkg.register_width; 

110use register_file.register_operations_pkg.register_bus_master; 

111 

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

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

114 

115 

116package {package_name} is 

117 

118{self._declarations()}\ 

119end package; 

120 

121package body {package_name} is 

122 

123{self._implementations()}\ 

124end package body; 

125""" 

126 

127 def _declarations(self) -> str: 

128 """ 

129 Get procedure declarations for all procedures. 

130 """ 

131 separator = self.get_separator_line(indent=2) 

132 vhdl = "" 

133 

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

135 register_name = self.qualified_register_name( 

136 register=register, register_array=register_array 

137 ) 

138 declarations = [] 

139 

140 if register.mode.software_can_read: 

141 # Read the register as a plain SLV. 

142 signature = self._register_read_write_signature( 

143 is_read_not_write=True, 

144 register=register, 

145 register_array=register_array, 

146 value_type="register_t", 

147 ) 

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

149 

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

151 signature = self._register_read_write_signature( 

152 is_read_not_write=True, 

153 register=register, 

154 register_array=register_array, 

155 value_type="integer", 

156 ) 

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

158 

159 if register.fields: 

160 # Read the register as a record. 

161 signature = self._register_read_write_signature( 

162 is_read_not_write=True, 

163 register=register, 

164 register_array=register_array, 

165 value_type=f"{register_name}_t", 

166 ) 

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

168 

169 for field in register.fields: 

170 # Read the field as its native type. 

171 value_type = self.field_type_name( 

172 register=register, register_array=register_array, field=field 

173 ) 

174 signature = self._field_read_write_signature( 

175 is_read_not_write=True, 

176 register=register, 

177 register_array=register_array, 

178 field=field, 

179 value_type=value_type, 

180 ) 

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

182 

183 if self._should_be_able_to_access_field_as_integer(field=field): 

184 # Read the field casted to an integer. 

185 signature = self._field_read_write_signature( 

186 is_read_not_write=True, 

187 register=register, 

188 register_array=register_array, 

189 field=field, 

190 value_type="integer", 

191 ) 

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

193 

194 if register.mode.software_can_write: 

195 # Write the register as an integer. 

196 signature = self._register_read_write_signature( 

197 is_read_not_write=False, 

198 register=register, 

199 register_array=register_array, 

200 value_type="integer", 

201 ) 

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

203 

204 if register.fields: 

205 # Write the register as a record. 

206 signature = self._register_read_write_signature( 

207 is_read_not_write=False, 

208 register=register, 

209 register_array=register_array, 

210 value_type=f"{register_name}_t", 

211 ) 

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

213 else: 

214 # Write the register as a plain SLV. 

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

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

217 # that some compilers can not resolve. 

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

219 # Where the field is a std_logic. 

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

221 # or the one with SLV. 

222 signature = self._register_read_write_signature( 

223 is_read_not_write=False, 

224 register=register, 

225 register_array=register_array, 

226 value_type="register_t", 

227 ) 

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

229 

230 for field in register.fields: 

231 # Write the field as its native type. 

232 value_type = self.field_type_name( 

233 register=register, register_array=register_array, field=field 

234 ) 

235 signature = self._field_read_write_signature( 

236 is_read_not_write=False, 

237 register=register, 

238 register_array=register_array, 

239 field=field, 

240 value_type=value_type, 

241 ) 

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

243 

244 if self._should_be_able_to_access_field_as_integer(field=field): 

245 # Write the field casted to an integer. 

246 signature = self._field_read_write_signature( 

247 is_read_not_write=False, 

248 register=register, 

249 register_array=register_array, 

250 field=field, 

251 value_type="integer", 

252 ) 

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

254 

255 vhdl += separator 

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

257 vhdl += separator 

258 vhdl += "\n" 

259 

260 return vhdl 

261 

262 def _register_read_write_signature( 

263 self, 

264 is_read_not_write: bool, 

265 register: Register, 

266 register_array: RegisterArray | None, 

267 value_type: str, 

268 ) -> str: 

269 """ 

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

271 """ 

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

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

274 

275 register_name = self.qualified_register_name( 

276 register=register, register_array=register_array 

277 ) 

278 register_description = self.register_description( 

279 register=register, register_array=register_array 

280 ) 

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

282 # since it is the default. 

283 type_comment = ( 

284 " as a plain 'register_t'" 

285 if value_type == "register_t" 

286 else " as an 'integer'" 

287 if value_type == "integer" 

288 else "" 

289 ) 

290 

291 return f"""\ 

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

293 procedure {direction}_{register_name}( 

294 signal net : inout network_t; 

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

296 value : {value_direction} {value_type}; 

297 base_address : in addr_t := (others => '0'); 

298 bus_handle : in bus_master_t := register_bus_master 

299 )\ 

300""" 

301 

302 def _field_read_write_signature( 

303 self, 

304 is_read_not_write: bool, 

305 register: Register, 

306 register_array: RegisterArray | None, 

307 field: RegisterField, 

308 value_type: str, 

309 ) -> str: 

310 """ 

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

312 """ 

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

314 

315 field_name = self.qualified_field_name( 

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

317 ) 

318 field_description = self.field_description( 

319 register=register, field=field, register_array=register_array 

320 ) 

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

322 # since it is the default. 

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

324 

325 comment = "" 

326 if not is_read_not_write: 

327 if self.field_setter_should_read_modify_write(register=register): 

328 comment = ( 

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

330 "supplied 'value'.\n" 

331 ) 

332 else: 

333 comment = ( 

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

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

336 ) 

337 

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

339 

340 return f"""\ 

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

342{comment}\ 

343 procedure {direction}_{field_name}( 

344 signal net : inout network_t; 

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

346 value : {value_direction} {value_type}; 

347 base_address : in addr_t := (others => '0'); 

348 bus_handle : in bus_master_t := register_bus_master 

349 )\ 

350""" 

351 

352 @staticmethod 

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

354 """ 

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

356 casted as an integer. 

357 """ 

358 return isinstance(field, BitVector) and isinstance( 

359 field.numerical_interpretation, (Signed, Unsigned) 

360 ) 

361 

362 def _implementations(self) -> str: 

363 """ 

364 Get implementations of all procedures. 

365 """ 

366 separator = self.get_separator_line(indent=2) 

367 vhdl = "" 

368 

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

370 register_name = self.qualified_register_name( 

371 register=register, register_array=register_array 

372 ) 

373 implementations = [] 

374 

375 if register.mode.software_can_read: 

376 # Read the register as a plain SLV. 

377 implementations.append( 

378 self._register_read_implementation( 

379 register=register, 

380 register_array=register_array, 

381 value_type="register_t", 

382 value_conversion="reg_value", 

383 ) 

384 ) 

385 

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

387 implementations.append( 

388 self._register_read_implementation( 

389 register=register, 

390 register_array=register_array, 

391 value_type="integer", 

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

393 ) 

394 ) 

395 

396 if register.fields: 

397 # Read the register as a record. 

398 implementations.append( 

399 self._register_read_implementation( 

400 register=register, 

401 register_array=register_array, 

402 value_type=f"{register_name}_t", 

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

404 ) 

405 ) 

406 

407 for field in register.fields: 

408 # Read the field as its native type. 

409 value_type = self.field_type_name( 

410 register=register, register_array=register_array, field=field 

411 ) 

412 implementations.append( 

413 self._field_read_implementation( 

414 register=register, 

415 register_array=register_array, 

416 field=field, 

417 value_type=value_type, 

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

419 ) 

420 ) 

421 

422 if self._should_be_able_to_access_field_as_integer(field=field): 

423 # Read the field casted to an integer. 

424 implementations.append( 

425 self._field_read_implementation( 

426 register=register, 

427 register_array=register_array, 

428 field=field, 

429 value_type="integer", 

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

431 ) 

432 ) 

433 

434 if register.mode.software_can_write: 

435 # Write the register as an integer. 

436 implementations.append( 

437 self._register_write_implementation( 

438 register=register, 

439 register_array=register_array, 

440 value_type="integer", 

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

442 ) 

443 ) 

444 

445 if register.fields: 

446 # Write the register as a record. 

447 implementations.append( 

448 self._register_write_implementation( 

449 register=register, 

450 register_array=register_array, 

451 value_type=f"{register_name}_t", 

452 value_conversion="to_slv(value)", 

453 ) 

454 ) 

455 else: 

456 # Write the register as a plain SLV. 

457 # Only if there are no fields. 

458 # See the signatures method for more info. 

459 implementations.append( 

460 self._register_write_implementation( 

461 register=register, 

462 register_array=register_array, 

463 value_type="register_t", 

464 value_conversion="value", 

465 ) 

466 ) 

467 

468 for field in register.fields: 

469 # Write the field as its native type. 

470 value_type = self.field_type_name( 

471 register=register, register_array=register_array, field=field 

472 ) 

473 implementations.append( 

474 self._field_write_implementation( 

475 register=register, 

476 register_array=register_array, 

477 field=field, 

478 value_type=value_type, 

479 ) 

480 ) 

481 

482 if self._should_be_able_to_access_field_as_integer(field=field): 

483 # Read the field casted to an integer. 

484 implementations.append( 

485 self._field_write_implementation( 

486 register=register, 

487 register_array=register_array, 

488 field=field, 

489 value_type="integer", 

490 ) 

491 ) 

492 

493 vhdl += separator 

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

495 vhdl += separator 

496 vhdl += "\n" 

497 

498 return vhdl 

499 

500 def _register_read_implementation( 

501 self, 

502 register: Register, 

503 register_array: RegisterArray | None, 

504 value_type: str, 

505 value_conversion: str, 

506 ) -> str: 

507 """ 

508 Get implementation for a 'read_reg' procedure. 

509 """ 

510 signature = self._register_read_write_signature( 

511 is_read_not_write=True, 

512 register=register, 

513 register_array=register_array, 

514 value_type=value_type, 

515 ) 

516 

517 return f"""\ 

518{signature} is 

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

520{self.reg_address_constant()}\ 

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

522 begin 

523 read_bus( 

524 net => net, 

525 bus_handle => bus_handle, 

526 address => std_logic_vector(reg_address), 

527 data => reg_value 

528 ); 

529 value := {value_conversion}; 

530 end procedure; 

531""" 

532 

533 def _register_write_implementation( 

534 self, 

535 register: Register, 

536 register_array: RegisterArray | None, 

537 value_type: str, 

538 value_conversion: str, 

539 ) -> str: 

540 """ 

541 Get implementation for a 'write_reg' procedure. 

542 """ 

543 signature = self._register_read_write_signature( 

544 is_read_not_write=False, 

545 register=register, 

546 register_array=register_array, 

547 value_type=value_type, 

548 ) 

549 

550 return f"""\ 

551{signature} is 

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

553{self.reg_address_constant()}\ 

554 constant reg_value : register_t := {value_conversion}; 

555 begin 

556 write_bus( 

557 net => net, 

558 bus_handle => bus_handle, 

559 address => std_logic_vector(reg_address), 

560 data => reg_value 

561 ); 

562 end procedure; 

563""" 

564 

565 def _field_read_implementation( 

566 self, 

567 register: Register, 

568 register_array: RegisterArray | None, 

569 field: RegisterField, 

570 value_type: str, 

571 value_conversion: str, 

572 ) -> str: 

573 """ 

574 Get implementation for a 'read_field' procedure. 

575 """ 

576 signature = self._field_read_write_signature( 

577 is_read_not_write=True, 

578 register=register, 

579 register_array=register_array, 

580 field=field, 

581 value_type=value_type, 

582 ) 

583 

584 register_name = self.qualified_register_name( 

585 register=register, register_array=register_array 

586 ) 

587 

588 return f"""\ 

589{signature} is 

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

591 begin 

592 read_{register_name}( 

593 net => net, 

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

595 value => reg_value, 

596 base_address => base_address, 

597 bus_handle => bus_handle 

598 ); 

599 value := {value_conversion}; 

600 end procedure; 

601""" 

602 

603 def _field_write_implementation( 

604 self, 

605 register: Register, 

606 register_array: RegisterArray | None, 

607 field: RegisterField, 

608 value_type: str, 

609 ) -> str: 

610 """ 

611 Get implementation for a 'write_field' procedure. 

612 """ 

613 signature = self._field_read_write_signature( 

614 is_read_not_write=False, 

615 register=register, 

616 register_array=register_array, 

617 field=field, 

618 value_type=value_type, 

619 ) 

620 register_name = self.qualified_register_name( 

621 register=register, register_array=register_array 

622 ) 

623 

624 if self.field_setter_should_read_modify_write(register=register): 

625 set_base_value = f"""\ 

626 read_{register_name}( 

627 net => net, 

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

629 value => reg_value, 

630 base_address => base_address, 

631 bus_handle => bus_handle 

632 ); 

633""" 

634 else: 

635 set_base_value = "" 

636 

637 if value_type == "integer": 

638 field_name = self.qualified_field_name( 

639 register=register, register_array=register_array, field=field 

640 ) 

641 field_width = f"{field_name}_width" 

642 

643 if isinstance(field, BitVector) and isinstance( 

644 field.numerical_interpretation, Unsigned 

645 ): 

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

647 elif isinstance(field, BitVector) and isinstance( 

648 field.numerical_interpretation, Signed 

649 ): 

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

651 else: 

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

653 else: 

654 field_conversion = "value" 

655 

656 return f"""\ 

657{signature} is 

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

659 begin 

660{set_base_value}\ 

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

662 

663 write_{register_name}( 

664 net => net, 

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

666 value => reg_value, 

667 base_address => base_address, 

668 bus_handle => bus_handle 

669 ); 

670 end procedure; 

671"""