Coverage for hdl_registers/parser.py: 100%

125 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-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/tsfpga/hdl_registers 

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

9 

10import copy 

11import tomli 

12 

13from tsfpga.system_utils import read_file 

14 

15from .register_list import RegisterList 

16from .constant import Constant 

17 

18 

19def load_toml_file(toml_file): 

20 if not toml_file.exists(): 

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

22 

23 raw_toml = read_file(toml_file) 

24 try: 

25 return tomli.loads(raw_toml) 

26 except Exception as exception_info: 

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

28 raise ValueError(message) from exception_info 

29 

30 

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

32 """ 

33 Parse a register TOML file. 

34 

35 Arguments: 

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

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

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

39 

40 Returns: 

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

42 """ 

43 parser = RegisterParser( 

44 module_name=module_name, 

45 source_definition_file=toml_file, 

46 default_registers=default_registers, 

47 ) 

48 toml_data = load_toml_file(toml_file) 

49 

50 return parser.parse(toml_data) 

51 

52 

53class RegisterParser: 

54 

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

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

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

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

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

60 

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

62 self._register_list = RegisterList( 

63 name=module_name, source_definition_file=source_definition_file 

64 ) 

65 self._source_definition_file = source_definition_file 

66 

67 self._default_register_names = [] 

68 if default_registers is not None: 

69 # Perform deep copy of the mutable register objects 

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

71 for register in default_registers: 

72 self._default_register_names.append(register.name) 

73 

74 self._names_taken = set() 

75 

76 def parse(self, register_data): 

77 """ 

78 Parse the TOML data. 

79 

80 Arguments: 

81 register_data (str): TOML register data. 

82 

83 Returns: 

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

85 """ 

86 if "constant" in register_data: 

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

88 self._parse_constant(name, items) 

89 

90 if "register" in register_data: 

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

92 self._parse_plain_register(name, items) 

93 

94 if "register_array" in register_data: 

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

96 self._parse_register_array(name, items) 

97 

98 return self._register_list 

99 

100 def _parse_constant(self, name, items): 

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

102 

103 for item_name, item_value in items.items(): 

104 if item_name not in self.recognized_constant_items: 

105 message = ( 

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

107 f'Unknown key "{item_name}"' 

108 ) 

109 raise ValueError(message) 

110 

111 if item_name == "description": 

112 constant.description = item_value 

113 

114 self._register_list.constants.append(constant) 

115 

116 def _parse_plain_register(self, name, items): 

117 for item_name in items.keys(): 

118 if item_name not in self.recognized_register_items: 

119 message = ( 

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

121 f'Unknown key "{item_name}"' 

122 ) 

123 raise ValueError(message) 

124 

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

126 

127 if name in self._default_register_names: 

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

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

130 # change the mode. 

131 if "mode" in items: 

132 message = ( 

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

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

135 ) 

136 raise ValueError(message) 

137 

138 register = self._register_list.get_register(name) 

139 register.description = description 

140 

141 else: 

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

143 if "mode" not in items: 

144 raise ValueError( 

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

146 '"mode" field' 

147 ) 

148 mode = items["mode"] 

149 register = self._register_list.append_register( 

150 name=name, mode=mode, description=description 

151 ) 

152 

153 self._names_taken.add(name) 

154 

155 if "bit" in items: 

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

157 

158 if "bit_vector" in items: 

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

160 

161 def _parse_register_array(self, name, items): 

162 if name in self._names_taken: 

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

164 raise ValueError(message) 

165 if "array_length" not in items: 

166 message = ( 

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

168 '"array_length" attribute' 

169 ) 

170 raise ValueError(message) 

171 

172 for item_name in items: 

173 if item_name not in self.recognized_register_array_items: 

174 message = ( 

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

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

177 ) 

178 raise ValueError(message) 

179 

180 length = items["array_length"] 

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

182 register_array = self._register_list.append_register_array( 

183 name=name, length=length, description=description 

184 ) 

185 

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

187 # The only required field 

188 if "mode" not in register_items: 

189 message = ( 

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

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

192 ) 

193 raise ValueError(message) 

194 

195 for register_item_name in register_items.keys(): 

196 if register_item_name not in self.recognized_register_items: 

197 message = ( 

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

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

200 ) 

201 raise ValueError(message) 

202 

203 mode = register_items["mode"] 

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

205 

206 register = register_array.append_register( 

207 name=register_name, mode=mode, description=description 

208 ) 

209 

210 if "bit" in register_items: 

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

212 

213 if "bit_vector" in register_items: 

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

215 

216 def _parse_bits(self, register, bit_configurations): 

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

218 for item_name in bit_configuration.keys(): 

219 if item_name not in self.recognized_bit_items: 

220 message = ( 

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

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

223 ) 

224 raise ValueError(message) 

225 

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

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

228 

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

230 

231 def _parse_bit_vectors(self, register, bit_vector_configurations): 

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

233 # The only required field 

234 if "width" not in vector_configuration: 

235 message = ( 

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

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

238 ) 

239 raise ValueError(message) 

240 

241 for item_name in vector_configuration.keys(): 

242 if item_name not in self.recognized_bit_vector_items: 

243 message = ( 

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

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

246 f'Unknown key "{item_name}"' 

247 ) 

248 raise ValueError(message) 

249 

250 width = vector_configuration["width"] 

251 

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

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

254 

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