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