Coverage for hdl_registers/parser.py: 100%

127 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-29 22:03 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the hdl_registers project, a HDL register generator fast enough to be run 

5# in real time. 

6# https://hdl-registers.com 

7# https://gitlab.com/hdl_registers/hdl_registers 

8# -------------------------------------------------------------------------------------------------- 

9 

10# Standard libraries 

11import copy 

12 

13# Third party libraries 

14import tomli 

15from tsfpga.system_utils import read_file 

16 

17# Local folder libraries 

18from .constant import Constant 

19from .register_list import RegisterList 

20 

21 

22def load_toml_file(toml_file): 

23 if not toml_file.exists(): 

24 raise FileNotFoundError(f"Requested TOML file does not exist: {toml_file}") 

25 

26 raw_toml = read_file(toml_file) 

27 try: 

28 return tomli.loads(raw_toml) 

29 except Exception as exception_info: 

30 message = f"Error while parsing TOML file {toml_file}:\n{exception_info}" 

31 raise ValueError(message) from exception_info 

32 

33 

34def from_toml(module_name, toml_file, default_registers=None): 

35 """ 

36 Parse a register TOML file. 

37 

38 Arguments: 

39 module_name (str): The name of the module that these registers belong to. 

40 toml_file (pathlib.Path): The TOML file path. 

41 default_registers (list(Register)): List of default registers. 

42 

43 Returns: 

44 :class:`.RegisterList`: The resulting register list. 

45 """ 

46 parser = RegisterParser( 

47 module_name=module_name, 

48 source_definition_file=toml_file, 

49 default_registers=default_registers, 

50 ) 

51 toml_data = load_toml_file(toml_file) 

52 

53 return parser.parse(toml_data) 

54 

55 

56class RegisterParser: 

57 

58 recognized_constant_items = {"value", "description"} 

59 recognized_register_items = {"mode", "description", "bit", "bit_vector"} 

60 recognized_register_array_items = {"array_length", "description", "register"} 

61 recognized_bit_items = {"description", "default_value"} 

62 recognized_bit_vector_items = {"description", "width", "default_value"} 

63 

64 def __init__(self, module_name, source_definition_file, default_registers): 

65 self._register_list = RegisterList( 

66 name=module_name, source_definition_file=source_definition_file 

67 ) 

68 self._source_definition_file = source_definition_file 

69 

70 self._default_register_names = [] 

71 if default_registers is not None: 

72 # Perform deep copy of the mutable register objects 

73 self._register_list.register_objects = copy.deepcopy(default_registers) 

74 for register in default_registers: 

75 self._default_register_names.append(register.name) 

76 

77 self._names_taken = set() 

78 

79 def parse(self, register_data): 

80 """ 

81 Parse the TOML data. 

82 

83 Arguments: 

84 register_data (str): TOML register data. 

85 

86 Returns: 

87 :class:`.RegisterList`: The resulting register list. 

88 """ 

89 if "constant" in register_data: 

90 for name, items in register_data["constant"].items(): 

91 self._parse_constant(name, items) 

92 

93 if "register" in register_data: 

94 for name, items in register_data["register"].items(): 

95 self._parse_plain_register(name, items) 

96 

97 if "register_array" in register_data: 

98 for name, items in register_data["register_array"].items(): 

99 self._parse_register_array(name, items) 

100 

101 return self._register_list 

102 

103 def _parse_constant(self, name, items): 

104 if "value" not in items: 

105 message = ( 

106 f'Constant "{name}" in {self._source_definition_file} does not have ' 

107 '"value" field' 

108 ) 

109 raise ValueError(message) 

110 

111 description = items.get("description", "") 

112 

113 for item_name in items.keys(): 

114 if item_name not in self.recognized_constant_items: 

115 message = ( 

116 f'Error while parsing constant "{name}" in {self._source_definition_file}: ' 

117 f'Unknown key "{item_name}"' 

118 ) 

119 raise ValueError(message) 

120 

121 constant = Constant(name=name, value=items["value"], description=description) 

122 

123 self._register_list.constants.append(constant) 

124 

125 def _parse_plain_register(self, name, items): 

126 for item_name in items.keys(): 

127 if item_name not in self.recognized_register_items: 

128 message = ( 

129 f'Error while parsing register "{name}" in {self._source_definition_file}: ' 

130 f'Unknown key "{item_name}"' 

131 ) 

132 raise ValueError(message) 

133 

134 description = items.get("description", "") 

135 

136 if name in self._default_register_names: 

137 # Default registers can be "updated" in the sense that the user can use a custom 

138 # description and add whatever bits they use in the current module. They can not however 

139 # change the mode. 

140 if "mode" in items: 

141 message = ( 

142 f'Overloading register "{name}" in {self._source_definition_file}, ' 

143 'one can not change "mode" from default' 

144 ) 

145 raise ValueError(message) 

146 

147 register = self._register_list.get_register(name) 

148 register.description = description 

149 

150 else: 

151 # If it is a new register however the mode has to be specified. 

152 if "mode" not in items: 

153 raise ValueError( 

154 f'Register "{name}" in {self._source_definition_file} does not have ' 

155 '"mode" field' 

156 ) 

157 mode = items["mode"] 

158 register = self._register_list.append_register( 

159 name=name, mode=mode, description=description 

160 ) 

161 

162 self._names_taken.add(name) 

163 

164 if "bit" in items: 

165 self._parse_bits(register, items["bit"]) 

166 

167 if "bit_vector" in items: 

168 self._parse_bit_vectors(register, items["bit_vector"]) 

169 

170 def _parse_register_array(self, name, items): 

171 if name in self._names_taken: 

172 message = f'Duplicate name "{name}" in {self._source_definition_file}' 

173 raise ValueError(message) 

174 if "array_length" not in items: 

175 message = ( 

176 f'Register array "{name}" in {self._source_definition_file} does not have ' 

177 '"array_length" attribute' 

178 ) 

179 raise ValueError(message) 

180 

181 for item_name in items: 

182 if item_name not in self.recognized_register_array_items: 

183 message = ( 

184 f'Error while parsing register array "{name}" in ' 

185 f'{self._source_definition_file}: Unknown key "{item_name}"' 

186 ) 

187 raise ValueError(message) 

188 

189 length = items["array_length"] 

190 description = items.get("description", "") 

191 register_array = self._register_list.append_register_array( 

192 name=name, length=length, description=description 

193 ) 

194 

195 for register_name, register_items in items["register"].items(): 

196 # The only required field 

197 if "mode" not in register_items: 

198 message = ( 

199 f'Register "{register_name}" within array "{name}" in ' 

200 f'{self._source_definition_file} does not have "mode" field' 

201 ) 

202 raise ValueError(message) 

203 

204 for register_item_name in register_items.keys(): 

205 if register_item_name not in self.recognized_register_items: 

206 message = ( 

207 f'Error while parsing register "{register_name}" in array "{name}" in ' 

208 f'{self._source_definition_file}: Unknown key "{register_item_name}"' 

209 ) 

210 raise ValueError(message) 

211 

212 mode = register_items["mode"] 

213 description = register_items.get("description", "") 

214 

215 register = register_array.append_register( 

216 name=register_name, mode=mode, description=description 

217 ) 

218 

219 if "bit" in register_items: 

220 self._parse_bits(register, register_items["bit"]) 

221 

222 if "bit_vector" in register_items: 

223 self._parse_bit_vectors(register, register_items["bit_vector"]) 

224 

225 def _parse_bits(self, register, bit_configurations): 

226 for bit_name, bit_configuration in bit_configurations.items(): 

227 for item_name in bit_configuration.keys(): 

228 if item_name not in self.recognized_bit_items: 

229 message = ( 

230 f'Error while parsing bit "{bit_name}" in register "{register.name}" in ' 

231 f'{self._source_definition_file}: Unknown key "{item_name}"' 

232 ) 

233 raise ValueError(message) 

234 

235 description = bit_configuration.get("description", "") 

236 default_value = bit_configuration.get("default_value", "0") 

237 

238 register.append_bit(name=bit_name, description=description, default_value=default_value) 

239 

240 def _parse_bit_vectors(self, register, bit_vector_configurations): 

241 for vector_name, vector_configuration in bit_vector_configurations.items(): 

242 # The only required field 

243 if "width" not in vector_configuration: 

244 message = ( 

245 f'Bit vector "{vector_name}" in register "{register.name}" in file ' 

246 f'{self._source_definition_file} does not have a "width" property' 

247 ) 

248 raise ValueError(message) 

249 

250 for item_name in vector_configuration.keys(): 

251 if item_name not in self.recognized_bit_vector_items: 

252 message = ( 

253 f'Error while parsing bit vector "{vector_name}" in register ' 

254 f'"{register.name}" in {self._source_definition_file}: ' 

255 f'Unknown key "{item_name}"' 

256 ) 

257 raise ValueError(message) 

258 

259 width = vector_configuration["width"] 

260 

261 description = vector_configuration.get("description", "") 

262 default_value = vector_configuration.get("default_value", "0" * width) 

263 

264 register.append_bit_vector(vector_name, description, width, default_value)