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

77 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-07 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 typing import TYPE_CHECKING, Iterator, Optional, Union 

12 

13# First party libraries 

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 # First party libraries 

20 from hdl_registers.constant.constant import Constant 

21 from hdl_registers.field.register_field import RegisterField 

22 from hdl_registers.register_list import RegisterList 

23 

24 

25class RegisterCodeGeneratorHelpers: 

26 """ 

27 Various helper methods that make register code generation easier. 

28 """ 

29 

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

31 # is used. 

32 register_list: "RegisterList" 

33 name: str 

34 DEFAULT_INDENTATION_LEVEL: int 

35 COMMENT_START: str 

36 COMMENT_END: str 

37 

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

39 """ 

40 Iterate of all constants in the register list. 

41 """ 

42 yield from self.register_list.constants 

43 

44 def iterate_register_objects(self) -> Iterator[Union[Register, RegisterArray]]: 

45 """ 

46 Iterate over all register objects in the register list. 

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

48 """ 

49 yield from self.register_list.register_objects 

50 

51 def iterate_registers(self) -> Iterator[tuple[Register, Optional[RegisterArray]]]: 

52 """ 

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

54 

55 Return: 

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

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

58 """ 

59 for register_object in self.iterate_register_objects(): 

60 if isinstance(register_object, Register): 

61 yield (register_object, None) 

62 else: 

63 for register in register_object.registers: 

64 yield (register, register_object) 

65 

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

67 """ 

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

69 """ 

70 for register_object in self.iterate_register_objects(): 

71 if isinstance(register_object, Register): 

72 yield register_object 

73 

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

75 """ 

76 Iterate over all register arrays in the register list. 

77 """ 

78 for register_object in self.iterate_register_objects(): 

79 if isinstance(register_object, RegisterArray): 

80 yield register_object 

81 

82 def qualified_register_name( 

83 self, register: "Register", register_array: Optional["RegisterArray"] = None 

84 ) -> str: 

85 """ 

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

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

88 """ 

89 if register_array is None: 

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

91 

92 register_array_name = self.qualified_register_array_name(register_array=register_array) 

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

94 

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

96 """ 

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

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

99 """ 

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

101 

102 def qualified_field_name( 

103 self, 

104 register: "Register", 

105 field: "RegisterField", 

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

107 ) -> str: 

108 """ 

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

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

111 """ 

112 register_name = self.qualified_register_name( 

113 register=register, register_array=register_array 

114 ) 

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

116 

117 def get_indentation(self, indent: Optional[int] = None) -> str: 

118 """ 

119 Get the requested indentation in spaces. 

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

121 """ 

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

123 return " " * indent 

124 

125 def get_separator_line(self, indent: Optional[int] = None) -> str: 

126 """ 

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

128 """ 

129 indentation = self.get_indentation(indent=indent) 

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

131 

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

133 result += "-" * num_dash 

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

135 

136 return result 

137 

138 def comment(self, comment: str, indent: Optional[int] = None) -> str: 

139 """ 

140 Create a one-line comment. 

141 """ 

142 indentation = self.get_indentation(indent=indent) 

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

144 

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

146 """ 

147 Create a comment block from a string with newlines. 

148 """ 

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

150 

151 @staticmethod 

152 def register_description( 

153 register: Register, register_array: Optional[RegisterArray] = None 

154 ) -> str: 

155 """ 

156 Get a comment describing the register. 

157 """ 

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

159 

160 if register_array is None: 

161 return result 

162 

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

164 

165 def field_description( 

166 self, 

167 register: Register, 

168 field: "RegisterField", 

169 register_array: Optional[RegisterArray] = None, 

170 ) -> str: 

171 """ 

172 Get a comment describing the field. 

173 """ 

174 register_description = self.register_description( 

175 register=register, register_array=register_array 

176 ) 

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

178 

179 @staticmethod 

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

181 """ 

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

183 

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

185 a previously-written value. 

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

187 it is a waste of CPU cycles. 

188 """ 

189 assert register.fields, "Should not end up here if the register has no fields." 

190 

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

192 return len(register.fields) > 1 

193 

194 if register.mode in [ 

195 REGISTER_MODES["w"], 

196 REGISTER_MODES["wpulse"], 

197 REGISTER_MODES["r_wpulse"], 

198 ]: 

199 return False 

200 

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

202 

203 @staticmethod 

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

205 """ 

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

207 

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

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

210 """ 

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