1# 2# Copyright 2017-2019 Advanced Micro Devices, Inc. 3# 4# Permission is hereby granted, free of charge, to any person obtaining a 5# copy of this software and associated documentation files (the "Software"), 6# to deal in the Software without restriction, including without limitation 7# on the rights to use, copy, modify, merge, publish, distribute, sub 8# license, and/or sell copies of the Software, and to permit persons to whom 9# the Software is furnished to do so, subject to the following conditions: 10# 11# The above copyright notice and this permission notice (including the next 12# paragraph) shall be included in all copies or substantial portions of the 13# Software. 14# 15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18# THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, 19# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 21# USE OR OTHER DEALINGS IN THE SOFTWARE. 22# 23""" 24Python package containing common tools for manipulating register JSON. 25""" 26 27from __future__ import absolute_import, division, print_function, unicode_literals 28 29import itertools 30import json 31import re 32import sys 33 34from collections import defaultdict 35from contextlib import contextmanager 36 37class UnionFind(object): 38 """ 39 Simplistic implementation of a union-find data structure that also keeps 40 track of the sets that have been unified. 41 42 - add: add an element to the implied global set of elements 43 - union: unify the sets containing the two given elements 44 - find: return the representative element of the set containing the 45 given element 46 - get_set: get the set containing the given element 47 - sets: iterate over all sets (the sets form a partition of the set of all 48 elements that have ever been added) 49 """ 50 def __init__(self): 51 self.d = {} 52 53 def add(self, k): 54 if k not in self.d: 55 self.d[k] = set([k]) 56 57 def union(self, k1, k2): 58 k1 = self.find(k1) 59 k2 = self.find(k2) 60 if k1 == k2: 61 return 62 if len(k1) < len(k2): 63 k1, k2 = k2, k1 64 self.d[k1].update(self.d[k2]) 65 self.d[k2] = (k1,) 66 67 def find(self, k): 68 e = self.d[k] 69 if isinstance(e, set): 70 return k 71 assert isinstance(e, tuple) 72 r = self.find(e[0]) 73 self.d[k] = (r,) 74 return r 75 76 def get_set(self, k): 77 k = self.find(k) 78 assert isinstance(self.d[k], set) 79 return self.d[k] 80 81 def sets(self): 82 for v in self.d.values(): 83 if isinstance(v, set): 84 yield v 85 86 87class Object(object): 88 """ 89 Convenience helper class that essentially acts as a dictionary for convenient 90 conversion from and to JSON while allowing the use of .field notation 91 instead of subscript notation for member access. 92 """ 93 def __init__(self, **kwargs): 94 for k, v in kwargs.items(): 95 setattr(self, k, v) 96 97 def update(self, **kwargs): 98 for key, value in kwargs.items(): 99 setattr(self, key, value) 100 return self 101 102 def __str__(self): 103 return 'Object(' + ', '.join( 104 '{k}={v}'.format(**locals()) for k, v, in self.__dict__.items() 105 ) + ')' 106 107 @staticmethod 108 def from_json(json, keys=None): 109 if isinstance(json, list): 110 return [Object.from_json(v) for v in json] 111 elif isinstance(json, dict): 112 obj = Object() 113 for k, v in json.items(): 114 if keys is not None and k in keys: 115 v = keys[k](v) 116 else: 117 v = Object.from_json(v) 118 setattr(obj, k, v) 119 return obj 120 else: 121 return json 122 123 @staticmethod 124 def to_json(obj): 125 if isinstance(obj, Object): 126 return dict((k, Object.to_json(v)) for k, v in obj.__dict__.items()) 127 elif isinstance(obj, dict): 128 return dict((k, Object.to_json(v)) for k, v in obj.items()) 129 elif isinstance(obj, list): 130 return [Object.to_json(v) for v in obj] 131 else: 132 return obj 133 134class MergeError(Exception): 135 def __init__(self, msg): 136 super(MergeError, self).__init__(msg) 137 138class RegisterDatabaseError(Exception): 139 def __init__(self, msg): 140 super(RegisterDatabaseError, self).__init__(msg) 141 142@contextmanager 143def merge_scope(name): 144 """ 145 Wrap a merge handling function in a "scope" whose name will be added when 146 propagating MergeErrors. 147 """ 148 try: 149 yield 150 except Exception as e: 151 raise MergeError('{name}: {e}'.format(**locals())) 152 153def merge_dicts(dicts, keys=None, values=None): 154 """ 155 Generic dictionary merging function. 156 157 dicts -- list of (origin, dictionary) pairs to merge 158 keys -- optional dictionary to provide a merge-strategy per key; 159 the merge strategy is a callable which will receive a list of 160 (origin, value) pairs 161 value -- optional function which provides a merge-strategy for values; 162 the merge strategy is a callable which will receive the name of 163 the key and a list of (origin, value) pairs 164 165 The default strategy is to allow merging keys if all origin dictionaries 166 that contain the key have the same value for it. 167 """ 168 ks = set() 169 for _, d in dicts: 170 ks.update(d.keys()) 171 172 result = {} 173 for k in ks: 174 vs = [(o, d[k]) for o, d in dicts if k in d] 175 with merge_scope('Key {k}'.format(**locals())): 176 if keys is not None and k in keys: 177 result[k] = keys[k](vs) 178 elif values is not None: 179 result[k] = values(k, vs) 180 else: 181 base_origin, base = vs[0] 182 for other_origin, other in vs[1:]: 183 if base != other: 184 raise MergeError('{base} (from {base_origin}) != {other} (from {other_origin})'.format(**locals())) 185 result[k] = base 186 return result 187 188def merge_objects(objects, keys=None): 189 """ 190 Like merge_dicts, but applied to instances of Object. 191 """ 192 return Object(**merge_dicts([(origin, obj.__dict__) for origin, obj in objects], keys=keys)) 193 194class RegisterDatabase(object): 195 """ 196 A register database containing: 197 198 - enums: these are lists of named values that can occur in a register field 199 - register types: description of a register type or template as a list of 200 fields 201 - register mappings: named and typed registers mapped at locations in an 202 address space 203 """ 204 def __init__(self): 205 self.__enums = {} 206 self.__register_types = {} 207 self.__register_mappings = [] 208 self.__regmap_by_addr = None 209 self.__chips = None 210 211 def __post_init(self): 212 """ 213 Perform some basic canonicalization: 214 - enum entries are sorted by value 215 - register type fields are sorted by starting bit 216 - __register_mappings is sorted by offset 217 - the chips field of register mappings is sorted 218 219 Lazily computes the set of all chips mentioned by register mappings. 220 """ 221 if self.__regmap_by_addr is not None: 222 return 223 224 for enum in self.__enums.values(): 225 enum.entries.sort(key=lambda entry: entry.value) 226 227 for regtype in self.__register_types.values(): 228 regtype.fields.sort(key=lambda field: field.bits[0]) 229 230 self.__regmap_by_addr = defaultdict(list) 231 self.__chips = set() 232 233 # Merge register mappings using sort order and garbage collect enums 234 # and register types. 235 old_register_mappings = self.__register_mappings 236 old_register_mappings.sort(key=lambda regmap: regmap.map.at) 237 238 self.__register_mappings = [] 239 for regmap in old_register_mappings: 240 addr = (regmap.map.to, regmap.map.at) 241 chips = set(getattr(regmap, 'chips', ['undef'])) 242 type_ref = getattr(regmap, 'type_ref', None) 243 244 self.__chips.update(chips) 245 246 merged = False 247 for other in reversed(self.__register_mappings): 248 if other.name != regmap.name: 249 break 250 251 other_addr = (other.map.to, other.map.at) 252 other_chips = getattr(other, 'chips', ['undef']) 253 other_type_ref = getattr(other, 'type_ref', None) 254 255 if addr == other_addr and\ 256 (type_ref is None or other_type_ref is None or type_ref == other_type_ref): 257 other.chips = sorted(list(chips.union(other_chips))) 258 if type_ref is not None: 259 other.type_ref = type_ref 260 merged = True 261 break 262 263 if merged: 264 continue 265 266 addrmappings = self.__regmap_by_addr[addr] 267 268 for other in addrmappings: 269 other_type_ref = getattr(other, 'type_ref', None) 270 other_chips = getattr(other, 'chips', ['undef']) 271 if type_ref is not None and other_type_ref is not None and \ 272 type_ref != other_type_ref and chips.intersection(other_chips): 273 raise RegisterDatabaseError( 274 'Registers {0} and {1} overlap and have conflicting types'.format( 275 other.name, regmap.name)) 276 277 addrmappings.append(regmap) 278 self.__register_mappings.append(regmap) 279 280 def garbage_collect(self): 281 """ 282 Remove unreferenced enums and register types. 283 """ 284 old_enums = self.__enums 285 old_register_types = self.__register_types 286 287 self.__enums = {} 288 self.__register_types = {} 289 for regmap in self.__register_mappings: 290 if hasattr(regmap, 'type_ref') and regmap.type_ref not in self.__register_types: 291 regtype = old_register_types[regmap.type_ref] 292 self.__register_types[regmap.type_ref] = regtype 293 for field in regtype.fields: 294 if hasattr(field, 'enum_ref') and field.enum_ref not in self.__enums: 295 self.__enums[field.enum_ref] = old_enums[field.enum_ref] 296 297 def __validate_register_type(self, regtype): 298 for field in regtype.fields: 299 if hasattr(field, 'enum_ref') and field.enum_ref not in self.__enums: 300 raise RegisterDatabaseError( 301 'Register type field {0} has unknown enum_ref {1}'.format( 302 field.name, field.enum_ref)) 303 304 def __validate_register_mapping(self, regmap): 305 if hasattr(regmap, 'type_ref') and regmap.type_ref not in self.__register_types: 306 raise RegisterDatabaseError( 307 'Register mapping {0} has unknown type_ref {1}'.format( 308 regmap.name, regmap.type_ref)) 309 310 def __validate(self): 311 for regtype in self.__register_types.values(): 312 self.__validate_register_type(regtype) 313 for regmap in self.__register_mappings: 314 self.__validate_register_mapping(regmap) 315 316 @staticmethod 317 def enum_key(enum): 318 """ 319 Return a key that uniquely describes the signature of the given 320 enum (assuming that it has been canonicalized). Two enums with the 321 same key can be merged. 322 """ 323 return ''.join( 324 ':{0}:{1}'.format(entry.name, entry.value) 325 for entry in enum.entries 326 ) 327 328 def add_enum(self, name, enum): 329 if name in self.__enums: 330 raise RegisterDatabaseError('Duplicate enum ' + name) 331 self.__enums[name] = enum 332 333 @staticmethod 334 def __merge_enums(enums, union=False): 335 def merge_entries(entries_lists): 336 values = defaultdict(list) 337 for origin, enum in entries_lists: 338 for entry in enum: 339 values[entry.value].append((origin, entry)) 340 341 if not union: 342 if any(len(entries) != len(enums) for entries in values.values()): 343 raise RegisterDatabaseError( 344 'Attempting to merge enums with different values') 345 346 return [ 347 merge_objects(entries) 348 for entries in values.values() 349 ] 350 351 return merge_objects( 352 enums, 353 keys={ 354 'entries': merge_entries, 355 } 356 ) 357 358 def merge_enums(self, names, newname, union=False): 359 """ 360 Given a list of enum names, merge them all into one with a new name and 361 update all references. 362 """ 363 if newname not in names and newname in self.__enums: 364 raise RegisterDatabaseError('Enum {0} already exists'.format(newname)) 365 366 newenum = self.__merge_enums( 367 [(name, self.__enums[name]) for name in names], 368 union=union 369 ) 370 371 for name in names: 372 del self.__enums[name] 373 self.__enums[newname] = newenum 374 375 for regtype in self.__register_types.values(): 376 for field in regtype.fields: 377 if getattr(field, 'enum_ref', None) in names: 378 field.enum_ref = newname 379 380 self.__regmap_by_addr = None 381 382 def add_register_type(self, name, regtype): 383 if regtype in self.__register_types: 384 raise RegisterDatabaseError('Duplicate register type ' + name) 385 self.__register_types[name] = regtype 386 self.__validate_register_type(regtype) 387 388 def register_type(self, name): 389 self.__post_init() 390 return self.__register_types[name] 391 392 @staticmethod 393 def __merge_register_types(regtypes, union=False, field_keys={}): 394 def merge_fields(fields_lists): 395 fields = defaultdict(list) 396 for origin, fields_list in fields_lists: 397 for field in fields_list: 398 fields[field.bits[0]].append((origin, field)) 399 400 if not union: 401 if any(len(entries) != len(regtypes) for entries in fields.values()): 402 raise RegisterDatabaseError( 403 'Attempting to merge register types with different fields') 404 405 return [ 406 merge_objects(field, keys=field_keys) 407 for field in fields.values() 408 ] 409 410 with merge_scope('Register types {0}'.format(', '.join(name for name, _ in regtypes))): 411 return merge_objects( 412 regtypes, 413 keys={ 414 'fields': merge_fields, 415 } 416 ) 417 418 def merge_register_types(self, names, newname, union=False): 419 """ 420 Given a list of register type names, merge them all into one with a 421 new name and update all references. 422 """ 423 if newname not in names and newname in self.__register_types: 424 raise RegisterDatabaseError('Register type {0} already exists'.format(newname)) 425 426 newregtype = self.__merge_register_types( 427 [(name, self.__register_types[name]) for name in names], 428 union=union 429 ) 430 431 for name in names: 432 del self.__register_types[name] 433 self.__register_types[newname] = newregtype 434 435 for regmap in self.__register_mappings: 436 if getattr(regmap, 'type_ref', None) in names: 437 regmap.type_ref = newname 438 439 self.__regmap_by_addr = None 440 441 def add_register_mapping(self, regmap): 442 self.__regmap_by_addr = None 443 self.__register_mappings.append(regmap) 444 self.__validate_register_mapping(regmap) 445 446 def remove_register_mappings(self, regmaps_to_remove): 447 self.__post_init() 448 449 regmaps_to_remove = set(regmaps_to_remove) 450 451 regmaps = self.__register_mappings 452 self.__register_mappings = [] 453 for regmap in regmaps: 454 if regmap not in regmaps_to_remove: 455 self.__register_mappings.append(regmap) 456 457 self.__regmap_by_addr = None 458 459 def enum(self, name): 460 """ 461 Return the enum of the given name, if any. 462 """ 463 self.__post_init() 464 return self.__enums.get(name, None) 465 466 def enums(self): 467 """ 468 Yields all (name, enum) pairs. 469 """ 470 self.__post_init() 471 for name, enum in self.__enums.items(): 472 yield (name, enum) 473 474 def fields(self): 475 """ 476 Yields all (register_type, fields) pairs. 477 """ 478 self.__post_init() 479 for regtype in self.__register_types.values(): 480 for field in regtype.fields: 481 yield (regtype, field) 482 483 def register_types(self): 484 """ 485 Yields all (name, register_type) pairs. 486 """ 487 self.__post_init() 488 for name, regtype in self.__register_types.items(): 489 yield (name, regtype) 490 491 def register_mappings_by_name(self, name): 492 """ 493 Return a list of register mappings with the given name. 494 """ 495 self.__post_init() 496 497 begin = 0 498 end = len(self.__register_mappings) 499 while begin < end: 500 middle = (begin + end) // 2 501 if self.__register_mappings[middle].name < name: 502 begin = middle + 1 503 elif name < self.__register_mappings[middle].name: 504 end = middle 505 else: 506 break 507 508 if begin >= end: 509 return [] 510 511 # We now have begin <= mid < end with begin.name <= name, mid.name == name, name < end.name 512 # Narrow down begin and end 513 hi = middle 514 while begin < hi: 515 mid = (begin + hi) // 2 516 if self.__register_mappings[mid].name < name: 517 begin = mid + 1 518 else: 519 hi = mid 520 521 lo = middle + 1 522 while lo < end: 523 mid = (lo + end) // 2 524 if self.__register_mappings[mid].name == name: 525 lo = mid + 1 526 else: 527 end = mid 528 529 return self.__register_mappings[begin:end] 530 531 def register_mappings(self): 532 """ 533 Yields all register mappings. 534 """ 535 self.__post_init() 536 for regmap in self.__register_mappings: 537 yield regmap 538 539 def chips(self): 540 """ 541 Yields all chips. 542 """ 543 self.__post_init() 544 return iter(self.__chips) 545 546 def merge_chips(self, chips, newchip): 547 """ 548 Merge register mappings of the given chips into a single chip of the 549 given name. Recursively merges register types and enums when appropriate. 550 """ 551 self.__post_init() 552 553 chips = set(chips) 554 555 regtypes_merge = UnionFind() 556 enums_merge = UnionFind() 557 558 # Walk register mappings to find register types that should be merged. 559 for idx, regmap in itertools.islice(enumerate(self.__register_mappings), 1, None): 560 if not hasattr(regmap, 'type_ref'): 561 continue 562 if chips.isdisjoint(regmap.chips): 563 continue 564 565 for other in self.__register_mappings[idx-1::-1]: 566 if regmap.name != other.name: 567 break 568 if chips.isdisjoint(other.chips): 569 continue 570 if regmap.map.to != other.map.to or regmap.map.at != other.map.at: 571 raise RegisterDatabaseError( 572 'Attempting to merge chips with incompatible addresses of {0}'.format(regmap.name)) 573 if not hasattr(regmap, 'type_ref'): 574 continue 575 576 if regmap.type_ref != other.type_ref: 577 regtypes_merge.add(regmap.type_ref) 578 regtypes_merge.add(other.type_ref) 579 regtypes_merge.union(regmap.type_ref, other.type_ref) 580 581 # Walk over regtype sets that are to be merged and find enums that 582 # should be merged. 583 for type_refs in regtypes_merge.sets(): 584 fields_merge = defaultdict(set) 585 for type_ref in type_refs: 586 regtype = self.__register_types[type_ref] 587 for field in regtype.fields: 588 if hasattr(field, 'enum_ref'): 589 fields_merge[field.name].add(field.enum_ref) 590 591 for enum_refs in fields_merge.values(): 592 if len(enum_refs) > 1: 593 enum_refs = list(enum_refs) 594 enums_merge.add(enum_refs[0]) 595 for enum_ref in enum_refs[1:]: 596 enums_merge.add(enum_ref) 597 enums_merge.union(enum_ref, enum_refs[0]) 598 599 # Merge all mergeable enum sets 600 remap_enum_refs = {} 601 for enum_refs in enums_merge.sets(): 602 enum_refs = sorted(enum_refs) 603 newname = enum_refs[0] + '_' + newchip 604 i = 0 605 while newname in self.__enums: 606 newname = enum_refs[0] + '_' + newchip + str(i) 607 i += 1 608 609 for enum_ref in enum_refs: 610 remap_enum_refs[enum_ref] = newname 611 612 # Don't use self.merge_enums, because we don't want to automatically 613 # update _all_ references to the merged enums (some may be from 614 # register types that aren't going to be merged). 615 self.add_enum(newname, self.__merge_enums( 616 [(enum_ref, self.__enums[enum_ref]) for enum_ref in enum_refs], 617 union=True 618 )) 619 620 # Merge all mergeable type refs 621 remap_type_refs = {} 622 for type_refs in regtypes_merge.sets(): 623 type_refs = sorted(type_refs) 624 newname = type_refs[0] + '_' + newchip 625 i = 0 626 while newname in self.__enums: 627 newname = type_refs[0] + '_' + newchip + str(i) 628 i += 1 629 630 updated_regtypes = [] 631 for type_ref in type_refs: 632 remap_type_refs[type_ref] = newname 633 634 regtype = Object.from_json(Object.to_json(self.__register_types[type_ref])) 635 for field in regtype.fields: 636 if hasattr(field, 'enum_ref'): 637 field.enum_ref = remap_enum_refs.get(enum_ref, enum_ref) 638 639 updated_regtypes.append(regtype) 640 641 def merge_enum_refs(enum_refs): 642 enum_refs = set( 643 remap_enum_refs.get(enum_ref, enum_ref) 644 for origin, enum_ref in enum_refs 645 ) 646 assert len(enum_refs) == 1 # should be ensured by how we determine the enums to be merged 647 return enum_refs.pop() 648 649 self.add_register_type(newname, self.__merge_register_types( 650 [(type_ref, self.__register_types[type_ref]) for type_ref in type_refs], 651 field_keys={ 652 'enum_ref': merge_enum_refs, 653 }, 654 union=True 655 )) 656 657 # Merge register mappings 658 register_mappings = self.__register_mappings 659 self.__register_mappings = [] 660 661 regmap_accum = None 662 for regmap in register_mappings: 663 if regmap_accum and regmap.name != regmap_accum.name: 664 regmap_accum.chips = [newchip] 665 self.__register_mappings.append(regmap_accum) 666 regmap_accum = None 667 668 joining_chips = chips.intersection(regmap.chips) 669 if not joining_chips: 670 self.__register_mappings.append(regmap) 671 continue 672 remaining_chips = set(regmap.chips).difference(chips) 673 674 type_ref = getattr(regmap, 'type_ref', None) 675 if type_ref is None: 676 regmap.chips = sorted(remaining_chips.union([newchip])) 677 self.__register_mappings.append(regmap) 678 continue 679 680 type_ref = remap_type_refs.get(type_ref, type_ref) 681 if remaining_chips: 682 regmap.chips = sorted(remaining_chips) 683 self.__register_mappings.append(regmap) 684 if not regmap_accum: 685 regmap = Object.from_json(Object.to_json(regmap)) 686 if type_ref is not None: 687 regmap.type_ref = type_ref 688 689 if not regmap_accum: 690 regmap_accum = regmap 691 else: 692 if not hasattr(regmap_accum.type_ref, 'type_ref'): 693 if type_ref is not None: 694 regmap_accum.type_ref = type_ref 695 else: 696 assert type_ref is None or type_ref == regmap_accum.type_ref 697 if regmap_accum: 698 self.__register_mappings.append(regmap_accum) 699 700 def update(self, other): 701 """ 702 Add the contents of the other database to self. 703 704 Doesn't de-duplicate entries. 705 """ 706 self.__post_init() 707 other.__post_init() 708 709 enum_remap = {} 710 regtype_remap = {} 711 712 for regmap in other.__register_mappings: 713 regmap = Object.from_json(Object.to_json(regmap)) 714 715 type_ref = getattr(regmap, 'type_ref', None) 716 if type_ref is not None and type_ref not in regtype_remap: 717 regtype = Object.from_json(Object.to_json(other.__register_types[type_ref])) 718 719 chips = getattr(regmap, 'chips', []) 720 suffix = '_' + chips[0] if chips else '' 721 722 for field in regtype.fields: 723 enum_ref = getattr(field, 'enum_ref', None) 724 if enum_ref is not None and enum_ref not in enum_remap: 725 enum = Object.from_json(Object.to_json(other.__enums[enum_ref])) 726 727 remapped = enum_ref + suffix if enum_ref in self.__enums else enum_ref 728 i = 0 729 while remapped in self.__enums: 730 remapped = enum_ref + suffix + str(i) 731 i += 1 732 self.add_enum(remapped, enum) 733 enum_remap[enum_ref] = remapped 734 735 if enum_ref is not None: 736 field.enum_ref = enum_remap[enum_ref] 737 738 remapped = type_ref + suffix if type_ref in self.__register_types else type_ref 739 i = 0 740 while remapped in self.__register_types: 741 remapped = type_ref + suffix + str(i) 742 i += 1 743 self.add_register_type(remapped, regtype) 744 regtype_remap[type_ref] = remapped 745 746 if type_ref is not None: 747 regmap.type_ref = regtype_remap[type_ref] 748 749 self.add_register_mapping(regmap) 750 751 def to_json(self): 752 self.__post_init() 753 return { 754 'enums': Object.to_json(self.__enums), 755 'register_types': Object.to_json(self.__register_types), 756 'register_mappings': Object.to_json(self.__register_mappings), 757 } 758 759 def encode_json_pretty(self): 760 """ 761 Use a custom JSON encoder which pretty prints, but keeps inner structures compact 762 """ 763 # Since the JSON module isn't very extensible, this ends up being 764 # really hacky. 765 obj = self.to_json() 766 767 replacements = [] 768 def placeholder(s): 769 placeholder = "JSON-{key}-NOSJ".format(key=len(replacements)) 770 replacements.append(json.dumps(s, sort_keys=True)) 771 return placeholder 772 773 # Pre-create non-indented encodings for inner objects 774 for enum in obj['enums'].values(): 775 enum['entries'] = [ 776 placeholder(entry) 777 for entry in enum['entries'] 778 ] 779 780 for regtype in obj['register_types'].values(): 781 regtype['fields'] = [ 782 placeholder(field) 783 for field in regtype['fields'] 784 ] 785 786 for regmap in obj['register_mappings']: 787 regmap['map'] = placeholder(regmap['map']) 788 if 'chips' in regmap: 789 regmap['chips'] = placeholder(regmap['chips']) 790 791 # Now create the 'outer' encoding with indentation and search-and-replace 792 # placeholders 793 result = json.dumps(obj, indent=1, sort_keys=True) 794 795 result = re.sub( 796 '"JSON-([0-9]+)-NOSJ"', 797 lambda m: replacements[int(m.group(1))], 798 result 799 ) 800 801 return result 802 803 @staticmethod 804 def from_json(json): 805 db = RegisterDatabase() 806 807 db.__enums = dict((k, Object.from_json(v)) for k, v in json['enums'].items()) 808 if 'register_types' in json: 809 db.__register_types = dict( 810 (k, Object.from_json(v)) 811 for k, v in json['register_types'].items() 812 ) 813 if 'register_mappings' in json: 814 db.__register_mappings = Object.from_json(json['register_mappings']) 815 816 # Old format 817 if 'registers' in json: 818 for reg in json['registers']: 819 type_ref = None 820 if 'fields' in reg and reg['fields']: 821 type_ref = reg['names'][0] 822 db.add_register_type(type_ref, Object( 823 fields=Object.from_json(reg['fields']) 824 )) 825 826 for name in reg['names']: 827 regmap = Object( 828 name=name, 829 map=Object.from_json(reg['map']) 830 ) 831 if type_ref is not None: 832 regmap.type_ref = type_ref 833 db.add_register_mapping(regmap) 834 835 db.__post_init() 836 return db 837 838def deduplicate_enums(regdb): 839 """ 840 Find enums that have the exact same entries and merge them. 841 """ 842 buckets = defaultdict(list) 843 for name, enum in regdb.enums(): 844 buckets[RegisterDatabase.enum_key(enum)].append(name) 845 846 for bucket in buckets.values(): 847 if len(bucket) > 1: 848 regdb.merge_enums(bucket, bucket[0]) 849 850def deduplicate_register_types(regdb): 851 """ 852 Find register types with the exact same fields (identified by name and 853 bit range) and merge them. 854 855 However, register types *aren't* merged if they have different enums for 856 the same field (as an exception, if one of them has an enum and the other 857 one doesn't, we assume that one is simply missing a bit of information and 858 merge the register types). 859 """ 860 buckets = defaultdict(list) 861 for name, regtype in regdb.register_types(): 862 key = ''.join( 863 ':{0}:{1}:{2}:'.format( 864 field.name, field.bits[0], field.bits[1], 865 ) 866 for field in regtype.fields 867 ) 868 buckets[key].append((name, regtype.fields)) 869 870 for bucket in buckets.values(): 871 # Register types in the same bucket have the same fields in the same 872 # places, but they may have different enum_refs. Allow merging when 873 # one has an enum_ref and another doesn't, but don't merge if they 874 # have enum_refs that differ. 875 bucket_enum_refs = [ 876 [getattr(field, 'enum_ref', None) for field in fields] 877 for name, fields in bucket 878 ] 879 while bucket: 880 regtypes = [bucket[0][0]] 881 enum_refs = bucket_enum_refs[0] 882 del bucket[0] 883 del bucket_enum_refs[0] 884 885 idx = 0 886 while idx < len(bucket): 887 if all([ 888 not lhs or not rhs or lhs == rhs 889 for lhs, rhs in zip(enum_refs, bucket_enum_refs[idx]) 890 ]): 891 regtypes.append(bucket[idx][0]) 892 enum_refs = [lhs or rhs for lhs, rhs in zip(enum_refs, bucket_enum_refs[idx])] 893 del bucket[idx] 894 del bucket_enum_refs[idx] 895 else: 896 idx += 1 897 898 if len(regtypes) > 1: 899 regdb.merge_register_types(regtypes, regtypes[0]) 900 901# kate: space-indent on; indent-width 4; replace-tabs on; 902