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
« 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# --------------------------------------------------------------------------------------------------
10# Standard libraries
11import copy
13# Third party libraries
14import tomli
15from tsfpga.system_utils import read_file
17# Local folder libraries
18from .constant import Constant
19from .register_list import RegisterList
22def load_toml_file(toml_file):
23 if not toml_file.exists():
24 raise FileNotFoundError(f"Requested TOML file does not exist: {toml_file}")
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
34def from_toml(module_name, toml_file, default_registers=None):
35 """
36 Parse a register TOML file.
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.
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)
53 return parser.parse(toml_data)
56class RegisterParser:
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"}
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
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)
77 self._names_taken = set()
79 def parse(self, register_data):
80 """
81 Parse the TOML data.
83 Arguments:
84 register_data (str): TOML register data.
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)
93 if "register" in register_data:
94 for name, items in register_data["register"].items():
95 self._parse_plain_register(name, items)
97 if "register_array" in register_data:
98 for name, items in register_data["register_array"].items():
99 self._parse_register_array(name, items)
101 return self._register_list
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)
111 description = items.get("description", "")
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)
121 constant = Constant(name=name, value=items["value"], description=description)
123 self._register_list.constants.append(constant)
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)
134 description = items.get("description", "")
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)
147 register = self._register_list.get_register(name)
148 register.description = description
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 )
162 self._names_taken.add(name)
164 if "bit" in items:
165 self._parse_bits(register, items["bit"])
167 if "bit_vector" in items:
168 self._parse_bit_vectors(register, items["bit_vector"])
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)
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)
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 )
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)
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)
212 mode = register_items["mode"]
213 description = register_items.get("description", "")
215 register = register_array.append_register(
216 name=register_name, mode=mode, description=description
217 )
219 if "bit" in register_items:
220 self._parse_bits(register, register_items["bit"])
222 if "bit_vector" in register_items:
223 self._parse_bit_vectors(register, register_items["bit_vector"])
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)
235 description = bit_configuration.get("description", "")
236 default_value = bit_configuration.get("default_value", "0")
238 register.append_bit(name=bit_name, description=description, default_value=default_value)
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)
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)
259 width = vector_configuration["width"]
261 description = vector_configuration.get("description", "")
262 default_value = vector_configuration.get("default_value", "0" * width)
264 register.append_bit_vector(vector_name, description, width, default_value)