amd/registers: scripts for processing register descriptions in JSON
[mesa.git] / src / amd / registers / makeregheader.py
1 from __future__ import absolute_import, division, print_function, unicode_literals
2
3 COPYRIGHT = '''
4 /*
5 * Copyright 2015-2019 Advanced Micro Devices, Inc.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * on the rights to use, copy, modify, merge, publish, distribute, sub
11 * license, and/or sell copies of the Software, and to permit persons to whom
12 * the Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the next
15 * paragraph) shall be included in all copies or substantial portions of the
16 * Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24 * USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 */
27 '''
28 """
29 Create the (combined) register header from register JSON. Use --help for usage.
30 """
31
32 import argparse
33 from collections import defaultdict
34 import itertools
35 import json
36 import re
37 import sys
38
39 from regdb import Object, RegisterDatabase, deduplicate_enums, deduplicate_register_types
40
41
42 ######### BEGIN HARDCODED CONFIGURATION
43
44 # Chips are sorted chronologically
45 CHIPS = [
46 Object(name='si', disambiguation='GFX6'),
47 Object(name='cik', disambiguation='GFX6'),
48 Object(name='vi', disambiguation='GFX6'),
49 Object(name='fiji', disambiguation='GFX6'),
50 Object(name='stoney', disambiguation='GFX6'),
51 Object(name='gfx9', disambiguation='GFX9'),
52 ]
53
54 ######### END HARDCODED CONFIGURATION
55
56 def get_chip_index(chip):
57 """
58 Given a chip name, return its index in the global CHIPS list.
59 """
60 return next(idx for idx, obj in enumerate(CHIPS) if obj.name == chip)
61
62 def get_disambiguation_suffix(chips):
63 """
64 Disambiguation suffix to be used for an enum entry or field name that
65 is supported in the given set of chips.
66 """
67 oldest_chip_index = min([get_chip_index(chip) for chip in chips])
68 return CHIPS[oldest_chip_index].disambiguation
69
70 def get_chips_comment(chips, parent=None):
71 """
72 Generate a user-friendly comment describing the given set of chips.
73
74 The return value may be None, if such a comment is deemed unnecessary.
75
76 parent is an optional set of chips supporting a parent structure, e.g.
77 where chips may be the set of chips supporting a specific enum value,
78 parent would be the set of chips supporting the field containing the enum,
79 the idea being that no comment is necessary if all chips that support the
80 parent also support the child.
81 """
82 chipflags = [chip.name in chips for chip in CHIPS]
83 if all(chipflags):
84 return None
85
86 if parent is not None:
87 parentflags = [chip.name in parent for chip in CHIPS]
88 if all(childflag or not parentflag for childflag, parentflag in zip(chipflags, parentflags)):
89 return None
90
91 prefix = 0
92 for idx, chip, flag in zip(itertools.count(), CHIPS, chipflags):
93 if not flag:
94 break
95 prefix = idx + 1
96
97 suffix = len(CHIPS)
98 for idx, chip, flag in zip(itertools.count(), reversed(CHIPS), reversed(chipflags)):
99 if not flag:
100 break
101 suffix = len(CHIPS) - idx - 1
102
103 comment = []
104 if prefix > 0:
105 comment.append('<= {0}'.format(CHIPS[prefix - 1].name))
106 for chip, flag in zip(CHIPS[prefix:suffix], chipflags[prefix:suffix]):
107 if flag:
108 comment.append(chip.name)
109 if suffix < len(CHIPS):
110 comment.append('>= {0}'.format(CHIPS[suffix].name))
111
112 return ', '.join(comment)
113
114
115 class HeaderWriter(object):
116 def __init__(self, regdb, guard=None):
117 self.guard = guard
118
119 # The following contain: Object(address, chips, name, regmap/field/enumentry)
120 self.register_lines = []
121 self.field_lines = []
122 self.value_lines = []
123
124 regtype_emit = defaultdict(set)
125 enum_emit = defaultdict(set)
126
127 for regmap in regdb.register_mappings():
128 type_ref = getattr(regmap, 'type_ref', None)
129 self.register_lines.append(Object(
130 address=regmap.map.at,
131 chips=set(regmap.chips),
132 name=regmap.name,
133 regmap=regmap,
134 type_refs=set([type_ref]) if type_ref else set(),
135 ))
136
137 basename = re.sub(r'[0-9]+', '', regmap.name)
138 key = '{type_ref}::{basename}'.format(**locals())
139 if type_ref is not None and regtype_emit[key].isdisjoint(regmap.chips):
140 regtype_emit[key].update(regmap.chips)
141
142 regtype = regdb.register_type(type_ref)
143 for field in regtype.fields:
144 if field.name == 'RESERVED':
145 continue
146
147 enum_ref = getattr(field, 'enum_ref', None)
148 self.field_lines.append(Object(
149 address=regmap.map.at,
150 chips=set(regmap.chips),
151 name=field.name,
152 field=field,
153 bits=field.bits[:],
154 type_refs=set([type_ref]) if type_ref else set(),
155 enum_refs=set([enum_ref]) if enum_ref else set(),
156 ))
157
158 key = '{type_ref}::{basename}::{enum_ref}'.format(**locals())
159 if enum_ref is not None and enum_emit[key].isdisjoint(regmap.chips):
160 enum_emit[key].update(regmap.chips)
161
162 enum = regdb.enum(enum_ref)
163 for entry in enum.entries:
164 self.value_lines.append(Object(
165 address=regmap.map.at,
166 chips=set(regmap.chips),
167 name=entry.name,
168 enumentry=entry,
169 enum_refs=set([enum_ref]) if enum_ref else set(),
170 ))
171
172 # Merge register lines
173 lines = self.register_lines
174 lines.sort(key=lambda line: (line.address, line.name))
175
176 self.register_lines = []
177 for line in lines:
178 prev = self.register_lines[-1] if self.register_lines else None
179 if prev and prev.address == line.address and prev.name == line.name:
180 prev.chips.update(line.chips)
181 prev.type_refs.update(line.type_refs)
182 continue
183 self.register_lines.append(line)
184
185 # Merge field lines
186 lines = self.field_lines
187 lines.sort(key=lambda line: (line.address, line.name))
188
189 self.field_lines = []
190 for line in lines:
191 merged = False
192 for prev in reversed(self.field_lines):
193 if prev.address != line.address or prev.name != line.name:
194 break
195
196 # Can merge fields if they have the same starting bit and the
197 # range of the field as intended by the current line does not
198 # conflict with any of the regtypes covered by prev.
199 if prev.bits[0] != line.bits[0]:
200 continue
201
202 if prev.bits[1] < line.bits[1]:
203 # Current line's field extends beyond the range of prev.
204 # Need to check for conflicts
205 conflict = False
206 for type_ref in prev.type_refs:
207 for field in regdb.register_type(type_ref).fields:
208 # The only possible conflict is for a prev field
209 # that starts at a higher bit.
210 if (field.bits[0] > line.bits[0] and
211 field.bits[0] <= line.bits[1]):
212 conflict = True
213 break
214 if conflict:
215 break
216 if conflict:
217 continue
218
219 prev.bits[1] = max(prev.bits[1], line.bits[1])
220 prev.chips.update(line.chips)
221 prev.type_refs.update(line.type_refs)
222 prev.enum_refs.update(line.enum_refs)
223 merged = True
224 break
225 if not merged:
226 self.field_lines.append(line)
227
228 # Merge value lines
229 lines = self.value_lines
230 lines.sort(key=lambda line: (line.address, line.name))
231
232 self.value_lines = []
233 for line in lines:
234 for prev in reversed(self.value_lines):
235 if prev.address == line.address and prev.name == line.name and\
236 prev.enumentry.value == line.enumentry.value:
237 prev.chips.update(line.chips)
238 prev.enum_refs.update(line.enum_refs)
239 break
240 else:
241 self.value_lines.append(line)
242
243 # Disambiguate field and value lines
244 for idx, line in enumerate(self.field_lines):
245 prev = self.field_lines[idx - 1] if idx > 0 else None
246 next = self.field_lines[idx + 1] if idx + 1 < len(self.field_lines) else None
247 if (prev and prev.address == line.address and prev.field.name == line.field.name) or\
248 (next and next.address == line.address and next.field.name == line.field.name):
249 line.name += '_' + get_disambiguation_suffix(line.chips)
250
251 for idx, line in enumerate(self.value_lines):
252 prev = self.value_lines[idx - 1] if idx > 0 else None
253 next = self.value_lines[idx + 1] if idx + 1 < len(self.value_lines) else None
254 if (prev and prev.address == line.address and prev.enumentry.name == line.enumentry.name) or\
255 (next and next.address == line.address and next.enumentry.name == line.enumentry.name):
256 line.name += '_' + get_disambiguation_suffix(line.chips)
257
258 def print(self, filp, sort='address'):
259 """
260 Print out the entire register header.
261 """
262 if sort == 'address':
263 self.register_lines.sort(key=lambda line: (line.address, line.name))
264 else:
265 assert sort == 'name'
266 self.register_lines.sort(key=lambda line: (line.name, line.address))
267
268 # Collect and sort field lines by address
269 field_lines_by_address = defaultdict(list)
270 for line in self.field_lines:
271 field_lines_by_address[line.address].append(line)
272 for field_lines in field_lines_by_address.values():
273 if sort == 'address':
274 field_lines.sort(key=lambda line: (line.bits[0], line.name))
275 else:
276 field_lines.sort(key=lambda line: (line.name, line.bits[0]))
277
278 # Collect and sort value lines by address
279 value_lines_by_address = defaultdict(list)
280 for line in self.value_lines:
281 value_lines_by_address[line.address].append(line)
282 for value_lines in value_lines_by_address.values():
283 if sort == 'address':
284 value_lines.sort(key=lambda line: (line.enumentry.value, line.name))
285 else:
286 value_lines.sort(key=lambda line: (line.name, line.enumentry.value))
287
288 print('/* Automatically generated by amd/registers/makeregheader.py */\n', file=filp)
289 print(file=filp)
290 print(COPYRIGHT.strip(), file=filp)
291 print(file=filp)
292
293 if self.guard:
294 print('#ifndef {self.guard}'.format(**locals()), file=filp)
295 print('#define {self.guard}\n'.format(**locals()), file=filp)
296
297 for register_line in self.register_lines:
298 comment = get_chips_comment(register_line.chips)
299
300 address = '{0:X}'.format(register_line.address)
301 address = address.rjust(3 if register_line.regmap.map.to == 'pkt3' else 6, '0')
302
303 define_name = 'R_{address}_{register_line.name}'.format(**locals()).ljust(63)
304 comment = ' /* {0} */'.format(comment) if comment else ''
305 print('#define {define_name} 0x{address}{comment}'.format(**locals()), file=filp)
306
307 field_lines = field_lines_by_address[register_line.address]
308 field_idx = 0
309 while field_idx < len(field_lines):
310 field_line = field_lines[field_idx]
311
312 if field_line.type_refs.isdisjoint(register_line.type_refs):
313 field_idx += 1
314 continue
315 del field_lines[field_idx]
316
317 comment = get_chips_comment(field_line.chips, register_line.chips)
318
319 mask = (1 << (field_line.bits[1] - field_line.bits[0] + 1)) - 1
320 define_name = '_{address}_{field_line.name}(x)'.format(**locals()).ljust(58)
321 comment = ' /* {0} */'.format(comment) if comment else ''
322 print(
323 '#define S{define_name} (((unsigned)(x) & 0x{mask:X}) << {field_line.bits[0]}){comment}'
324 .format(**locals()), file=filp)
325 print('#define G{define_name} (((x) >> {field_line.bits[0]}) & 0x{mask:X})'
326 .format(**locals()), file=filp)
327
328 complement = ((1 << 32) - 1) ^ (mask << field_line.bits[0])
329 define_name = '_{address}_{field_line.name}'.format(**locals()).ljust(58)
330 print('#define C{define_name} 0x{complement:08X}'
331 .format(**locals()), file=filp)
332
333 value_lines = value_lines_by_address[register_line.address]
334 value_idx = 0
335 while value_idx < len(value_lines):
336 value_line = value_lines[value_idx]
337
338 if value_line.enum_refs.isdisjoint(field_line.enum_refs):
339 value_idx += 1
340 continue
341 del value_lines[value_idx]
342
343 comment = get_chips_comment(value_line.chips, field_line.chips)
344
345 define_name = 'V_{address}_{value_line.name}'.format(**locals()).ljust(55)
346 comment = ' /* {0} */'.format(comment) if comment else ''
347 print('#define {define_name} {value_line.enumentry.value}{comment}'
348 .format(**locals()), file=filp)
349
350 if self.guard:
351 print('\n#endif // {self.guard}'.format(**locals()), file=filp)
352
353
354 def main():
355 parser = argparse.ArgumentParser()
356 parser.add_argument('--chip', dest='chips', type=str, nargs='*',
357 help='Chip for which to generate the header (all chips if unspecified)')
358 parser.add_argument('--sort', choices=['name', 'address'], default='address',
359 help='Sort key for registers, fields, and enum values')
360 parser.add_argument('--guard', type=str, help='Name of the #include guard')
361 parser.add_argument('files', metavar='FILE', type=str, nargs='+',
362 help='Register database file')
363 args = parser.parse_args()
364
365 regdb = None
366 for filename in args.files:
367 with open(filename, 'r') as filp:
368 db = RegisterDatabase.from_json(json.load(filp))
369 if regdb is None:
370 regdb = db
371 else:
372 regdb.update(db)
373
374 deduplicate_enums(regdb)
375 deduplicate_register_types(regdb)
376
377 w = HeaderWriter(regdb, guard=args.guard)
378 w.print(sys.stdout, sort=args.sort)
379
380
381 if __name__ == '__main__':
382 main()
383
384 # kate: space-indent on; indent-width 4; replace-tabs on;