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

141 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_vector import BitVector 

16from hdl_registers.field.numerical_interpretation import Signed, Unsigned 

17 

18# Local folder libraries 

19from .vhdl_simulation_generator_common import VhdlSimulationGeneratorCommon 

20 

21if TYPE_CHECKING: 

22 # First party libraries 

23 from hdl_registers.field.register_field import RegisterField 

24 from hdl_registers.register import Register 

25 from hdl_registers.register_array import RegisterArray 

26 

27 

28class VhdlSimulationReadWritePackageGenerator(VhdlSimulationGeneratorCommon): 

29 """ 

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

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

32 

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

34 Value can be read as: 

35 

36 1. bit vector, 

37 

38 2. integer, or 

39 

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

41 

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

43 the field. 

44 

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

46 Value can be written as: 

47 

48 1. bit vector, or 

49 

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

51 

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

53 natively-typed field value. 

54 

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

56 

57 The generated VHDL file needs also the generated packages from 

58 :class:`.VhdlRegisterPackageGenerator` and :class:`.VhdlRecordPackageGenerator`. 

59 """ 

60 

61 __version__ = "1.1.0" 

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(self, **kwargs: Any) -> Path: 

73 """ 

74 See super class for API details. 

75 

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

77 actually has any registers. 

78 """ 

79 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs) 

80 

81 def get_code(self, **kwargs: Any) -> str: 

82 """ 

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

84 """ 

85 package_name = self.output_file.stem 

86 

87 vhdl = f"""\ 

88library ieee; 

89use ieee.numeric_std.all; 

90use ieee.std_logic_1164.all; 

91 

92library vunit_lib; 

93use vunit_lib.bus_master_pkg.bus_master_t; 

94use vunit_lib.bus_master_pkg.read_bus; 

95use vunit_lib.bus_master_pkg.write_bus; 

96use vunit_lib.com_types_pkg.network_t; 

97 

98library common; 

99use common.addr_pkg.addr_t; 

100use common.addr_pkg.addr_width; 

101 

102library register_file; 

103use register_file.register_file_pkg.register_t; 

104use register_file.register_file_pkg.register_width; 

105use register_file.register_operations_pkg.register_bus_master; 

106 

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

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

109 

110 

111package {package_name} is 

112 

113{self._declarations()}\ 

114end package; 

115 

116package body {package_name} is 

117 

118{self._implementations()}\ 

119end package body; 

120""" 

121 

122 return vhdl 

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: Optional["RegisterArray"], 

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'" if value_type == "integer" else "" 

284 ) 

285 

286 return f"""\ 

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

288 procedure {direction}_{register_name}( 

289 signal net : inout network_t; 

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

291 value : {value_direction} {value_type}; 

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

293 bus_handle : in bus_master_t := register_bus_master 

294 )\ 

295""" 

296 

297 def _field_read_write_signature( 

298 self, 

299 is_read_not_write: bool, 

300 register: "Register", 

301 register_array: Optional["RegisterArray"], 

302 field: "RegisterField", 

303 value_type: str, 

304 ) -> str: 

305 """ 

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

307 """ 

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

309 

310 field_name = self.qualified_field_name( 

311 register=register, register_array=register_array, field=field 

312 ) 

313 field_description = self.field_description( 

314 register=register, field=field, register_array=register_array 

315 ) 

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

317 # since it is the default. 

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

319 

320 comment = "" 

321 if not is_read_not_write: 

322 if self.field_setter_should_read_modify_write(register=register): 

323 comment = ( 

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

325 "supplied 'value'.\n" 

326 ) 

327 else: 

328 comment = ( 

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

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

331 ) 

332 

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

334 

335 return f"""\ 

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

337{comment}\ 

338 procedure {direction}_{field_name}( 

339 signal net : inout network_t; 

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

341 value : {value_direction} {value_type}; 

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

343 bus_handle : in bus_master_t := register_bus_master 

344 )\ 

345""" 

346 

347 @staticmethod 

348 def _should_be_able_to_access_field_as_integer(field: "RegisterField") -> bool: 

349 """ 

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

351 casted as an integer. 

352 """ 

353 return isinstance(field, BitVector) and isinstance( 

354 field.numerical_interpretation, (Signed, Unsigned) 

355 ) 

356 

357 def _implementations(self) -> str: 

358 """ 

359 Get implementations of all procedures. 

360 """ 

361 separator = self.get_separator_line(indent=2) 

362 vhdl = "" 

363 

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

365 register_name = self.qualified_register_name( 

366 register=register, register_array=register_array 

367 ) 

368 implementations = [] 

369 

370 if register.mode.software_can_read: 

371 # Read the register as a plain SLV. 

372 implementations.append( 

373 self._register_read_implementation( 

374 register=register, 

375 register_array=register_array, 

376 value_type="register_t", 

377 value_conversion="reg_value", 

378 ) 

379 ) 

380 

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

382 implementations.append( 

383 self._register_read_implementation( 

384 register=register, 

385 register_array=register_array, 

386 value_type="integer", 

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

388 ) 

389 ) 

390 

391 if register.fields: 

392 # Read the register as a record. 

393 implementations.append( 

394 self._register_read_implementation( 

395 register=register, 

396 register_array=register_array, 

397 value_type=f"{register_name}_t", 

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

399 ) 

400 ) 

401 

402 for field in register.fields: 

403 # Read the field as its native type. 

404 value_type = self.field_type_name( 

405 register=register, register_array=register_array, field=field 

406 ) 

407 implementations.append( 

408 self._field_read_implementation( 

409 register=register, 

410 register_array=register_array, 

411 field=field, 

412 value_type=value_type, 

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

414 ) 

415 ) 

416 

417 if self._should_be_able_to_access_field_as_integer(field=field): 

418 # Read the field casted to an integer. 

419 implementations.append( 

420 self._field_read_implementation( 

421 register=register, 

422 register_array=register_array, 

423 field=field, 

424 value_type="integer", 

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

426 ) 

427 ) 

428 

429 if register.mode.software_can_write: 

430 # Write the register as an integer. 

431 implementations.append( 

432 self._register_write_implementation( 

433 register=register, 

434 register_array=register_array, 

435 value_type="integer", 

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

437 ) 

438 ) 

439 

440 if register.fields: 

441 # Write the register as a record. 

442 implementations.append( 

443 self._register_write_implementation( 

444 register=register, 

445 register_array=register_array, 

446 value_type=f"{register_name}_t", 

447 value_conversion="to_slv(value)", 

448 ) 

449 ) 

450 else: 

451 # Write the register as a plain SLV. 

452 # Only if there are no fields. 

453 # See the signatures method for more info. 

454 implementations.append( 

455 self._register_write_implementation( 

456 register=register, 

457 register_array=register_array, 

458 value_type="register_t", 

459 value_conversion="value", 

460 ) 

461 ) 

462 

463 for field in register.fields: 

464 # Write the field as its native type. 

465 value_type = self.field_type_name( 

466 register=register, register_array=register_array, field=field 

467 ) 

468 implementations.append( 

469 self._field_write_implementation( 

470 register=register, 

471 register_array=register_array, 

472 field=field, 

473 value_type=value_type, 

474 ) 

475 ) 

476 

477 if self._should_be_able_to_access_field_as_integer(field=field): 

478 # Read the field casted to an integer. 

479 implementations.append( 

480 self._field_write_implementation( 

481 register=register, 

482 register_array=register_array, 

483 field=field, 

484 value_type="integer", 

485 ) 

486 ) 

487 

488 vhdl += separator 

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

490 vhdl += separator 

491 vhdl += "\n" 

492 

493 return vhdl 

494 

495 def _register_read_implementation( 

496 self, 

497 register: "Register", 

498 register_array: Optional["RegisterArray"], 

499 value_type: str, 

500 value_conversion: str, 

501 ) -> str: 

502 """ 

503 Get implementation for a 'read_reg' procedure. 

504 """ 

505 signature = self._register_read_write_signature( 

506 is_read_not_write=True, 

507 register=register, 

508 register_array=register_array, 

509 value_type=value_type, 

510 ) 

511 

512 return f"""\ 

513{signature} is 

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

515{self.reg_address_constant()}\ 

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

517 begin 

518 read_bus( 

519 net => net, 

520 bus_handle => bus_handle, 

521 address => std_logic_vector(reg_address), 

522 data => reg_value 

523 ); 

524 value := {value_conversion}; 

525 end procedure; 

526""" 

527 

528 def _register_write_implementation( 

529 self, 

530 register: "Register", 

531 register_array: Optional["RegisterArray"], 

532 value_type: str, 

533 value_conversion: str, 

534 ) -> str: 

535 """ 

536 Get implementation for a 'write_reg' procedure. 

537 """ 

538 signature = self._register_read_write_signature( 

539 is_read_not_write=False, 

540 register=register, 

541 register_array=register_array, 

542 value_type=value_type, 

543 ) 

544 

545 return f"""\ 

546{signature} is 

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

548{self.reg_address_constant()}\ 

549 constant reg_value : register_t := {value_conversion}; 

550 begin 

551 write_bus( 

552 net => net, 

553 bus_handle => bus_handle, 

554 address => std_logic_vector(reg_address), 

555 data => reg_value 

556 ); 

557 end procedure; 

558""" 

559 

560 def _field_read_implementation( 

561 self, 

562 register: "Register", 

563 register_array: Optional["RegisterArray"], 

564 field: "RegisterField", 

565 value_type: str, 

566 value_conversion: str, 

567 ) -> str: 

568 """ 

569 Get implementation for a 'read_field' procedure. 

570 """ 

571 signature = self._field_read_write_signature( 

572 is_read_not_write=True, 

573 register=register, 

574 register_array=register_array, 

575 field=field, 

576 value_type=value_type, 

577 ) 

578 

579 register_name = self.qualified_register_name( 

580 register=register, register_array=register_array 

581 ) 

582 

583 return f"""\ 

584{signature} is 

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

586 begin 

587 read_{register_name}( 

588 net => net, 

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

590 value => reg_value, 

591 base_address => base_address, 

592 bus_handle => bus_handle 

593 ); 

594 value := {value_conversion}; 

595 end procedure; 

596""" 

597 

598 def _field_write_implementation( 

599 self, 

600 register: "Register", 

601 register_array: Optional["RegisterArray"], 

602 field: "RegisterField", 

603 value_type: str, 

604 ) -> str: 

605 """ 

606 Get implementation for a 'write_field' procedure. 

607 """ 

608 signature = self._field_read_write_signature( 

609 is_read_not_write=False, 

610 register=register, 

611 register_array=register_array, 

612 field=field, 

613 value_type=value_type, 

614 ) 

615 register_name = self.qualified_register_name( 

616 register=register, register_array=register_array 

617 ) 

618 

619 if self.field_setter_should_read_modify_write(register=register): 

620 set_base_value = f"""\ 

621 read_{register_name}( 

622 net => net, 

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

624 value => reg_value, 

625 base_address => base_address, 

626 bus_handle => bus_handle 

627 ); 

628""" 

629 else: 

630 set_base_value = "" 

631 

632 if value_type == "integer": 

633 field_name = self.qualified_field_name( 

634 register=register, register_array=register_array, field=field 

635 ) 

636 field_width = f"{field_name}_width" 

637 

638 if isinstance(field, BitVector) and isinstance( 

639 field.numerical_interpretation, Unsigned 

640 ): 

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

642 elif isinstance(field, BitVector) and isinstance( 

643 field.numerical_interpretation, Signed 

644 ): 

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

646 else: 

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

648 else: 

649 field_conversion = "value" 

650 

651 return f"""\ 

652{signature} is 

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

654 begin 

655{set_base_value}\ 

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

657 

658 write_{register_name}( 

659 net => net, 

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

661 value => reg_value, 

662 base_address => base_address, 

663 bus_handle => bus_handle 

664 ); 

665 end procedure; 

666"""