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

260 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-17 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{self.header} 

80# Standard libraries 

81import pickle 

82from dataclasses import dataclass 

83from enum import Enum 

84from pathlib import Path 

85from typing import TYPE_CHECKING 

86from termcolor import colored 

87 

88# Third party libraries 

89from hdl_registers.field.numerical_interpretation import to_unsigned_binary 

90from tsfpga.math_utils import to_binary_nibble_string, to_hex_byte_string 

91 

92if TYPE_CHECKING: 

93 # Third party libraries 

94 from hdl_registers.generator.python.register_accessor_interface import ( 

95 PythonRegisterAccessorInterface, 

96 ) 

97 from hdl_registers.register_list import RegisterList 

98 

99 

100THIS_DIR = Path(__file__).parent 

101 

102 

103class {class_name}: 

104 """ 

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

106 """ 

107 

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

109 """ 

110 Arguments: 

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

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

113 with the same interface and behavior as the interface class 

114 :class:`.PythonRegisterAccessorInterface`. 

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

116 """ 

117 self._register_accessor = register_accessor 

118 

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

120 if not pickle_path.exists(): 

121 raise FileNotFoundError( 

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

123 "make sure this artifact is generated." 

124 ) 

125 

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

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

128 

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

130 """ 

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

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

133 two interfaces. 

134 """ 

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

136 raise ValueError( 

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

138 ) 

139 

140 register_value = self._register_accessor.read_register( 

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

142 ) 

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

144 raise ValueError( 

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

146 ) 

147 

148 return register_value 

149 

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

151 """ 

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

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

154 two interfaces. 

155 """ 

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

157 raise ValueError( 

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

159 ) 

160 

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

162 raise ValueError( 

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

164 ) 

165 

166 self._register_accessor.write_register( 

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

168 register_address=4 * register_index, 

169 register_value=register_value 

170 ) 

171{register_access_methods}\ 

172 

173 

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

175 """ 

176 Factory function to create an accessor object. 

177 """ 

178 return {class_name}(register_accessor=register_accessor) 

179 

180 

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

182 """ 

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

184 Suitable for printing field and register values. 

185 """ 

186 result = "" 

187 if include_decimal: 

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

189 

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

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

192 

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

194 result += f"binary {{binary_string}}" 

195 

196 return result 

197 

198{register_value_types} 

199''' 

200 return self._format_with_black(result) 

201 

202 def _get_register_value_type_name( 

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

204 ) -> str: 

205 """ 

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

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

208 """ 

209 if not register.fields: 

210 return "int" 

211 

212 register_name = self.qualified_register_name( 

213 register=register, register_array=register_array 

214 ) 

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

216 

217 def _get_register_value_types(self) -> str: 

218 """ 

219 Get all the classes needed to represent register values. 

220 One dataclass for each register that contains fields. 

221 """ 

222 result = "" 

223 

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

225 if not register.fields: 

226 continue 

227 

228 result += self._get_register_value_type( 

229 register=register, register_array=register_array 

230 ) 

231 

232 return result 

233 

234 def _get_register_value_type( 

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

236 ) -> str: 

237 """ 

238 Get class for register value for one specific register. 

239 Assumes that the register has fields. 

240 """ 

241 class_name = self._get_register_value_type_name( 

242 register=register, register_array=register_array 

243 ) 

244 register_description = self.register_description( 

245 register=register, register_array=register_array 

246 ) 

247 result = f''' 

248@dataclass 

249class {class_name}: 

250 """ 

251 Represents the field values of the {register_description}. 

252 Uses native Python types for easy handling. 

253 """ 

254''' 

255 for field in register.fields: 

256 if isinstance(field, Enumeration): 

257 elements = [ 

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

259 ] 

260 separator = "\n " 

261 result += f''' 

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

263 """ 

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

265 """ 

266 {separator.join(elements)} 

267 

268''' 

269 for field in register.fields: 

270 field_comment = self._get_field_type_and_range_comment(field=field) 

271 field_type = self._get_field_python_type_name(field=field) 

272 result += f"""\ 

273 # {field_comment} 

274 {field.name}: {field_type} 

275""" 

276 result += ''' 

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

278 """ 

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

280 

281 Arguments: 

282 indentation: Optionally indent each field value line. 

283 no_color: Disable color output. 

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

285 """ 

286 values = [] 

287 

288''' 

289 for field in register.fields: 

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

291 result += f"""\ 

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

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

294 

295""" 

296 

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

298 

299 return result 

300 

301 def _get_field_python_type_name( 

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

303 ) -> str: 

304 """ 

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

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

307 

308 Arguments: 

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

310 the dataclass of the register. 

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

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

313 know the name of the register value class also. 

314 """ 

315 if isinstance(field, BitVector): 

316 if isinstance(field.numerical_interpretation, Fixed): 

317 return "float" 

318 

319 return "int" 

320 

321 if isinstance(field, Bit): 

322 return "int" 

323 

324 if isinstance(field, Enumeration): 

325 local_name = self.to_pascal_case(field.name) 

326 

327 if global_type_name is None: 

328 return local_name 

329 

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

331 

332 if isinstance(field, Integer): 

333 return "int" 

334 

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

336 

337 @staticmethod 

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

339 """ 

340 A string the describes the field. 

341 Suitable for descriptions in accessor methods. 

342 """ 

343 if isinstance(field, BitVector): 

344 if field.numerical_interpretation.is_signed: 

345 sign_comment = "Signed" 

346 else: 

347 sign_comment = "Unsigned" 

348 

349 range_comment = ( 

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

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

352 ) 

353 

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

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

356 

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

358 integer_width = ( 

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

360 ) 

361 fractional_width = ( 

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

363 ) 

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

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

366 

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

368 

369 if isinstance(field, Bit): 

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

371 

372 if isinstance(field, Enumeration): 

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

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

375 

376 if isinstance(field, Integer): 

377 if field.is_signed: 

378 sign_comment = "Signed" 

379 else: 

380 sign_comment = "Unsigned" 

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

382 

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

384 

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

386 """ 

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

388 """ 

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

390 

391 if isinstance(field, Enumeration): 

392 field_type = self._get_field_python_type_name(field=field) 

393 return f""" 

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

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

396""" 

397 if isinstance(field, Integer): 

398 unsigned = ( 

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

400 if field.is_signed 

401 else field_value 

402 ) 

403 

404 return f""" 

405 unsigned_value = {unsigned} 

406 value_comment = _format_unsigned_number( 

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

408 ) 

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

410""" 

411 if isinstance(field, Bit): 

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

413 

414 if isinstance(field, BitVector): 

415 if isinstance(field.numerical_interpretation, Unsigned): 

416 return f""" 

417 value_comment = _format_unsigned_number( 

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

419 ) 

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

421""" 

422 if isinstance(field.numerical_interpretation, Signed): 

423 return f""" 

424 unsigned_value = to_unsigned_binary( 

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

426 ) 

427 value_comment = _format_unsigned_number( 

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

429 ) 

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

431""" 

432 

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

434 return f""" 

435 unsigned_value = to_unsigned_binary( 

436 num_bits={field.width}, 

437 value={field_value}, 

438 num_integer_bits={field.numerical_interpretation.integer_bit_width}, 

439 num_fractional_bits={field.numerical_interpretation.fraction_bit_width}, 

440 is_signed={field.numerical_interpretation.is_signed} 

441 ) 

442 value_comment = _format_unsigned_number( 

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

444 ) 

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

446""" 

447 

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

449 

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

451 

452 def _get_register_access_methods(self) -> str: 

453 """ 

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

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

456 """ 

457 result = "" 

458 

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

460 if register.mode.software_can_read: 

461 if register.fields: 

462 result += self._get_register_read_as_class( 

463 register=register, register_array=register_array 

464 ) 

465 else: 

466 result += self._get_register_read_as_integer( 

467 register=register, register_array=register_array 

468 ) 

469 

470 if register.mode.software_can_write: 

471 if register.fields: 

472 result += self._get_register_write_as_class( 

473 register=register, register_array=register_array 

474 ) 

475 result += self._get_fields_write( 

476 register=register, register_array=register_array 

477 ) 

478 else: 

479 result += self._get_register_write_as_integer( 

480 register=register, register_array=register_array 

481 ) 

482 

483 result += self._get_print_registers() 

484 

485 return result 

486 

487 def _get_register_read_as_class( 

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

489 ) -> str: 

490 """ 

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

492 custom class that represents the field values. 

493 """ 

494 register_value_type_name = self._get_register_value_type_name( 

495 register=register, register_array=register_array 

496 ) 

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

498 common = self._get_register_read_common( 

499 register=register, 

500 register_array=register_array, 

501 register_value_type_name=register_value_type_name, 

502 ) 

503 result = f"""{common}\ 

504 register = self._register_list.get_register( 

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

506 ) 

507 return {register_value_type_name}( 

508""" 

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

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

511 

512 if isinstance(field, Enumeration): 

513 type_name = self._get_field_python_type_name( 

514 field=field, global_type_name=register_value_type_name 

515 ) 

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

517 

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

519 

520 result += ")" 

521 return result 

522 

523 def _get_register_read_as_integer( 

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

525 ) -> str: 

526 """ 

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

528 """ 

529 common = self._get_register_read_common( 

530 register=register, 

531 register_array=register_array, 

532 register_value_type_name="int", 

533 extra_docstring=( 

534 " Return type is a plain unsigned integer " 

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

536 ), 

537 ) 

538 return f"""{common}\ 

539 return value 

540""" 

541 

542 def _get_register_read_common( 

543 self, 

544 register: "Register", 

545 register_array: Optional["RegisterArray"], 

546 register_value_type_name: str, 

547 extra_docstring: str = "", 

548 ) -> str: 

549 """ 

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

551 """ 

552 register_name = self._get_semi_qualified_register_name( 

553 register=register, register_array=register_array 

554 ) 

555 register_description = self.register_description( 

556 register=register, register_array=register_array 

557 ) 

558 

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

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

561 array_index_documentation = ( 

562 f""" 

563 Arguments: 

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

565""" 

566 if register_array 

567 else "" 

568 ) 

569 return f''' 

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

571 """ 

572 Read the {register_description}. 

573{extra_docstring}\ 

574{array_index_documentation}\ 

575 """ 

576{index_variable}\ 

577 value = self._read_register(register_index=index) 

578''' 

579 

580 def _get_register_write_as_class( 

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

582 ) -> str: 

583 """ 

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

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

586 """ 

587 register_value_type_name = self._get_register_value_type_name( 

588 register=register, register_array=register_array 

589 ) 

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

591 common = self._get_register_write_common( 

592 register=register, 

593 register_array=register_array, 

594 register_value_type_name=register_value_type_name, 

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

596 ) 

597 result = f"""{common}\ 

598 register = self._register_list.get_register( 

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

600 ) 

601 integer_value = 0 

602""" 

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

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

605 

606 if isinstance(field, Enumeration): 

607 convert += ( 

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

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

610 ) 

611 else: 

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

613 

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

615 

616 result += """ self._write_register( 

617 register_index=index, register_value=integer_value 

618) 

619""" 

620 return result 

621 

622 def _get_register_write_as_integer( 

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

624 ) -> str: 

625 """ 

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

627 integer that can be written straight away. 

628 """ 

629 result = self._get_register_write_common( 

630 register=register, 

631 register_array=register_array, 

632 register_value_type_name="int", 

633 register_value_docstring=( 

634 "Value to be written.\n" 

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

636 ), 

637 ) 

638 

639 result += """ self._write_register( 

640 register_index=index, register_value=register_value 

641) 

642""" 

643 return result 

644 

645 def _get_register_write_common( 

646 self, 

647 register: "Register", 

648 register_array: Optional["RegisterArray"], 

649 register_value_type_name: str, 

650 register_value_docstring: str, 

651 ) -> str: 

652 """ 

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

654 """ 

655 register_name = self._get_semi_qualified_register_name( 

656 register=register, register_array=register_array 

657 ) 

658 register_description = self.register_description( 

659 register=register, register_array=register_array 

660 ) 

661 

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

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

664 array_index_documentation = ( 

665 ( 

666 " array_index: Register array iteration " 

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

668 ) 

669 if register_array 

670 else "" 

671 ) 

672 return f''' 

673 def write_{register_name}(self, \ 

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

675 """ 

676 Write the whole {register_description}. 

677 

678 Arguments: 

679 register_value: {register_value_docstring}. 

680{array_index_documentation}\ 

681 """ 

682{index_variable}\ 

683''' 

684 

685 @staticmethod 

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

687 """ 

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

689 """ 

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

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

692 return f"""\ 

693 index = self._register_list.get_register_index( 

694 register_name="{register.name}", 

695 register_array_name={register_array_name}, 

696 register_array_index={array_index_value}, 

697 ) 

698""" 

699 

700 def _get_fields_write( 

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

702 ) -> str: 

703 """ 

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

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

706 """ 

707 result = "" 

708 

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

710 if self.field_setter_should_read_modify_write(register=register): 

711 result += self._get_field_read_modify_write( 

712 field=field, register=register, register_array=register_array 

713 ) 

714 else: 

715 result += self._get_field_write( 

716 field_idx=field_idx, 

717 field=field, 

718 register=register, 

719 register_array=register_array, 

720 ) 

721 

722 return result 

723 

724 def _get_field_read_modify_write( 

725 self, 

726 field: "RegisterField", 

727 register: "Register", 

728 register_array: Optional["RegisterArray"], 

729 ) -> str: 

730 """ 

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

732 """ 

733 result = self._get_field_write_common( 

734 field=field, 

735 register=register, 

736 register_array=register_array, 

737 docstring=f"""\ 

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

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

740""", 

741 ) 

742 

743 register_name = self._get_semi_qualified_register_name( 

744 register=register, register_array=register_array 

745 ) 

746 array_index_association_plain = "array_index=array_index" if register_array else "" 

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

748 result += f"""\ 

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

750 register_value.{field.name} = field_value 

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

752""" 

753 

754 return result 

755 

756 def _get_field_write( 

757 self, 

758 field_idx: int, 

759 field: "RegisterField", 

760 register: "Register", 

761 register_array: Optional["RegisterArray"], 

762 ) -> str: 

763 """ 

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

765 """ 

766 result = self._get_field_write_common( 

767 field=field, 

768 register=register, 

769 register_array=register_array, 

770 docstring=f"""\ 

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

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

773""", 

774 ) 

775 

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

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

778 result += f"""\ 

779{index_variable} 

780 register = self._register_list.get_register( 

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

782 ) 

783 register_value = 0 

784""" 

785 

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

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

788 result += " register_value += " 

789 if iterate_field_idx == field_idx: 

790 if isinstance(field, Enumeration): 

791 result += ( 

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

793 "get_element_by_name(field_value.value))" 

794 ) 

795 else: 

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

797 else: 

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

799 

800 result += "\n" 

801 

802 result += """ 

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

804 """ 

805 

806 return result 

807 

808 def _get_field_write_common( 

809 self, 

810 field: "RegisterField", 

811 register: "Register", 

812 register_array: Optional["RegisterArray"], 

813 docstring: str, 

814 ) -> str: 

815 """ 

816 Common portions for field write methods. 

817 """ 

818 field_name = self._get_semi_qualified_field_name( 

819 field=field, register=register, register_array=register_array 

820 ) 

821 field_value_type_name = self._get_field_python_type_name( 

822 field=field, 

823 global_type_name=self._get_register_value_type_name( 

824 register=register, register_array=register_array 

825 ), 

826 ) 

827 field_description = self.field_description( 

828 register=register, field=field, register_array=register_array 

829 ) 

830 field_comment = self._get_field_type_and_range_comment(field=field) 

831 

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

833 array_index_documentation = ( 

834 ( 

835 " array_index: Register array iteration " 

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

837 ) 

838 if register_array 

839 else "" 

840 ) 

841 return f''' 

842 def write_{field_name}(self, \ 

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

844 """ 

845 Write the {field_description}. 

846 {docstring}\ 

847 

848 Arguments: 

849 field_value: The field value to be written. 

850 {field_comment} 

851{array_index_documentation}\ 

852 """ 

853''' 

854 

855 @staticmethod 

856 def _get_semi_qualified_register_name( 

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

858 ) -> str: 

859 """ 

860 Base name of register access methods. 

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

862 """ 

863 if register_array is None: 

864 return register.name 

865 

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

867 

868 def _get_semi_qualified_field_name( 

869 self, 

870 field: "RegisterField", 

871 register: "Register", 

872 register_array: Optional["RegisterArray"], 

873 ) -> str: 

874 """ 

875 Base name of field access methods. 

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

877 """ 

878 register_name = self._get_semi_qualified_register_name( 

879 register=register, register_array=register_array 

880 ) 

881 

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

883 

884 def _get_print_registers(self) -> str: 

885 """ 

886 Python method to print all registers and their values. 

887 """ 

888 result = ''' 

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

890 """ 

891 Print all registers and their values to STDOUT. 

892 

893 Arguments: 

894 no_color: Disable color output. 

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

896 """ 

897''' 

898 

899 for register_object in self.register_list.register_objects: 

900 if isinstance(register_object, Register): 

901 result += self._get_print_register( 

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

903 ) 

904 else: 

905 for array_index in range(register_object.length): 

906 for register in register_object.registers: 

907 result += self._get_print_register( 

908 is_readable=register.mode.software_can_read, 

909 register=register, 

910 register_array=register_object, 

911 register_array_index=array_index, 

912 ) 

913 

914 return result 

915 

916 @staticmethod 

917 def _get_print_register( 

918 is_readable: bool, 

919 register: "Register", 

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

921 register_array_index: Optional[int] = None, 

922 ) -> str: 

923 """ 

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

925 """ 

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

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

928 index = ( 

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

930 ) 

931 read = ( 

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

933 f"(array_index={register_array_index})" 

934 ) 

935 else: 

936 name = register.name 

937 index = register.index 

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

939 

940 heading = f"""\ 

941print( 

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

943 + " '{name}' " 

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

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

946) 

947""" 

948 

949 if not is_readable: 

950 return f"""\ 

951 {heading}\ 

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

953 

954""" 

955 

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

957 if not register.fields: 

958 return f"""\ 

959 {heading}\ 

960 {read_value}\ 

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

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

963 

964""" 

965 

966 return f"""\ 

967 {heading}\ 

968 {read_value}\ 

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

970 print() 

971 

972""" 

973 

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

975 """ 

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

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

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

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

980 """ 

981 # Not that this is NOT a public API: 

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

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

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

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

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

987 # Should give a consistent look and feel. 

988 mode = Mode(line_length=100) 

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