Coverage for hdl_registers/generator/c/header.py: 98%

108 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 pathlib import Path 

12from typing import TYPE_CHECKING, Any, Optional 

13 

14# First party libraries 

15from hdl_registers.constant.bit_vector_constant import UnsignedVectorConstant 

16from hdl_registers.constant.boolean_constant import BooleanConstant 

17from hdl_registers.constant.float_constant import FloatConstant 

18from hdl_registers.constant.integer_constant import IntegerConstant 

19from hdl_registers.constant.string_constant import StringConstant 

20from hdl_registers.field.enumeration import Enumeration 

21from hdl_registers.generator.register_code_generator import RegisterCodeGenerator 

22from hdl_registers.register import Register 

23from hdl_registers.register_list import RegisterList 

24 

25if TYPE_CHECKING: 

26 # First party libraries 

27 from hdl_registers.register_array import RegisterArray 

28 

29# There is no unit test of this class that checks the generated code. It is instead functionally 

30# tested in the file 'test_compiled_c_code.py'. That test generates C code from an example 

31# register set, compiles it and performs some run-time assertions in a C program. 

32# That test is considered more meaningful and exhaustive than a unit test would be. 

33 

34 

35class CHeaderGenerator(RegisterCodeGenerator): 

36 """ 

37 Generate a C header. 

38 See the :ref:`generator_c` article for usage details. 

39 

40 The header will contain: 

41 

42 * Constant values for all :ref:`register constants <constant_overview>`. 

43 

44 * Enumeration types for all :ref:`field_enumeration`. 

45 

46 * A ``struct`` type with all registers as members, which can be memory mapped directly. 

47 

48 * For each register, ``#define`` constants with the index and address of the register. 

49 

50 * For each field in each register, ``#define`` constants with the bit shift, bit mask and 

51 inverse bit mask of the field. 

52 """ 

53 

54 __version__ = "1.0.0" 

55 

56 SHORT_DESCRIPTION = "C header" 

57 

58 COMMENT_START = "//" 

59 

60 # The most commonly used indentation. 

61 # For code at the top level. 

62 DEFAULT_INDENTATION_LEVEL = 0 

63 

64 @property 

65 def output_file(self) -> Path: 

66 """ 

67 Result will be placed in this file. 

68 """ 

69 return self.output_folder / self._file_name 

70 

71 def __init__( 

72 self, register_list: RegisterList, output_folder: Path, file_name: Optional[str] = None 

73 ): 

74 """ 

75 For argument description, please see the super class. 

76 

77 Arguments: 

78 file_name: Optionally specify an explicit result file name. 

79 If not specified, the name will be derived from the name of the register list. 

80 """ 

81 super().__init__(register_list=register_list, output_folder=output_folder) 

82 

83 self._file_name = f"{self.name}_regs.h" if file_name is None else file_name 

84 

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

86 """ 

87 Get a complete C header with all constants and all registers. 

88 """ 

89 define_name = f"{self.name.upper()}_REGS_H" 

90 

91 c_code = f"""\ 

92{self.header} 

93#ifndef {define_name} 

94#define {define_name} 

95 

96{self._constants()} 

97{self._number_of_registers()} 

98{self._register_struct()} 

99{self._register_defines()}\ 

100#endif {self.comment(define_name)}""" 

101 

102 return c_code 

103 

104 def _register_struct(self) -> str: 

105 array_structs = "" 

106 

107 register_struct_type = f"{self.name}_regs_t" 

108 

109 register_struct = self.comment("Type for this register map.") 

110 register_struct += f"typedef struct {register_struct_type}\n" 

111 register_struct += "{\n" 

112 

113 for register_object in self.iterate_register_objects(): 

114 if isinstance(register_object, Register): 

115 register_struct += self.comment(f'Mode "{register_object.mode.name}".', indent=2) 

116 register_struct += f" uint32_t {register_object.name};\n" 

117 

118 else: 

119 array_struct_type = f"{self.name}_{register_object.name}_t" 

120 

121 array_structs += self.comment( 

122 f"Type for the '{register_object.name}' register array." 

123 ) 

124 array_structs += f"typedef struct {array_struct_type}\n" 

125 array_structs += "{\n" 

126 for register in register_object.registers: 

127 array_structs += self.comment(f"Mode '{register.mode.name}'.", indent=2) 

128 array_structs += f" uint32_t {register.name};\n" 

129 array_structs += f"}} {array_struct_type};\n\n" 

130 

131 register_struct += ( 

132 f" {array_struct_type} {register_object.name}[{register_object.length}];\n" 

133 ) 

134 

135 register_struct += f"}} {register_struct_type};\n" 

136 

137 return array_structs + register_struct 

138 

139 def _number_of_registers(self) -> str: 

140 # It is possible that we have constants but no registers 

141 num_regs = 0 

142 if self.register_list.register_objects: 

143 num_regs = self.register_list.register_objects[-1].index + 1 

144 

145 c_code = self.comment("Number of registers within this register map.") 

146 c_code += f"#define {self.name.upper()}_NUM_REGS ({num_regs}u)\n" 

147 

148 return c_code 

149 

150 def _register_defines(self) -> str: 

151 c_code = "" 

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

153 c_code += self._addr_define(register, register_array) 

154 c_code += self._field_definitions(register, register_array) 

155 c_code += "\n" 

156 

157 return c_code 

158 

159 def _addr_define(self, register: Register, register_array: Optional["RegisterArray"]) -> str: 

160 name = self.qualified_register_name( 

161 register=register, register_array=register_array 

162 ).upper() 

163 

164 register_description = self.register_description( 

165 register=register, register_array=register_array 

166 ) 

167 

168 register_array_comment = ( 

169 f" (array_index < {register_array.length})" if register_array else "" 

170 ) 

171 comment = [ 

172 f"Address of the {register_description}{register_array_comment}.", 

173 f"Mode '{register.mode.name}'.", 

174 ] 

175 c_code = self.comment_block(comment) 

176 

177 if register_array: 

178 c_code += ( 

179 f"#define {name}_INDEX(array_index) ({register_array.base_index}u + " 

180 f"(array_index) * {len(register_array.registers)}u + {register.index}u)\n" 

181 ) 

182 c_code += f"#define {name}_ADDR(array_index) (4u * {name}_INDEX(array_index))\n" 

183 else: 

184 c_code += f"#define {name}_INDEX ({register.index}u)\n" 

185 c_code += f"#define {name}_ADDR (4u * {name}_INDEX)\n" 

186 

187 return c_code 

188 

189 def _field_definitions( 

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

191 ) -> str: 

192 c_code = "" 

193 for field in register.fields: 

194 field_description = self.field_description( 

195 register=register, register_array=register_array, field=field 

196 ) 

197 c_code += self.comment(f"Attributes for the {field_description}.") 

198 

199 name = self.qualified_field_name( 

200 register=register, field=field, register_array=register_array 

201 ).upper() 

202 c_code += f"#define {name}_SHIFT ({field.base_index}u)\n" 

203 c_code += f'#define {name}_MASK (0b{"1" * field.width}u << {field.base_index}u)\n' 

204 c_code += f"#define {name}_MASK_INVERSE (~{name}_MASK)\n" 

205 

206 if isinstance(field, Enumeration): 

207 # Enums in C export their enumerators to the surrounding scope, causing huge risk of 

208 # name clashes. 

209 # Hence we give the enumerators long names qualified with the register name, etc. 

210 name_value_pairs = [ 

211 f"{name}_{element.name.upper()} = {element.value}," 

212 for element in field.elements 

213 ] 

214 separator = "\n " 

215 

216 c_code += f"""\ 

217enum {self.to_pascal_case(name)} 

218{{ 

219 {separator.join(name_value_pairs)} 

220}}; 

221""" 

222 

223 return c_code 

224 

225 def _constants(self) -> str: 

226 c_code = "" 

227 

228 def define(name: str, value: str) -> str: 

229 return f"#define {name} ({value})\n" 

230 

231 for constant in self.iterate_constants(): 

232 c_code += self.comment(f"Value of register constant '{constant.name}'.") 

233 

234 constant_name = f"{self.name.upper()}_{constant.name.upper()}" 

235 

236 if isinstance(constant, BooleanConstant): 

237 c_code += define(constant_name, str(constant.value).lower()) 

238 elif isinstance(constant, IntegerConstant): 

239 # No suffix -> "int", i.e. signed integer of at least 32 bits. 

240 c_code += define(constant_name, str(constant.value)) 

241 elif isinstance(constant, FloatConstant): 

242 # No suffix -> "double" (https://stackoverflow.com/questions/13276862). 

243 # Matches the VHDL type which is at least 64 bits (IEEE 1076-2008, 5.2.5.1). 

244 # Note that casting a Python float to string guarantees full precision in the 

245 # resulting string: https://stackoverflow.com/a/60026172 

246 c_code += define(constant_name, str(constant.value)) 

247 elif isinstance(constant, StringConstant): 

248 # C string literal: Raw value enclosed in double quotation marks. 

249 # Without "static", we get a linker warning about multiple definitions 

250 # despite the multiple-include guard. 

251 # See here https://stackoverflow.com/a/9196883 for some other information. 

252 c_code += f'static char *{constant_name} = "{constant.value}";\n' 

253 elif isinstance(constant, UnsignedVectorConstant): 

254 # "unsigned" and "long" as suffix. 

255 # Makes it possible to use large numbers for e.g. base addresses. 

256 c_code += define( 

257 constant_name, f"{constant.prefix}{constant.value_without_separator}UL" 

258 ) 

259 else: 

260 raise ValueError(f"Got unexpected constant type. {constant}") 

261 

262 return c_code