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

257 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-15 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 

10from __future__ import annotations 

11 

12from typing import TYPE_CHECKING, Any 

13 

14from black import Mode, format_str 

15 

16from hdl_registers.field.bit import Bit 

17from hdl_registers.field.bit_vector import BitVector 

18from hdl_registers.field.enumeration import Enumeration 

19from hdl_registers.field.integer import Integer 

20from hdl_registers.field.numerical_interpretation import ( 

21 Fixed, 

22 Signed, 

23 SignedFixedPoint, 

24 Unsigned, 

25 UnsignedFixedPoint, 

26) 

27from hdl_registers.generator.register_code_generator import RegisterCodeGenerator 

28from hdl_registers.register import Register 

29 

30if TYPE_CHECKING: 

31 from pathlib import Path 

32 

33 from hdl_registers.field.register_field import RegisterField 

34 from hdl_registers.register_array import RegisterArray 

35 

36 

37class PythonAccessorGenerator(RegisterCodeGenerator): 

38 """ 

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

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

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

42 

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

44 See also :class:`.PythonRegisterAccessorInterface`. 

45 

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

47 same output folder. 

48 """ 

49 

50 __version__ = "0.1.0" 

51 

52 SHORT_DESCRIPTION = "Python accessor" 

53 

54 COMMENT_START = "#" 

55 

56 @property 

57 def output_file(self) -> Path: 

58 """ 

59 Result will be placed in this file. 

60 """ 

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

62 

63 def get_code( 

64 self, 

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

66 ) -> str: 

67 """ 

68 Get Python code for accessing register and field values. 

69 """ 

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

71 

72 if self.register_list.register_objects: 

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

74 else: 

75 last_index = -1 

76 

77 register_access_methods = self._get_register_access_methods() 

78 register_value_types = self._get_register_value_types() 

79 

80 result = f'''\ 

81import pickle 

82from dataclasses import dataclass 

83from enum import Enum 

84from pathlib import Path 

85from typing import TYPE_CHECKING 

86from termcolor import colored 

87 

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 from hdl_registers.generator.python.register_accessor_interface import ( 

93 PythonRegisterAccessorInterface, 

94 ) 

95 from hdl_registers.register_list import RegisterList 

96 

97 

98THIS_DIR = Path(__file__).parent 

99 

100 

101class {class_name}: 

102 """ 

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

104 """ 

105 

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

107 """ 

108 Arguments: 

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

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

111 with the same interface and behavior as the interface class 

112 :class:`.PythonRegisterAccessorInterface`. 

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

114 """ 

115 self._register_accessor = register_accessor 

116 

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

118 if not pickle_path.exists(): 

119 raise FileNotFoundError( 

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

121 "make sure this artifact is generated." 

122 ) 

123 

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

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

126 

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

128 """ 

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

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

131 two interfaces. 

132 """ 

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

134 raise ValueError( 

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

136 ) 

137 

138 register_value = self._register_accessor.read_register( 

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

140 ) 

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

142 raise ValueError( 

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

144 ) 

145 

146 return register_value 

147 

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

149 """ 

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

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

152 two interfaces. 

153 """ 

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

155 raise ValueError( 

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

157 ) 

158 

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

160 raise ValueError( 

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

162 ) 

163 

164 self._register_accessor.write_register( 

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

166 register_address=4 * register_index, 

167 register_value=register_value 

168 ) 

169{register_access_methods}\ 

170 

171 

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

173 """ 

174 Factory function to create an accessor object. 

175 """ 

176 return {class_name}(register_accessor=register_accessor) 

177 

178 

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

180 """ 

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

182 Suitable for printing field and register values. 

183 """ 

184 result = "" 

185 if include_decimal: 

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

187 

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

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

190 

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

192 result += f"binary { binary_string} " 

193 

194 return result 

195 

196{register_value_types} 

197''' 

198 return self._format_with_black(result) 

199 

200 def _get_register_value_type_name( 

201 self, register: Register, register_array: RegisterArray | None 

202 ) -> str: 

203 """ 

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

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

206 """ 

207 if not register.fields: 

208 return "int" 

209 

210 register_name = self.qualified_register_name( 

211 register=register, register_array=register_array 

212 ) 

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

214 

215 def _get_register_value_types(self) -> str: 

216 """ 

217 Get all the classes needed to represent register values. 

218 One dataclass for each register that contains fields. 

219 """ 

220 result = "" 

221 

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

223 if not register.fields: 

224 continue 

225 

226 result += self._get_register_value_type( 

227 register=register, register_array=register_array 

228 ) 

229 

230 return result 

231 

232 def _get_register_value_type( 

233 self, register: Register, register_array: RegisterArray | None 

234 ) -> str: 

235 """ 

236 Get class for register value for one specific register. 

237 Assumes that the register has fields. 

238 """ 

239 class_name = self._get_register_value_type_name( 

240 register=register, register_array=register_array 

241 ) 

242 register_description = self.register_description( 

243 register=register, register_array=register_array 

244 ) 

245 result = f''' 

246@dataclass 

247class {class_name}: 

248 """ 

249 Represents the field values of the {register_description}. 

250 Uses native Python types for easy handling. 

251 """ 

252''' 

253 for field in register.fields: 

254 if isinstance(field, Enumeration): 

255 elements = [ 

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

257 ] 

258 separator = "\n " 

259 result += f''' 

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

261 """ 

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

263 """ 

264 {separator.join(elements)} 

265 

266''' 

267 for field in register.fields: 

268 field_comment = self._get_field_type_and_range_comment(field=field) 

269 field_type = self._get_field_python_type_name(field=field) 

270 result += f"""\ 

271 # {field_comment} 

272 {field.name}: {field_type} 

273""" 

274 result += ''' 

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

276 """ 

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

278 

279 Arguments: 

280 indentation: Optionally indent each field value line. 

281 no_color: Disable color output. 

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

283 """ 

284 values = [] 

285 

286''' 

287 for field in register.fields: 

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

289 result += f"""\ 

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

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

292 

293""" 

294 

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

296 

297 return result 

298 

299 def _get_field_python_type_name( 

300 self, field: RegisterField, global_type_name: str | None = None 

301 ) -> str: 

302 """ 

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

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

305 

306 Arguments: 

307 field: The field for which we want to get the type name. 

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 sign_comment = "Signed" if field.numerical_interpretation.is_signed else "Unsigned" 

344 

345 range_comment = ( 

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

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

348 ) 

349 

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

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

352 

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

354 integer_width = ( 

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

356 ) 

357 fractional_width = ( 

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

359 ) 

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

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

362 

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

364 

365 if isinstance(field, Bit): 

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

367 

368 if isinstance(field, Enumeration): 

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

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

371 

372 if isinstance(field, Integer): 

373 sign_comment = "Signed" if field.is_signed else "Unsigned" 

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

375 

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

377 

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

379 """ 

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

381 """ 

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

383 

384 if isinstance(field, Enumeration): 

385 field_type = self._get_field_python_type_name(field=field) 

386 return f""" 

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

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

389""" 

390 if isinstance(field, Integer): 

391 unsigned = ( 

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

393 if field.is_signed 

394 else field_value 

395 ) 

396 

397 return f""" 

398 unsigned_value = {unsigned} 

399 value_comment = _format_unsigned_number( 

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

401 ) 

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

403""" 

404 if isinstance(field, Bit): 

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

406 

407 if isinstance(field, BitVector): 

408 if isinstance(field.numerical_interpretation, Unsigned): 

409 return f""" 

410 value_comment = _format_unsigned_number( 

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

412 ) 

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

414""" 

415 if isinstance(field.numerical_interpretation, Signed): 

416 return f""" 

417 unsigned_value = to_unsigned_binary( 

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

419 ) 

420 value_comment = _format_unsigned_number( 

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

422 ) 

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

424""" 

425 

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

427 return f""" 

428 unsigned_value = to_unsigned_binary( 

429 num_bits={field.width}, 

430 value={field_value}, 

431 num_integer_bits={field.numerical_interpretation.integer_bit_width}, 

432 num_fractional_bits={field.numerical_interpretation.fraction_bit_width}, 

433 is_signed={field.numerical_interpretation.is_signed} 

434 ) 

435 value_comment = _format_unsigned_number( 

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

437 ) 

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

439""" 

440 

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

442 

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

444 

445 def _get_register_access_methods(self) -> str: 

446 """ 

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

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

449 """ 

450 result = "" 

451 

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

453 if register.mode.software_can_read: 

454 if register.fields: 

455 result += self._get_register_read_as_class( 

456 register=register, register_array=register_array 

457 ) 

458 else: 

459 result += self._get_register_read_as_integer( 

460 register=register, register_array=register_array 

461 ) 

462 

463 if register.mode.software_can_write: 

464 if register.fields: 

465 result += self._get_register_write_as_class( 

466 register=register, register_array=register_array 

467 ) 

468 result += self._get_fields_write( 

469 register=register, register_array=register_array 

470 ) 

471 else: 

472 result += self._get_register_write_as_integer( 

473 register=register, register_array=register_array 

474 ) 

475 

476 result += self._get_print_registers() 

477 

478 return result 

479 

480 def _get_register_read_as_class( 

481 self, register: Register, register_array: RegisterArray | None 

482 ) -> str: 

483 """ 

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

485 custom class that represents the field values. 

486 """ 

487 register_value_type_name = self._get_register_value_type_name( 

488 register=register, register_array=register_array 

489 ) 

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

491 common = self._get_register_read_common( 

492 register=register, 

493 register_array=register_array, 

494 register_value_type_name=register_value_type_name, 

495 ) 

496 result = f"""{common}\ 

497 register = self._register_list.get_register( 

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

499 ) 

500 return {register_value_type_name}( 

501""" 

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

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

504 

505 if isinstance(field, Enumeration): 

506 type_name = self._get_field_python_type_name( 

507 field=field, global_type_name=register_value_type_name 

508 ) 

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

510 

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

512 

513 result += ")" 

514 return result 

515 

516 def _get_register_read_as_integer( 

517 self, register: Register, register_array: RegisterArray | None 

518 ) -> str: 

519 """ 

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

521 """ 

522 common = self._get_register_read_common( 

523 register=register, 

524 register_array=register_array, 

525 register_value_type_name="int", 

526 extra_docstring=( 

527 " Return type is a plain unsigned integer " 

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

529 ), 

530 ) 

531 return f"""{common}\ 

532 return value 

533""" 

534 

535 def _get_register_read_common( 

536 self, 

537 register: Register, 

538 register_array: RegisterArray | None, 

539 register_value_type_name: str, 

540 extra_docstring: str = "", 

541 ) -> str: 

542 """ 

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

544 """ 

545 register_name = self._get_semi_qualified_register_name( 

546 register=register, register_array=register_array 

547 ) 

548 register_description = self.register_description( 

549 register=register, register_array=register_array 

550 ) 

551 

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

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

554 array_index_documentation = ( 

555 f""" 

556 Arguments: 

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

558""" 

559 if register_array 

560 else "" 

561 ) 

562 return f''' 

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

564 """ 

565 Read the {register_description}. 

566{extra_docstring}\ 

567{array_index_documentation}\ 

568 """ 

569{index_variable}\ 

570 value = self._read_register(register_index=index) 

571''' 

572 

573 def _get_register_write_as_class( 

574 self, register: Register, register_array: RegisterArray | None 

575 ) -> str: 

576 """ 

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

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

579 """ 

580 register_value_type_name = self._get_register_value_type_name( 

581 register=register, register_array=register_array 

582 ) 

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

584 common = self._get_register_write_common( 

585 register=register, 

586 register_array=register_array, 

587 register_value_type_name=register_value_type_name, 

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

589 ) 

590 result = f"""{common}\ 

591 register = self._register_list.get_register( 

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

593 ) 

594 integer_value = 0 

595""" 

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

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

598 

599 if isinstance(field, Enumeration): 

600 convert += ( 

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

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

603 ) 

604 else: 

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

606 

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

608 

609 result += """ self._write_register( 

610 register_index=index, register_value=integer_value 

611) 

612""" 

613 return result 

614 

615 def _get_register_write_as_integer( 

616 self, register: Register, register_array: RegisterArray | None 

617 ) -> str: 

618 """ 

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

620 integer that can be written straight away. 

621 """ 

622 result = self._get_register_write_common( 

623 register=register, 

624 register_array=register_array, 

625 register_value_type_name="int", 

626 register_value_docstring=( 

627 "Value to be written.\n" 

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

629 ), 

630 ) 

631 

632 result += """ self._write_register( 

633 register_index=index, register_value=register_value 

634) 

635""" 

636 return result 

637 

638 def _get_register_write_common( 

639 self, 

640 register: Register, 

641 register_array: RegisterArray | None, 

642 register_value_type_name: str, 

643 register_value_docstring: str, 

644 ) -> str: 

645 """ 

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

647 """ 

648 register_name = self._get_semi_qualified_register_name( 

649 register=register, register_array=register_array 

650 ) 

651 register_description = self.register_description( 

652 register=register, register_array=register_array 

653 ) 

654 

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

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

657 array_index_documentation = ( 

658 ( 

659 " array_index: Register array iteration " 

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

661 ) 

662 if register_array 

663 else "" 

664 ) 

665 return f''' 

666 def write_{register_name}(self, \ 

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

668 """ 

669 Write the whole {register_description}. 

670 

671 Arguments: 

672 register_value: {register_value_docstring}. 

673{array_index_documentation}\ 

674 """ 

675{index_variable}\ 

676''' 

677 

678 @staticmethod 

679 def _get_index_variable(register: Register, register_array: RegisterArray | None) -> str: 

680 """ 

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

682 """ 

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

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

685 return f"""\ 

686 index = self._register_list.get_register_index( 

687 register_name="{register.name}", 

688 register_array_name={register_array_name}, 

689 register_array_index={array_index_value}, 

690 ) 

691""" 

692 

693 def _get_fields_write(self, register: Register, register_array: RegisterArray | None) -> str: 

694 """ 

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

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

697 """ 

698 result = "" 

699 

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

701 if self.field_setter_should_read_modify_write(register=register): 

702 result += self._get_field_read_modify_write( 

703 field=field, register=register, register_array=register_array 

704 ) 

705 else: 

706 result += self._get_field_write( 

707 field_idx=field_idx, 

708 field=field, 

709 register=register, 

710 register_array=register_array, 

711 ) 

712 

713 return result 

714 

715 def _get_field_read_modify_write( 

716 self, field: RegisterField, register: Register, register_array: RegisterArray | None 

717 ) -> str: 

718 """ 

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

720 """ 

721 result = self._get_field_write_common( 

722 field=field, 

723 register=register, 

724 register_array=register_array, 

725 docstring=f"""\ 

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

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

728""", 

729 ) 

730 

731 register_name = self._get_semi_qualified_register_name( 

732 register=register, register_array=register_array 

733 ) 

734 array_index_association_plain = "array_index=array_index" if register_array else "" 

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

736 result += f"""\ 

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

738 register_value.{field.name} = field_value 

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

740""" 

741 

742 return result 

743 

744 def _get_field_write( 

745 self, 

746 field_idx: int, 

747 field: RegisterField, 

748 register: Register, 

749 register_array: RegisterArray | None, 

750 ) -> str: 

751 """ 

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

753 """ 

754 result = self._get_field_write_common( 

755 field=field, 

756 register=register, 

757 register_array=register_array, 

758 docstring=f"""\ 

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

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

761""", 

762 ) 

763 

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

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

766 result += f"""\ 

767{index_variable} 

768 register = self._register_list.get_register( 

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

770 ) 

771 register_value = 0 

772""" 

773 

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

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

776 result += " register_value += " 

777 if iterate_field_idx == field_idx: 

778 if isinstance(field, Enumeration): 

779 result += ( 

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

781 "get_element_by_name(field_value.value))" 

782 ) 

783 else: 

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

785 else: 

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

787 

788 result += "\n" 

789 

790 result += """ 

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

792 """ 

793 

794 return result 

795 

796 def _get_field_write_common( 

797 self, 

798 field: RegisterField, 

799 register: Register, 

800 register_array: RegisterArray | None, 

801 docstring: str, 

802 ) -> str: 

803 """ 

804 Common portions for field write methods. 

805 """ 

806 field_name = self._get_semi_qualified_field_name( 

807 field=field, register=register, register_array=register_array 

808 ) 

809 field_value_type_name = self._get_field_python_type_name( 

810 field=field, 

811 global_type_name=self._get_register_value_type_name( 

812 register=register, register_array=register_array 

813 ), 

814 ) 

815 field_description = self.field_description( 

816 register=register, field=field, register_array=register_array 

817 ) 

818 field_comment = self._get_field_type_and_range_comment(field=field) 

819 

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

821 array_index_documentation = ( 

822 ( 

823 " array_index: Register array iteration " 

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

825 ) 

826 if register_array 

827 else "" 

828 ) 

829 return f''' 

830 def write_{field_name}(self, \ 

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

832 """ 

833 Write the {field_description}. 

834 {docstring}\ 

835 

836 Arguments: 

837 field_value: The field value to be written. 

838 {field_comment} 

839{array_index_documentation}\ 

840 """ 

841''' 

842 

843 @staticmethod 

844 def _get_semi_qualified_register_name( 

845 register: Register, register_array: RegisterArray | None 

846 ) -> str: 

847 """ 

848 Base name of register access methods. 

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

850 """ 

851 if register_array is None: 

852 return register.name 

853 

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

855 

856 def _get_semi_qualified_field_name( 

857 self, field: RegisterField, register: Register, register_array: RegisterArray | None 

858 ) -> str: 

859 """ 

860 Base name of field access methods. 

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

862 """ 

863 register_name = self._get_semi_qualified_register_name( 

864 register=register, register_array=register_array 

865 ) 

866 

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

868 

869 def _get_print_registers(self) -> str: 

870 """ 

871 Python method to print all registers and their values. 

872 """ 

873 result = ''' 

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

875 """ 

876 Print all registers and their values to STDOUT. 

877 

878 Arguments: 

879 no_color: Disable color output. 

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

881 """ 

882''' 

883 

884 for register_object in self.register_list.register_objects: 

885 if isinstance(register_object, Register): 

886 result += self._get_print_register( 

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

888 ) 

889 else: 

890 for array_index in range(register_object.length): 

891 for register in register_object.registers: 

892 result += self._get_print_register( 

893 is_readable=register.mode.software_can_read, 

894 register=register, 

895 register_array=register_object, 

896 register_array_index=array_index, 

897 ) 

898 

899 return result 

900 

901 @staticmethod 

902 def _get_print_register( 

903 is_readable: bool, 

904 register: Register, 

905 register_array: RegisterArray | None = None, 

906 register_array_index: int | None = None, 

907 ) -> str: 

908 """ 

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

910 """ 

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

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

913 index = ( 

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

915 ) 

916 read = ( 

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

918 f"(array_index={register_array_index})" 

919 ) 

920 else: 

921 name = register.name 

922 index = register.index 

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

924 

925 heading = f"""\ 

926print( 

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

928 + " '{name}' " 

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

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

931) 

932""" 

933 

934 if not is_readable: 

935 return f"""\ 

936 {heading}\ 

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

938 

939""" 

940 

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

942 if not register.fields: 

943 return f"""\ 

944 {heading}\ 

945 {read_value}\ 

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

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

948 

949""" 

950 

951 return f"""\ 

952 {heading}\ 

953 {read_value}\ 

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

955 print() 

956 

957""" 

958 

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

960 """ 

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

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

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

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

965 """ 

966 # Not that this is NOT a public API: 

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

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

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

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

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

972 # Should give a consistent look and feel. 

973 mode = Mode(line_length=100) 

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