Coverage for hdl_registers/generator/register_code_generator_helpers.py: 95%

80 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 11:11 +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 

13 

14from hdl_registers.register import Register 

15from hdl_registers.register_array import RegisterArray 

16from hdl_registers.register_modes import REGISTER_MODES 

17 

18if TYPE_CHECKING: 

19 from collections.abc import Iterator 

20 

21 from hdl_registers.constant.constant import Constant 

22 from hdl_registers.field.register_field import RegisterField 

23 from hdl_registers.register_list import RegisterList 

24 

25 

26class RegisterCodeGeneratorHelpers: 

27 """ 

28 Various helper methods that make register code generation easier. 

29 """ 

30 

31 # Defined in 'RegisterCodeGenerator' class, which shall also be inherited wherever this class 

32 # is used. 

33 register_list: RegisterList 

34 name: str 

35 DEFAULT_INDENTATION_LEVEL: int 

36 COMMENT_START: str 

37 COMMENT_END: str 

38 

39 def iterate_constants(self) -> Iterator[Constant]: 

40 """ 

41 Iterate of all constants in the register list. 

42 """ 

43 yield from self.register_list.constants 

44 

45 def iterate_register_objects(self) -> Iterator[Register | RegisterArray]: 

46 """ 

47 Iterate over all register objects in the register list. 

48 I.e. all plain registers and all register arrays. 

49 """ 

50 yield from self.register_list.register_objects 

51 

52 def iterate_registers(self) -> Iterator[tuple[Register, RegisterArray | None]]: 

53 """ 

54 Iterate over all registers, plain or in array, in the register list. 

55 

56 Return: 

57 If the register is plain, the array return value in the tuple will be ``None``. 

58 If the register is in an array, the array return value will conversely be non-``None``. 

59 """ 

60 for register_object in self.iterate_register_objects(): 

61 if isinstance(register_object, Register): 

62 yield (register_object, None) 

63 else: 

64 for register in register_object.registers: 

65 yield (register, register_object) 

66 

67 def iterate_plain_registers(self) -> Iterator[Register]: 

68 """ 

69 Iterate over all plain registers (i.e. registers not in array) in the register list. 

70 """ 

71 for register_object in self.iterate_register_objects(): 

72 if isinstance(register_object, Register): 

73 yield register_object 

74 

75 def iterate_register_arrays(self) -> Iterator[RegisterArray]: 

76 """ 

77 Iterate over all register arrays in the register list. 

78 """ 

79 for register_object in self.iterate_register_objects(): 

80 if isinstance(register_object, RegisterArray): 

81 yield register_object 

82 

83 def qualified_register_name( 

84 self, register: Register, register_array: RegisterArray | None = None 

85 ) -> str: 

86 """ 

87 Get the qualified register name, e.g. "<module name>_<register name>". 

88 To be used where the scope requires it, i.e. outside of records. 

89 """ 

90 if register_array is None: 

91 return f"{self.name}_{register.name}" 

92 

93 register_array_name = self.qualified_register_array_name(register_array=register_array) 

94 return f"{register_array_name}_{register.name}" 

95 

96 def qualified_register_array_name(self, register_array: RegisterArray) -> str: 

97 """ 

98 Get the qualified register array name, e.g. "<module name>_<register array name>". 

99 To be used where the scope requires it, i.e. outside of records. 

100 """ 

101 return f"{self.name}_{register_array.name}" 

102 

103 def qualified_field_name( 

104 self, 

105 register: Register, 

106 field: RegisterField, 

107 register_array: RegisterArray | None = None, 

108 ) -> str: 

109 """ 

110 Get the qualified field name, e.g. "<module name>_<register name>_<field_name>". 

111 To be used where the scope requires it, i.e. outside of records. 

112 """ 

113 register_name = self.qualified_register_name( 

114 register=register, register_array=register_array 

115 ) 

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

117 

118 def get_indentation(self, indent: int | None = None) -> str: 

119 """ 

120 Get the requested indentation in spaces. 

121 Will use the default indentation for this generator if not specified. 

122 """ 

123 indent = self.DEFAULT_INDENTATION_LEVEL if indent is None else indent 

124 return " " * indent 

125 

126 def get_separator_line(self, indent: int | None = None) -> str: 

127 """ 

128 Get a separator line, e.g. ``# ---------------------------------``. 

129 """ 

130 indentation = self.get_indentation(indent=indent) 

131 result = f"{indentation}{self.COMMENT_START} " 

132 

133 num_dash = 80 - len(result) - len(self.COMMENT_END) 

134 result += "-" * num_dash 

135 result += f"{self.COMMENT_END}\n" 

136 

137 return result 

138 

139 def comment(self, comment: str, indent: int | None = None) -> str: 

140 """ 

141 Create a one-line comment. 

142 """ 

143 indentation = self.get_indentation(indent=indent) 

144 return f"{indentation}{self.COMMENT_START} {comment}{self.COMMENT_END}\n" 

145 

146 def comment_block(self, text: list[str], indent: int | None = None) -> str: 

147 """ 

148 Create a comment block from a list of text lines. 

149 """ 

150 return "".join(self.comment(comment=line, indent=indent) for line in text) 

151 

152 @staticmethod 

153 def register_description( 

154 register: Register, register_array: RegisterArray | None = None 

155 ) -> str: 

156 """ 

157 Get a comment describing the register. 

158 """ 

159 result = f"'{register.name}' register" 

160 

161 if register_array is None: 

162 return result 

163 

164 return f"{result} within the '{register_array.name}' register array" 

165 

166 def field_description( 

167 self, 

168 register: Register, 

169 field: RegisterField, 

170 register_array: RegisterArray | None = None, 

171 ) -> str: 

172 """ 

173 Get a comment describing the field. 

174 """ 

175 register_description = self.register_description( 

176 register=register, register_array=register_array 

177 ) 

178 return f"'{field.name}' field in the {register_description}" 

179 

180 @staticmethod 

181 def field_setter_should_read_modify_write(register: Register) -> bool: 

182 """ 

183 Returns True if a field value setter should read-modify-write the register. 

184 

185 Is only true if the register is of a writeable type where the software can also read back 

186 a previously-written value. 

187 Furthermore, read-modify-write only makes sense if there is more than one field, otherwise 

188 it is a waste of CPU cycles. 

189 """ 

190 if not register.fields: 

191 raise ValueError("Should not end up here if the register has no fields.") 

192 

193 if register.mode == REGISTER_MODES["r_w"]: 

194 return len(register.fields) > 1 

195 

196 if register.mode in [ 

197 REGISTER_MODES["w"], 

198 REGISTER_MODES["wpulse"], 

199 REGISTER_MODES["r_wpulse"], 

200 ]: 

201 return False 

202 

203 raise ValueError(f"Got non-writeable register: {register}") 

204 

205 @staticmethod 

206 def to_pascal_case(snake_string: str) -> str: 

207 """ 

208 Converts e.g., "my_funny_string" to "MyFunnyString". 

209 

210 Pascal case is like camel case but with the initial character being capitalized. 

211 I.e. how classes are named in Python, C and C++. 

212 """ 

213 return snake_string.title().replace("_", "")