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

141 statements  

« prev     ^ index     » next       coverage.py v7.6.8, created at 2024-12-01 20:50 +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"""\ 

88{self.header} 

89library ieee; 

90use ieee.numeric_std.all; 

91use ieee.std_logic_1164.all; 

92 

93library vunit_lib; 

94use vunit_lib.bus_master_pkg.bus_master_t; 

95use vunit_lib.bus_master_pkg.read_bus; 

96use vunit_lib.bus_master_pkg.write_bus; 

97use vunit_lib.com_types_pkg.network_t; 

98 

99library common; 

100use common.addr_pkg.addr_t; 

101use common.addr_pkg.addr_width; 

102 

103library reg_file; 

104use reg_file.reg_file_pkg.reg_t; 

105use reg_file.reg_file_pkg.reg_width; 

106use reg_file.reg_operations_pkg.regs_bus_master; 

107 

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

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

110 

111 

112package {package_name} is 

113 

114{self._declarations()}\ 

115end package; 

116 

117package body {package_name} is 

118 

119{self._implementations()}\ 

120end package body; 

121""" 

122 

123 return vhdl 

124 

125 def _declarations(self) -> str: 

126 """ 

127 Get procedure declarations for all procedures. 

128 """ 

129 separator = self.get_separator_line(indent=2) 

130 vhdl = "" 

131 

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

133 register_name = self.qualified_register_name( 

134 register=register, register_array=register_array 

135 ) 

136 declarations = [] 

137 

138 if register.mode.software_can_read: 

139 # Read the register as a plain SLV. 

140 signature = self._register_read_write_signature( 

141 is_read_not_write=True, 

142 register=register, 

143 register_array=register_array, 

144 value_type="reg_t", 

145 ) 

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

147 

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

149 signature = self._register_read_write_signature( 

150 is_read_not_write=True, 

151 register=register, 

152 register_array=register_array, 

153 value_type="integer", 

154 ) 

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

156 

157 if register.fields: 

158 # Read the register as a record. 

159 signature = self._register_read_write_signature( 

160 is_read_not_write=True, 

161 register=register, 

162 register_array=register_array, 

163 value_type=f"{register_name}_t", 

164 ) 

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

166 

167 for field in register.fields: 

168 # Read the field as its native type. 

169 value_type = self.field_type_name( 

170 register=register, register_array=register_array, field=field 

171 ) 

172 signature = self._field_read_write_signature( 

173 is_read_not_write=True, 

174 register=register, 

175 register_array=register_array, 

176 field=field, 

177 value_type=value_type, 

178 ) 

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

180 

181 if self._should_be_able_to_access_field_as_integer(field=field): 

182 # Read the field casted to an integer. 

183 signature = self._field_read_write_signature( 

184 is_read_not_write=True, 

185 register=register, 

186 register_array=register_array, 

187 field=field, 

188 value_type="integer", 

189 ) 

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

191 

192 if register.mode.software_can_write: 

193 # Write the register as an integer. 

194 signature = self._register_read_write_signature( 

195 is_read_not_write=False, 

196 register=register, 

197 register_array=register_array, 

198 value_type="integer", 

199 ) 

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

201 

202 if register.fields: 

203 # Write the register as a record. 

204 signature = self._register_read_write_signature( 

205 is_read_not_write=False, 

206 register=register, 

207 register_array=register_array, 

208 value_type=f"{register_name}_t", 

209 ) 

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

211 else: 

212 # Write the register as a plain SLV. 

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

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

215 # that some compilers can not resolve. 

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

217 # Where the field is a std_logic. 

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

219 # or the one with SLV. 

220 signature = self._register_read_write_signature( 

221 is_read_not_write=False, 

222 register=register, 

223 register_array=register_array, 

224 value_type="reg_t", 

225 ) 

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

227 

228 for field in register.fields: 

229 # Write the field as its native type. 

230 value_type = self.field_type_name( 

231 register=register, register_array=register_array, field=field 

232 ) 

233 signature = self._field_read_write_signature( 

234 is_read_not_write=False, 

235 register=register, 

236 register_array=register_array, 

237 field=field, 

238 value_type=value_type, 

239 ) 

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

241 

242 if self._should_be_able_to_access_field_as_integer(field=field): 

243 # Write the field casted to an integer. 

244 signature = self._field_read_write_signature( 

245 is_read_not_write=False, 

246 register=register, 

247 register_array=register_array, 

248 field=field, 

249 value_type="integer", 

250 ) 

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

252 

253 vhdl += separator 

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

255 vhdl += separator 

256 vhdl += "\n" 

257 

258 return vhdl 

259 

260 def _register_read_write_signature( 

261 self, 

262 is_read_not_write: bool, 

263 register: "Register", 

264 register_array: Optional["RegisterArray"], 

265 value_type: str, 

266 ) -> str: 

267 """ 

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

269 """ 

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

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

272 

273 register_name = self.qualified_register_name( 

274 register=register, register_array=register_array 

275 ) 

276 register_description = self.register_description( 

277 register=register, register_array=register_array 

278 ) 

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

280 # since it is the default. 

281 type_comment = ( 

282 " as a plain 'reg_t'" 

283 if value_type == "reg_t" 

284 else " as an 'integer'" if value_type == "integer" else "" 

285 ) 

286 

287 return f"""\ 

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

289 procedure {direction}_{register_name}( 

290 signal net : inout network_t; 

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

292 value : {value_direction} {value_type}; 

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

294 bus_handle : in bus_master_t := regs_bus_master 

295 )\ 

296""" 

297 

298 def _field_read_write_signature( 

299 self, 

300 is_read_not_write: bool, 

301 register: "Register", 

302 register_array: Optional["RegisterArray"], 

303 field: "RegisterField", 

304 value_type: str, 

305 ) -> str: 

306 """ 

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

308 """ 

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

310 

311 field_name = self.qualified_field_name( 

312 register=register, register_array=register_array, field=field 

313 ) 

314 field_description = self.field_description( 

315 register=register, field=field, register_array=register_array 

316 ) 

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

318 # since it is the default. 

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

320 

321 comment = "" 

322 if not is_read_not_write: 

323 if self.field_setter_should_read_modify_write(register=register): 

324 comment = ( 

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

326 "supplied 'value'.\n" 

327 ) 

328 else: 

329 comment = ( 

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

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

332 ) 

333 

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

335 

336 return f"""\ 

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

338{comment}\ 

339 procedure {direction}_{field_name}( 

340 signal net : inout network_t; 

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

342 value : {value_direction} {value_type}; 

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

344 bus_handle : in bus_master_t := regs_bus_master 

345 )\ 

346""" 

347 

348 @staticmethod 

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

350 """ 

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

352 casted as an integer. 

353 """ 

354 return isinstance(field, BitVector) and isinstance( 

355 field.numerical_interpretation, (Signed, Unsigned) 

356 ) 

357 

358 def _implementations(self) -> str: 

359 """ 

360 Get implementations of all procedures. 

361 """ 

362 separator = self.get_separator_line(indent=2) 

363 vhdl = "" 

364 

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

366 register_name = self.qualified_register_name( 

367 register=register, register_array=register_array 

368 ) 

369 implementations = [] 

370 

371 if register.mode.software_can_read: 

372 # Read the register as a plain SLV. 

373 implementations.append( 

374 self._register_read_implementation( 

375 register=register, 

376 register_array=register_array, 

377 value_type="reg_t", 

378 value_conversion="reg_value", 

379 ) 

380 ) 

381 

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

383 implementations.append( 

384 self._register_read_implementation( 

385 register=register, 

386 register_array=register_array, 

387 value_type="integer", 

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

389 ) 

390 ) 

391 

392 if register.fields: 

393 # Read the register as a record. 

394 implementations.append( 

395 self._register_read_implementation( 

396 register=register, 

397 register_array=register_array, 

398 value_type=f"{register_name}_t", 

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

400 ) 

401 ) 

402 

403 for field in register.fields: 

404 # Read the field as its native type. 

405 value_type = self.field_type_name( 

406 register=register, register_array=register_array, field=field 

407 ) 

408 implementations.append( 

409 self._field_read_implementation( 

410 register=register, 

411 register_array=register_array, 

412 field=field, 

413 value_type=value_type, 

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

415 ) 

416 ) 

417 

418 if self._should_be_able_to_access_field_as_integer(field=field): 

419 # Read the field casted to an integer. 

420 implementations.append( 

421 self._field_read_implementation( 

422 register=register, 

423 register_array=register_array, 

424 field=field, 

425 value_type="integer", 

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

427 ) 

428 ) 

429 

430 if register.mode.software_can_write: 

431 # Write the register as an integer. 

432 implementations.append( 

433 self._register_write_implementation( 

434 register=register, 

435 register_array=register_array, 

436 value_type="integer", 

437 value_conversion="std_ulogic_vector(to_unsigned(value, reg_width))", 

438 ) 

439 ) 

440 

441 if register.fields: 

442 # Write the register as a record. 

443 implementations.append( 

444 self._register_write_implementation( 

445 register=register, 

446 register_array=register_array, 

447 value_type=f"{register_name}_t", 

448 value_conversion="to_slv(value)", 

449 ) 

450 ) 

451 else: 

452 # Write the register as a plain SLV. 

453 # Only if there are no fields. 

454 # See the signatures method for more info. 

455 implementations.append( 

456 self._register_write_implementation( 

457 register=register, 

458 register_array=register_array, 

459 value_type="reg_t", 

460 value_conversion="value", 

461 ) 

462 ) 

463 

464 for field in register.fields: 

465 # Write the field as its native type. 

466 value_type = self.field_type_name( 

467 register=register, register_array=register_array, field=field 

468 ) 

469 implementations.append( 

470 self._field_write_implementation( 

471 register=register, 

472 register_array=register_array, 

473 field=field, 

474 value_type=value_type, 

475 ) 

476 ) 

477 

478 if self._should_be_able_to_access_field_as_integer(field=field): 

479 # Read the field casted to an integer. 

480 implementations.append( 

481 self._field_write_implementation( 

482 register=register, 

483 register_array=register_array, 

484 field=field, 

485 value_type="integer", 

486 ) 

487 ) 

488 

489 vhdl += separator 

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

491 vhdl += separator 

492 vhdl += "\n" 

493 

494 return vhdl 

495 

496 def _register_read_implementation( 

497 self, 

498 register: "Register", 

499 register_array: Optional["RegisterArray"], 

500 value_type: str, 

501 value_conversion: str, 

502 ) -> str: 

503 """ 

504 Get implementation for a 'read_reg' procedure. 

505 """ 

506 signature = self._register_read_write_signature( 

507 is_read_not_write=True, 

508 register=register, 

509 register_array=register_array, 

510 value_type=value_type, 

511 ) 

512 

513 return f"""\ 

514{signature} is 

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

516{self.reg_address_constant()}\ 

517 variable reg_value : reg_t := (others => '0'); 

518 begin 

519 read_bus( 

520 net => net, 

521 bus_handle => bus_handle, 

522 address => std_logic_vector(reg_address), 

523 data => reg_value 

524 ); 

525 value := {value_conversion}; 

526 end procedure; 

527""" 

528 

529 def _register_write_implementation( 

530 self, 

531 register: "Register", 

532 register_array: Optional["RegisterArray"], 

533 value_type: str, 

534 value_conversion: str, 

535 ) -> str: 

536 """ 

537 Get implementation for a 'write_reg' procedure. 

538 """ 

539 signature = self._register_read_write_signature( 

540 is_read_not_write=False, 

541 register=register, 

542 register_array=register_array, 

543 value_type=value_type, 

544 ) 

545 

546 return f"""\ 

547{signature} is 

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

549{self.reg_address_constant()}\ 

550 constant reg_value : reg_t := {value_conversion}; 

551 begin 

552 write_bus( 

553 net => net, 

554 bus_handle => bus_handle, 

555 address => std_logic_vector(reg_address), 

556 data => reg_value 

557 ); 

558 end procedure; 

559""" 

560 

561 def _field_read_implementation( 

562 self, 

563 register: "Register", 

564 register_array: Optional["RegisterArray"], 

565 field: "RegisterField", 

566 value_type: str, 

567 value_conversion: str, 

568 ) -> str: 

569 """ 

570 Get implementation for a 'read_field' procedure. 

571 """ 

572 signature = self._field_read_write_signature( 

573 is_read_not_write=True, 

574 register=register, 

575 register_array=register_array, 

576 field=field, 

577 value_type=value_type, 

578 ) 

579 

580 register_name = self.qualified_register_name( 

581 register=register, register_array=register_array 

582 ) 

583 

584 return f"""\ 

585{signature} is 

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

587 begin 

588 read_{register_name}( 

589 net => net, 

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

591 value => reg_value, 

592 base_address => base_address, 

593 bus_handle => bus_handle 

594 ); 

595 value := {value_conversion}; 

596 end procedure; 

597""" 

598 

599 def _field_write_implementation( 

600 self, 

601 register: "Register", 

602 register_array: Optional["RegisterArray"], 

603 field: "RegisterField", 

604 value_type: str, 

605 ) -> str: 

606 """ 

607 Get implementation for a 'write_field' procedure. 

608 """ 

609 signature = self._field_read_write_signature( 

610 is_read_not_write=False, 

611 register=register, 

612 register_array=register_array, 

613 field=field, 

614 value_type=value_type, 

615 ) 

616 register_name = self.qualified_register_name( 

617 register=register, register_array=register_array 

618 ) 

619 

620 if self.field_setter_should_read_modify_write(register=register): 

621 set_base_value = f"""\ 

622 read_{register_name}( 

623 net => net, 

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

625 value => reg_value, 

626 base_address => base_address, 

627 bus_handle => bus_handle 

628 ); 

629""" 

630 else: 

631 set_base_value = "" 

632 

633 if value_type == "integer": 

634 field_name = self.qualified_field_name( 

635 register=register, register_array=register_array, field=field 

636 ) 

637 field_width = f"{field_name}_width" 

638 

639 if isinstance(field, BitVector) and isinstance( 

640 field.numerical_interpretation, Unsigned 

641 ): 

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

643 elif isinstance(field, BitVector) and isinstance( 

644 field.numerical_interpretation, Signed 

645 ): 

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

647 else: 

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

649 else: 

650 field_conversion = "value" 

651 

652 return f"""\ 

653{signature} is 

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

655 begin 

656{set_base_value}\ 

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

658 

659 write_{register_name}( 

660 net => net, 

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

662 value => reg_value, 

663 base_address => base_address, 

664 bus_handle => bus_handle 

665 ); 

666 end procedure; 

667"""