Coverage for hdl_registers/generator/python/accessor.py: 97%

260 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# Third party libraries 

15from black import Mode, format_str 

16 

17# First party libraries 

18from hdl_registers.field.bit import Bit 

19from hdl_registers.field.bit_vector import BitVector 

20from hdl_registers.field.enumeration import Enumeration 

21from hdl_registers.field.integer import Integer 

22from hdl_registers.field.numerical_interpretation import ( 

23 Fixed, 

24 Signed, 

25 SignedFixedPoint, 

26 Unsigned, 

27 UnsignedFixedPoint, 

28) 

29from hdl_registers.generator.register_code_generator import RegisterCodeGenerator 

30from hdl_registers.register import Register 

31 

32if TYPE_CHECKING: 

33 # First party libraries 

34 from hdl_registers.field.register_field import RegisterField 

35 from hdl_registers.register_array import RegisterArray 

36 

37 

38class PythonAccessorGenerator(RegisterCodeGenerator): 

39 """ 

40 Generate a Python class to read/write/print register and field values on a target device. 

41 Field and register values are represented using their native Python types, for easy handling. 

42 Meaning, no manual type casting back and forth for the user. 

43 

44 See the :ref:`generator_python` article for usage details. 

45 See also :class:`.PythonRegisterAccessorInterface`. 

46 

47 Needs to have the result from the :class:`.PythonPickleGenerator` generator in the 

48 same output folder. 

49 """ 

50 

51 __version__ = "0.1.0" 

52 

53 SHORT_DESCRIPTION = "Python accessor" 

54 

55 COMMENT_START = "#" 

56 

57 @property 

58 def output_file(self) -> Path: 

59 """ 

60 Result will be placed in this file. 

61 """ 

62 return self.output_folder / f"{self.name}_accessor.py" 

63 

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

65 """ 

66 Get Python code for accessing register and field values. 

67 """ 

68 class_name = f"{self.to_pascal_case(self.name)}Accessor" 

69 

70 if self.register_list.register_objects: 

71 last_index = self.register_list.register_objects[-1].index 

72 else: 

73 last_index = -1 

74 

75 register_access_methods = self._get_register_access_methods() 

76 register_value_types = self._get_register_value_types() 

77 

78 result = f'''\ 

79# Standard libraries 

80import pickle 

81from dataclasses import dataclass 

82from enum import Enum 

83from pathlib import Path 

84from typing import TYPE_CHECKING 

85from termcolor import colored 

86 

87# Third party libraries 

88from hdl_registers.field.numerical_interpretation import to_unsigned_binary 

89from tsfpga.math_utils import to_binary_nibble_string, to_hex_byte_string 

90 

91if TYPE_CHECKING: 

92 # Third party libraries 

93 from hdl_registers.generator.python.register_accessor_interface import ( 

94 PythonRegisterAccessorInterface, 

95 ) 

96 from hdl_registers.register_list import RegisterList 

97 

98 

99THIS_DIR = Path(__file__).parent 

100 

101 

102class {class_name}: 

103 """ 

104 Class to read/write registers and fields of the ``{self.name}`` register list. 

105 """ 

106 

107 def __init__(self, register_accessor: "PythonRegisterAccessorInterface"): 

108 """ 

109 Arguments: 

110 register_accessor: Object that implements register access methods in your system. 

111 Must have the methods ``read_register`` and ``write_register`` 

112 with the same interface and behavior as the interface class 

113 :class:`.PythonRegisterAccessorInterface`. 

114 It is highly recommended that your accessor class inherits from that class. 

115 """ 

116 self._register_accessor = register_accessor 

117 

118 pickle_path = THIS_DIR / "{self.name}.pickle" 

119 if not pickle_path.exists(): 

120 raise FileNotFoundError( 

121 f"Could not find the pickle file { pickle_path} , " 

122 "make sure this artifact is generated." 

123 ) 

124 

125 with pickle_path.open("rb") as file_handle: 

126 self._register_list: "RegisterList" = pickle.load(file_handle) 

127 

128 def _read_register(self, register_index: int) -> int: 

129 """ 

130 Read a register value via the register accessor provided by the user. 

131 We perform sanity checks in both directions here, since this is the glue logic between 

132 two interfaces. 

133 """ 

134 if not 0 <= register_index <= {last_index}: 

135 raise ValueError( 

136 f"Register index { register_index} out of range. This is likely an internal error." 

137 ) 

138 

139 register_value = self._register_accessor.read_register( 

140 register_list_name="{self.register_list.name}", register_address=4 * register_index 

141 ) 

142 if not 0 <= register_value < 2 ** 32: 

143 raise ValueError( 

144 f'Register read value "{ register_value} " from accessor is out of range.' 

145 ) 

146 

147 return register_value 

148 

149 def _write_register(self, register_index: int, register_value: int) -> None: 

150 """ 

151 Write a register value via the register accessor provided by the user. 

152 We perform sanity checks in both directions here, since this is the glue logic between 

153 two interfaces. 

154 """ 

155 if not 0 <= register_index <= {last_index}: 

156 raise ValueError( 

157 f"Register index { register_index} out of range. This is likely an internal error." 

158 ) 

159 

160 if not 0 <= register_value < 2 ** 32: 

161 raise ValueError( 

162 f'Register write value "{ register_value} " is out of range.' 

163 ) 

164 

165 self._register_accessor.write_register( 

166 register_list_name="{self.register_list.name}", 

167 register_address=4 * register_index, 

168 register_value=register_value 

169 ) 

170{register_access_methods}\ 

171 

172 

173def get_accessor(register_accessor: "PythonRegisterAccessorInterface") -> {class_name}: 

174 """ 

175 Factory function to create an accessor object. 

176 """ 

177 return {class_name}(register_accessor=register_accessor) 

178 

179 

180def _format_unsigned_number(value: int, width: int, include_decimal: bool=True) -> str: 

181 """ 

182 Format a string representation of the provided ``value``. 

183 Suitable for printing field and register values. 

184 """ 

185 result = "" 

186 if include_decimal: 

187 result += f"unsigned decimal { value} , " 

188 

189 hex_string = to_hex_byte_string(value=value, result_width_bits=width) 

190 result += f"hexadecimal { hex_string} , " 

191 

192 binary_string = to_binary_nibble_string(value=value, result_width_bits=width) 

193 result += f"binary { binary_string} " 

194 

195 return result 

196 

197{register_value_types} 

198''' 

199 return self._format_with_black(result) 

200 

201 def _get_register_value_type_name( 

202 self, register: "Register", register_array: Optional["RegisterArray"] 

203 ) -> str: 

204 """ 

205 The name of the python type that represents the native Python value of a register. 

206 Either a built-in type (int) or a dataclass that we define in another place in the file. 

207 """ 

208 if not register.fields: 

209 return "int" 

210 

211 register_name = self.qualified_register_name( 

212 register=register, register_array=register_array 

213 ) 

214 return f"{self.to_pascal_case(register_name)}Value" 

215 

216 def _get_register_value_types(self) -> str: 

217 """ 

218 Get all the classes needed to represent register values. 

219 One dataclass for each register that contains fields. 

220 """ 

221 result = "" 

222 

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

224 if not register.fields: 

225 continue 

226 

227 result += self._get_register_value_type( 

228 register=register, register_array=register_array 

229 ) 

230 

231 return result 

232 

233 def _get_register_value_type( 

234 self, register: "Register", register_array: Optional["RegisterArray"] 

235 ) -> str: 

236 """ 

237 Get class for register value for one specific register. 

238 Assumes that the register has fields. 

239 """ 

240 class_name = self._get_register_value_type_name( 

241 register=register, register_array=register_array 

242 ) 

243 register_description = self.register_description( 

244 register=register, register_array=register_array 

245 ) 

246 result = f''' 

247@dataclass 

248class {class_name}: 

249 """ 

250 Represents the field values of the {register_description}. 

251 Uses native Python types for easy handling. 

252 """ 

253''' 

254 for field in register.fields: 

255 if isinstance(field, Enumeration): 

256 elements = [ 

257 f'{element.name.upper()} = "{element.name}"' for element in field.elements 

258 ] 

259 separator = "\n " 

260 result += f''' 

261 class {self._get_field_python_type_name(field=field)}(Enum): 

262 """ 

263 Enumeration elements for the ``{field.name}`` field. 

264 """ 

265 {separator.join(elements)} 

266 

267''' 

268 for field in register.fields: 

269 field_comment = self._get_field_type_and_range_comment(field=field) 

270 field_type = self._get_field_python_type_name(field=field) 

271 result += f"""\ 

272 # {field_comment} 

273 {field.name}: {field_type} 

274""" 

275 result += ''' 

276 def to_string(self, indentation: str="", no_color: bool = False) -> str: 

277 """ 

278 Get a string of the field values, suitable for printing. 

279 

280 Arguments: 

281 indentation: Optionally indent each field value line. 

282 no_color: Disable color output. 

283 Can also be achieved by setting the environment variable ``NO_COLOR`` to any value. 

284 """ 

285 values = [] 

286 

287''' 

288 for field in register.fields: 

289 result += self._get_field_type_to_string_value(field=field) 

290 result += f"""\ 

291 field_name = colored("{field.name}", color="light_cyan", no_color=no_color) 

292 values.append(f"{ indentation} { field_name} : { value} ") 

293 

294""" 

295 

296 result += ' return "\\n".join(values)' 

297 

298 return result 

299 

300 def _get_field_python_type_name( 

301 self, field: "RegisterField", global_type_name: Optional[str] = None 

302 ) -> str: 

303 """ 

304 Get the Python type name for the native type that represents a field value. 

305 Is either a built-in type (e.g. int) or a type that we define in another place in the file. 

306 

307 Arguments: 

308 global_type_name: When the field type is a custom one, the type class is declared within 

309 the dataclass of the register. 

310 When the field type is referenced within the register class, its local name can be 

311 used, but when we want to reference it outside of the register class, we need to 

312 know the name of the register value class also. 

313 """ 

314 if isinstance(field, BitVector): 

315 if isinstance(field.numerical_interpretation, Fixed): 

316 return "float" 

317 

318 return "int" 

319 

320 if isinstance(field, Bit): 

321 return "int" 

322 

323 if isinstance(field, Enumeration): 

324 local_name = self.to_pascal_case(field.name) 

325 

326 if global_type_name is None: 

327 return local_name 

328 

329 return f"{global_type_name}.{local_name}" 

330 

331 if isinstance(field, Integer): 

332 return "int" 

333 

334 raise ValueError(f"Unknown field {field}") 

335 

336 @staticmethod 

337 def _get_field_type_and_range_comment(field: "RegisterField") -> str: 

338 """ 

339 A string the describes the field. 

340 Suitable for descriptions in accessor methods. 

341 """ 

342 if isinstance(field, BitVector): 

343 if field.numerical_interpretation.is_signed: 

344 sign_comment = "Signed" 

345 else: 

346 sign_comment = "Unsigned" 

347 

348 range_comment = ( 

349 f"Range: {field.numerical_interpretation.min_value} - " 

350 f"{field.numerical_interpretation.max_value}." 

351 ) 

352 

353 if isinstance(field.numerical_interpretation, (Signed, Unsigned)): 

354 return f"{sign_comment} bit vector. Width: {field.width}. {range_comment}" 

355 

356 if isinstance(field.numerical_interpretation, (SignedFixedPoint, UnsignedFixedPoint)): 

357 integer_width = ( 

358 f"Integer width: {field.numerical_interpretation.integer_bit_width}." 

359 ) 

360 fractional_width = ( 

361 f"Fractional width: {field.numerical_interpretation.fraction_bit_width}." 

362 ) 

363 width_comment = f"{integer_width} {fractional_width} " 

364 return f"{sign_comment} fixed-point bit vector. {width_comment}{range_comment}" 

365 

366 raise ValueError(f"Unknown field type {field}") 

367 

368 if isinstance(field, Bit): 

369 return "Single bit. Range: 0-1." 

370 

371 if isinstance(field, Enumeration): 

372 element_names = ", ".join([element.name.upper() for element in field.elements]) 

373 return f"Enumeration. Possible values: {element_names}." 

374 

375 if isinstance(field, Integer): 

376 if field.is_signed: 

377 sign_comment = "Signed" 

378 else: 

379 sign_comment = "Unsigned" 

380 return f"{sign_comment} integer. Range: {field.min_value} - {field.max_value}." 

381 

382 raise ValueError(f"Unknown field {field}") 

383 

384 def _get_field_type_to_string_value(self, field: "RegisterField") -> str: 

385 """ 

386 Get Python code that casts a field value to a string suitable for printing. 

387 """ 

388 field_value = f"self.{field.name}" 

389 

390 if isinstance(field, Enumeration): 

391 field_type = self._get_field_python_type_name(field=field) 

392 return f""" 

393 enum_index = list(self.{field_type}).index({field_value}) 

394 value = f"{ {field_value}.name} ({ enum_index} )" 

395""" 

396 if isinstance(field, Integer): 

397 unsigned = ( 

398 f"to_unsigned_binary(num_bits={field.width}, value={field_value}, is_signed=True)" 

399 if field.is_signed 

400 else field_value 

401 ) 

402 

403 return f""" 

404 unsigned_value = {unsigned} 

405 value_comment = _format_unsigned_number( 

406 value=unsigned_value, width={field.width}, include_decimal={field.is_signed} 

407 ) 

408 value = f"{ {field_value}} ({ value_comment} )" 

409""" 

410 if isinstance(field, Bit): 

411 return f" value = str(self.{field.name})\n" 

412 

413 if isinstance(field, BitVector): 

414 if isinstance(field.numerical_interpretation, Unsigned): 

415 return f""" 

416 value_comment = _format_unsigned_number( 

417 value={field_value}, width={field.width}, include_decimal=False 

418 ) 

419 value = f"{ {field_value}} ({ value_comment} )" 

420""" 

421 if isinstance(field.numerical_interpretation, Signed): 

422 return f""" 

423 unsigned_value = to_unsigned_binary( 

424 num_bits={field.width}, value={field_value}, is_signed=True 

425 ) 

426 value_comment = _format_unsigned_number( 

427 value=unsigned_value, width={field.width} 

428 ) 

429 value = f"{ {field_value}} ({ value_comment} )" 

430""" 

431 

432 if isinstance(field.numerical_interpretation, (UnsignedFixedPoint, SignedFixedPoint)): 

433 return f""" 

434 unsigned_value = to_unsigned_binary( 

435 num_bits={field.width}, 

436 value={field_value}, 

437 num_integer_bits={field.numerical_interpretation.integer_bit_width}, 

438 num_fractional_bits={field.numerical_interpretation.fraction_bit_width}, 

439 is_signed={field.numerical_interpretation.is_signed} 

440 ) 

441 value_comment = _format_unsigned_number( 

442 value=unsigned_value, width={field.width} 

443 ) 

444 value = f"{ {field_value}} ({ value_comment} )" 

445""" 

446 

447 raise ValueError(f"Unknown field type {field}") 

448 

449 raise ValueError(f"Unknown field {field}") 

450 

451 def _get_register_access_methods(self) -> str: 

452 """ 

453 Get all the methods to read and write registers and fields. 

454 Will only include methods for registers that are accessible in either direction. 

455 """ 

456 result = "" 

457 

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

459 if register.mode.software_can_read: 

460 if register.fields: 

461 result += self._get_register_read_as_class( 

462 register=register, register_array=register_array 

463 ) 

464 else: 

465 result += self._get_register_read_as_integer( 

466 register=register, register_array=register_array 

467 ) 

468 

469 if register.mode.software_can_write: 

470 if register.fields: 

471 result += self._get_register_write_as_class( 

472 register=register, register_array=register_array 

473 ) 

474 result += self._get_fields_write( 

475 register=register, register_array=register_array 

476 ) 

477 else: 

478 result += self._get_register_write_as_integer( 

479 register=register, register_array=register_array 

480 ) 

481 

482 result += self._get_print_registers() 

483 

484 return result 

485 

486 def _get_register_read_as_class( 

487 self, register: "Register", register_array: Optional["RegisterArray"] 

488 ) -> str: 

489 """ 

490 For a register that has fields, the unsigned value read from the bus is converted to a 

491 custom class that represents the field values. 

492 """ 

493 register_value_type_name = self._get_register_value_type_name( 

494 register=register, register_array=register_array 

495 ) 

496 register_array_name = f'"{register_array.name}"' if register_array else "None" 

497 common = self._get_register_read_common( 

498 register=register, 

499 register_array=register_array, 

500 register_value_type_name=register_value_type_name, 

501 ) 

502 result = f"""{common}\ 

503 register = self._register_list.get_register( 

504 register_name="{register.name}", register_array_name={register_array_name} 

505 ) 

506 return {register_value_type_name}( 

507""" 

508 for field_idx, field in enumerate(register.fields): 

509 get_value = f"register.fields[{field_idx}].get_value(register_value=value)" 

510 

511 if isinstance(field, Enumeration): 

512 type_name = self._get_field_python_type_name( 

513 field=field, global_type_name=register_value_type_name 

514 ) 

515 get_value = f"{type_name}({get_value}.name)" 

516 

517 result += f"{field.name}={get_value}," 

518 

519 result += ")" 

520 return result 

521 

522 def _get_register_read_as_integer( 

523 self, register: "Register", register_array: Optional["RegisterArray"] 

524 ) -> str: 

525 """ 

526 For registers without fields, the unsigned value read from the bus is simply passed on. 

527 """ 

528 common = self._get_register_read_common( 

529 register=register, 

530 register_array=register_array, 

531 register_value_type_name="int", 

532 extra_docstring=( 

533 " Return type is a plain unsigned integer " 

534 "since the register has no fields.\n" 

535 ), 

536 ) 

537 return f"""{common}\ 

538 return value 

539""" 

540 

541 def _get_register_read_common( 

542 self, 

543 register: "Register", 

544 register_array: Optional["RegisterArray"], 

545 register_value_type_name: str, 

546 extra_docstring: str = "", 

547 ) -> str: 

548 """ 

549 Common register read portions for both 'integer' and 'class'. 

550 """ 

551 register_name = self._get_semi_qualified_register_name( 

552 register=register, register_array=register_array 

553 ) 

554 register_description = self.register_description( 

555 register=register, register_array=register_array 

556 ) 

557 

558 array_index_argument = ", array_index: int" if register_array else "" 

559 index_variable = self._get_index_variable(register=register, register_array=register_array) 

560 array_index_documentation = ( 

561 f""" 

562 Arguments: 

563 array_index: Register array iteration index (range 0-{register_array.length - 1}). 

564""" 

565 if register_array 

566 else "" 

567 ) 

568 return f''' 

569 def read_{register_name}(self{array_index_argument}) -> "{register_value_type_name}": 

570 """ 

571 Read the {register_description}. 

572{extra_docstring}\ 

573{array_index_documentation}\ 

574 """ 

575{index_variable}\ 

576 value = self._read_register(register_index=index) 

577''' 

578 

579 def _get_register_write_as_class( 

580 self, register: "Register", register_array: Optional["RegisterArray"] 

581 ) -> str: 

582 """ 

583 For a register that has fields, the value to be written is provided as a custom class. 

584 This must be converted to an unsigned integer before writing to the bus. 

585 """ 

586 register_value_type_name = self._get_register_value_type_name( 

587 register=register, register_array=register_array 

588 ) 

589 register_array_name = f'"{register_array.name}"' if register_array else "None" 

590 common = self._get_register_write_common( 

591 register=register, 

592 register_array=register_array, 

593 register_value_type_name=register_value_type_name, 

594 register_value_docstring="Object with the field values that shall be written", 

595 ) 

596 result = f"""{common}\ 

597 register = self._register_list.get_register( 

598 register_name="{register.name}", register_array_name={register_array_name} 

599 ) 

600 integer_value = 0 

601""" 

602 for field_idx, field in enumerate(register.fields): 

603 convert = f"integer_value += register.fields[{field_idx}].set_value(field_value=" 

604 

605 if isinstance(field, Enumeration): 

606 convert += ( 

607 f"register.fields[{field_idx}]." 

608 f"get_element_by_name(register_value.{field.name}.value)" 

609 ) 

610 else: 

611 convert += f"register_value.{field.name}" 

612 

613 result += f" {convert})\n" 

614 

615 result += """ self._write_register( 

616 register_index=index, register_value=integer_value 

617) 

618""" 

619 return result 

620 

621 def _get_register_write_as_integer( 

622 self, register: "Register", register_array: Optional["RegisterArray"] 

623 ) -> str: 

624 """ 

625 For a register without fields, the value to be written is provided as an unsigned 

626 integer that can be written straight away. 

627 """ 

628 result = self._get_register_write_common( 

629 register=register, 

630 register_array=register_array, 

631 register_value_type_name="int", 

632 register_value_docstring=( 

633 "Value to be written.\n" 

634 " Is a plain unsigned integer since the register has no fields" 

635 ), 

636 ) 

637 

638 result += """ self._write_register( 

639 register_index=index, register_value=register_value 

640) 

641""" 

642 return result 

643 

644 def _get_register_write_common( 

645 self, 

646 register: "Register", 

647 register_array: Optional["RegisterArray"], 

648 register_value_type_name: str, 

649 register_value_docstring: str, 

650 ) -> str: 

651 """ 

652 Common register write portions for both 'integer' and 'class'. 

653 """ 

654 register_name = self._get_semi_qualified_register_name( 

655 register=register, register_array=register_array 

656 ) 

657 register_description = self.register_description( 

658 register=register, register_array=register_array 

659 ) 

660 

661 array_index_argument = ", array_index: int" if register_array else "" 

662 index_variable = self._get_index_variable(register=register, register_array=register_array) 

663 array_index_documentation = ( 

664 ( 

665 " array_index: Register array iteration " 

666 f"index (range 0-{register_array.length - 1}).\n" 

667 ) 

668 if register_array 

669 else "" 

670 ) 

671 return f''' 

672 def write_{register_name}(self, \ 

673register_value: "{register_value_type_name}"{array_index_argument}) -> None: 

674 """ 

675 Write the whole {register_description}. 

676 

677 Arguments: 

678 register_value: {register_value_docstring}. 

679{array_index_documentation}\ 

680 """ 

681{index_variable}\ 

682''' 

683 

684 @staticmethod 

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

686 """ 

687 Get Python code that extracts the register index from the register list. 

688 """ 

689 register_array_name = f'"{register_array.name}"' if register_array else "None" 

690 array_index_value = "array_index" if register_array else "None" 

691 return f"""\ 

692 index = self._register_list.get_register_index( 

693 register_name="{register.name}", 

694 register_array_name={register_array_name}, 

695 register_array_index={array_index_value}, 

696 ) 

697""" 

698 

699 def _get_fields_write( 

700 self, register: "Register", register_array: Optional["RegisterArray"] 

701 ) -> str: 

702 """ 

703 Get Python class methods for writing the values of each field in the register. 

704 Will either write the whole register or read-modify-write the register. 

705 """ 

706 result = "" 

707 

708 for field_idx, field in enumerate(register.fields): 

709 if self.field_setter_should_read_modify_write(register=register): 

710 result += self._get_field_read_modify_write( 

711 field=field, register=register, register_array=register_array 

712 ) 

713 else: 

714 result += self._get_field_write( 

715 field_idx=field_idx, 

716 field=field, 

717 register=register, 

718 register_array=register_array, 

719 ) 

720 

721 return result 

722 

723 def _get_field_read_modify_write( 

724 self, 

725 field: "RegisterField", 

726 register: "Register", 

727 register_array: Optional["RegisterArray"], 

728 ) -> str: 

729 """ 

730 Python class method to read-modify-write a register, updating the value of one field. 

731 """ 

732 result = self._get_field_write_common( 

733 field=field, 

734 register=register, 

735 register_array=register_array, 

736 docstring=f"""\ 

737Will read-modify-write the ``{register.name}`` register, setting the ``{field.name}`` field 

738 to the provided value, while leaving the other fields at their previous values. 

739""", 

740 ) 

741 

742 register_name = self._get_semi_qualified_register_name( 

743 register=register, register_array=register_array 

744 ) 

745 array_index_association_plain = "array_index=array_index" if register_array else "" 

746 array_index_association_comma = ", array_index=array_index" if register_array else "" 

747 result += f"""\ 

748 register_value = self.read_{register_name}({array_index_association_plain}) 

749 register_value.{field.name} = field_value 

750 self.write_{register_name}(register_value=register_value{array_index_association_comma}) 

751""" 

752 

753 return result 

754 

755 def _get_field_write( 

756 self, 

757 field_idx: int, 

758 field: "RegisterField", 

759 register: "Register", 

760 register_array: Optional["RegisterArray"], 

761 ) -> str: 

762 """ 

763 Python class method to write a register, setting the value of one field only. 

764 """ 

765 result = self._get_field_write_common( 

766 field=field, 

767 register=register, 

768 register_array=register_array, 

769 docstring=f"""\ 

770Will write the ``{register.name}`` register, setting the ``{field.name}`` field to the 

771 provided value, and all other fields to their default values. 

772""", 

773 ) 

774 

775 index_variable = self._get_index_variable(register=register, register_array=register_array) 

776 register_array_name = f'"{register_array.name}"' if register_array else "None" 

777 result += f"""\ 

778{index_variable} 

779 register = self._register_list.get_register( 

780 register_name="{register.name}", register_array_name={register_array_name} 

781 ) 

782 register_value = 0 

783""" 

784 

785 for iterate_field_idx in range(len(register.fields)): 

786 iterate_field = f"register.fields[{iterate_field_idx}]" 

787 result += " register_value += " 

788 if iterate_field_idx == field_idx: 

789 if isinstance(field, Enumeration): 

790 result += ( 

791 f"{iterate_field}.set_value({iterate_field}." 

792 "get_element_by_name(field_value.value))" 

793 ) 

794 else: 

795 result += f"{iterate_field}.set_value(field_value)" 

796 else: 

797 result += f"{iterate_field}.default_value_uint << {iterate_field}.base_index" 

798 

799 result += "\n" 

800 

801 result += """ 

802 self._write_register(register_index=index, register_value=register_value) 

803 """ 

804 

805 return result 

806 

807 def _get_field_write_common( 

808 self, 

809 field: "RegisterField", 

810 register: "Register", 

811 register_array: Optional["RegisterArray"], 

812 docstring: str, 

813 ) -> str: 

814 """ 

815 Common portions for field write methods. 

816 """ 

817 field_name = self._get_semi_qualified_field_name( 

818 field=field, register=register, register_array=register_array 

819 ) 

820 field_value_type_name = self._get_field_python_type_name( 

821 field=field, 

822 global_type_name=self._get_register_value_type_name( 

823 register=register, register_array=register_array 

824 ), 

825 ) 

826 field_description = self.field_description( 

827 register=register, field=field, register_array=register_array 

828 ) 

829 field_comment = self._get_field_type_and_range_comment(field=field) 

830 

831 array_index_argument = ", array_index: int" if register_array else "" 

832 array_index_documentation = ( 

833 ( 

834 " array_index: Register array iteration " 

835 f"index (range 0-{register_array.length - 1}).\n" 

836 ) 

837 if register_array 

838 else "" 

839 ) 

840 return f''' 

841 def write_{field_name}(self, \ 

842field_value: "{field_value_type_name}"{array_index_argument}) -> None: 

843 """ 

844 Write the {field_description}. 

845 {docstring}\ 

846 

847 Arguments: 

848 field_value: The field value to be written. 

849 {field_comment} 

850{array_index_documentation}\ 

851 """ 

852''' 

853 

854 @staticmethod 

855 def _get_semi_qualified_register_name( 

856 register: "Register", register_array: Optional["RegisterArray"] 

857 ) -> str: 

858 """ 

859 Base name of register access methods. 

860 Name is a contrast to 'self.qualified_register_name'. 

861 """ 

862 if register_array is None: 

863 return register.name 

864 

865 return f"{register_array.name}_{register.name}" 

866 

867 def _get_semi_qualified_field_name( 

868 self, 

869 field: "RegisterField", 

870 register: "Register", 

871 register_array: Optional["RegisterArray"], 

872 ) -> str: 

873 """ 

874 Base name of field access methods. 

875 Name is a contrast to 'self.qualified_field_name'. 

876 """ 

877 register_name = self._get_semi_qualified_register_name( 

878 register=register, register_array=register_array 

879 ) 

880 

881 return f"{register_name}_{field.name}" 

882 

883 def _get_print_registers(self) -> str: 

884 """ 

885 Python method to print all registers and their values. 

886 """ 

887 result = ''' 

888 def print_registers(self, no_color: bool=False) -> None: 

889 """ 

890 Print all registers and their values to STDOUT. 

891 

892 Arguments: 

893 no_color: Disable color output. 

894 Can also be achieved by setting the environment variable ``NO_COLOR`` to any value. 

895 """ 

896''' 

897 

898 for register_object in self.register_list.register_objects: 

899 if isinstance(register_object, Register): 

900 result += self._get_print_register( 

901 is_readable=register_object.mode.software_can_read, register=register_object 

902 ) 

903 else: 

904 for array_index in range(register_object.length): 

905 for register in register_object.registers: 

906 result += self._get_print_register( 

907 is_readable=register.mode.software_can_read, 

908 register=register, 

909 register_array=register_object, 

910 register_array_index=array_index, 

911 ) 

912 

913 return result 

914 

915 @staticmethod 

916 def _get_print_register( 

917 is_readable: bool, 

918 register: "Register", 

919 register_array: Optional["RegisterArray"] = None, 

920 register_array_index: Optional[int] = None, 

921 ) -> str: 

922 """ 

923 Python code to print a single register and its field values. 

924 """ 

925 if register_array is not None and register_array_index is not None: 

926 name = f"{register_array.name}[{register_array_index}].{register.name}" 

927 index = ( 

928 register_array.get_start_index(array_index=register_array_index) + register.index 

929 ) 

930 read = ( 

931 f"self.read_{register_array.name}_{register.name}" 

932 f"(array_index={register_array_index})" 

933 ) 

934 else: 

935 name = register.name 

936 index = register.index 

937 read = f"self.read_{register.name}()" 

938 

939 heading = f"""\ 

940print( 

941 colored("Register", color="light_yellow", no_color=no_color) 

942 + " '{name}' " 

943 + colored("." * {67 - len(name)}, color="dark_grey", no_color=no_color) 

944 + " (index {index}, address {4 * index}):" 

945) 

946""" 

947 

948 if not is_readable: 

949 return f"""\ 

950 {heading}\ 

951 print(" Not readable.\\n") 

952 

953""" 

954 

955 read_value = f"register_value = {read}\n" 

956 if not register.fields: 

957 return f"""\ 

958 {heading}\ 

959 {read_value}\ 

960 formatted_value = _format_unsigned_number(value=register_value, width=32) 

961 print(f" { formatted_value} \\n") 

962 

963""" 

964 

965 return f"""\ 

966 {heading}\ 

967 {read_value}\ 

968 print(register_value.to_string(indentation=" ", no_color=no_color)) 

969 print() 

970 

971""" 

972 

973 def _format_with_black(self, code: str) -> str: 

974 """ 

975 Format the code with the Black code formatter, so the result always looks perfect. 

976 Means that we can be a little more sloppy when we construct the generated code. 

977 But even then, it would be a huge pain to determine when to break and not break lines. 

978 So it's easier to just apply a stable and fast formatter. 

979 """ 

980 # Not that this is NOT a public API: 

981 # * https://stackoverflow.com/questions/57652922 

982 # * https://github.com/psf/black/issues/779 

983 # The simple mode construction below will use the default settings for black 

984 # (magic trailing comma, etc) apart from the line length. 

985 # This is identical to how Python files in this project are formatted. 

986 # Should give a consistent look and feel. 

987 mode = Mode(line_length=100) 

988 return format_str(src_contents=code, mode=mode)