[topgen] Use OrderedDict for top, xbar objects

This is related to PR #1552.

Problem:

    In Azure Pipeline that uses Python 3.5, doesn't keep the order of
    hjson keys. It results the generated hjson has a lot of change
    randomly.

`dict` type in python is unordered dictionary. It doesn't maintain the
order of keys in the dictionary. `topgen` dumps a couple of hjson files,
such as generated tops (e.g.
`hw/top_earlgrey_data/autogen/top_earlgrey.gen.hjson`) and the crossbar
configurations (e.g.
`$TOP/ip/xbar_{name}/data/autogen/xbar_{name}.hjson`). This unordered
behavior creates random changes to the generated files, which increases
commit diffs.

From python3.7, the default dictionary type is changed to
`collections.OrderedDict`. So, if the `topgen` runs on the system >=
python3.7, it always maintain the order.

Resolution:

    Use OrderedDict when create dictionary variables.

With OrderedDict (introduced in Python2.7), the generated hjson files
are remains in order.

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/util/topgen.py b/util/topgen.py
index 0efe46a..dab1da0 100755
--- a/util/topgen.py
+++ b/util/topgen.py
@@ -9,6 +9,7 @@
 import sys
 from io import StringIO
 from pathlib import Path
+from collections import OrderedDict
 
 import hjson
 from mako.template import Template
@@ -86,6 +87,7 @@
         # generate testbench for xbar
         tlgen.generate_tb(xbar, dv_path)
 
+
 def generate_alert_handler(top, out_path):
     # default values
     esc_cnt_dw = 32
@@ -231,7 +233,7 @@
     # TODO: More secure way to gneerate RTL
     hjson_obj = hjson.loads(out,
                             use_decimal=True,
-                            object_pairs_hook=validate.checking_dict)
+                            object_pairs_hook=OrderedDict)
     validate.validate(hjson_obj)
     gen_rtl.gen_rtl(hjson_obj, str(rtl_path))
 
@@ -465,7 +467,9 @@
         # load top configuration
         try:
             with open(args.topcfg, 'r') as ftop:
-                topcfg = hjson.load(ftop, use_decimal=True)
+                topcfg = hjson.load(ftop,
+                                    use_decimal=True,
+                                    object_pairs_hook=OrderedDict)
         except ValueError:
             raise SystemExit(sys.exc_info()[1])
 
@@ -500,7 +504,7 @@
 
                 obj = hjson.load(x.open('r'),
                                  use_decimal=True,
-                                 object_pairs_hook=validate.checking_dict)
+                                 object_pairs_hook=OrderedDict)
                 if validate.validate(obj) != 0:
                     log.info("Parsing IP %s configuration failed. Skip" % x)
                     continue
@@ -541,7 +545,9 @@
         # load top.complete configuration
         try:
             with open(args.topcfg, 'r') as ftop:
-                completecfg = hjson.load(ftop, use_decimal=True)
+                completecfg = hjson.load(ftop,
+                                         use_decimal=True,
+                                         object_pairs_hook=OrderedDict)
         except ValueError:
             raise SystemExit(sys.exc_info()[1])
 
diff --git a/util/topgen/intermodule.py b/util/topgen/intermodule.py
index ad73c21..334b9db 100644
--- a/util/topgen/intermodule.py
+++ b/util/topgen/intermodule.py
@@ -4,6 +4,7 @@
 
 import logging as log
 from typing import Dict
+from collections import OrderedDict
 
 from .lib import *
 
@@ -39,7 +40,7 @@
     return result
 
 
-def elab_intermodule(topcfg):
+def elab_intermodule(topcfg: OrderedDict):
     """Check the connection of inter-module and categorize them
 
     In the top template, it uses updated inter_module fields to create
@@ -51,7 +52,7 @@
     list_of_intersignals = []
 
     if "inter_signal" not in topcfg:
-        topcfg["inter_signal"] = {}
+        topcfg["inter_signal"] = OrderedDict()
 
     # Gather the inter_signal_list
     instances = topcfg["module"] + topcfg["memory"]
@@ -105,12 +106,11 @@
                 assert "package" in rsp_struct, "Either req/rsp shall have 'package' field"
                 package = rsp_struct["package"]
 
-            definitions.append({
-                'package': package,
-                'struct': req_struct["struct"],
-                'signame': sig_name,
-                'type': req_struct["type"]
-            })
+            definitions.append(
+                OrderedDict([('package', package),
+                             ('struct', req_struct["struct"]),
+                             ('signame', sig_name),
+                             ('type', req_struct["type"])]))
 
             if rsp_len != 1:
                 log.warning("{req}[{i}] -> {rsp}".format(req=req, i=i,
diff --git a/util/topgen/lib.py b/util/topgen/lib.py
index e5ed163..598482e 100644
--- a/util/topgen/lib.py
+++ b/util/topgen/lib.py
@@ -5,6 +5,7 @@
 import logging as log
 from copy import deepcopy
 from pathlib import Path
+from collections import OrderedDict
 import hjson
 
 import re
@@ -49,7 +50,11 @@
     """
     p = xbar_path.glob('*.hjson')
     try:
-        xbar_objs = [hjson.load(x.open('r'), use_decimal=True) for x in p]
+        xbar_objs = [
+            hjson.load(x.open('r'),
+                       use_decimal=True,
+                       object_pairs_hook=OrderedDict) for x in p
+        ]
     except ValueError:
         raise Systemexit(sys.exc_info()[1])
 
@@ -160,7 +165,7 @@
     width = first - last + 1
 
     for p in range(first, last + 1):
-        pads.append({"name": pad, "index": p})
+        pads.append(OrderedDict([("name", pad), ("index", p)]))
 
     return pads
 
diff --git a/util/topgen/merge.py b/util/topgen/merge.py
index 62e96ec..0c3d01e 100644
--- a/util/topgen/merge.py
+++ b/util/topgen/merge.py
@@ -5,6 +5,7 @@
 import logging as log
 from copy import deepcopy
 from functools import partial
+from collections import OrderedDict
 
 from .lib import *
 from .intermodule import elab_intermodule
@@ -245,10 +246,10 @@
                         "clock": xbar['clock'],
                         "reset": xbar['reset'],
                         "inst_type": predefined_modules["debug_mem"],
-                        "addr_range": [{
-                            "base_addr": top["debug_mem_base_addr"],
-                            "size_byte": "0x1000",
-                        }],
+                        "addr_range": [OrderedDict([
+                            ("base_addr", top["debug_mem_base_addr"]),
+                            ("size_byte", "0x1000"),
+                        ])],
                         "xbar": False,
                         "pipeline" : "true",
                         "pipeline_byp" : "true"
@@ -257,12 +258,10 @@
                     # Update if exists
                     node = nodeobj[0]
                     node["inst_type"] = predefined_modules["debug_mem"]
-                    node["addr_range"] = [{
-                        "base_addr":
-                        top["debug_mem_base_addr"],
-                        "size_byte":
-                        "0x1000"
-                    }]
+                    node["addr_range"] = [
+                        OrderedDict([("base_addr", top["debug_mem_base_addr"]),
+                                     ("size_byte", "0x1000")])
+                    ]
                     node["xbar"] = False
                     process_pipeline_var(node)
             else:
@@ -285,8 +284,8 @@
             "clock" : deviceobj[0]["clock"],
             "reset" : deviceobj[0]["reset"],
             "inst_type" : deviceobj[0]["type"],
-            "addr_range": [{"base_addr" : deviceobj[0]["base_addr"],
-                            "size_byte": deviceobj[0]["size"]}],
+            "addr_range": [OrderedDict([("base_addr", deviceobj[0]["base_addr"]),
+                            ("size_byte", deviceobj[0]["size"])])],
             "pipeline" : "true",
             "pipeline_byp" : "true",
             "xbar" : True if device in xbar_list else False
@@ -296,10 +295,10 @@
         # found and exist in the nodes too
         node = nodeobj[0]
         node["inst_type"] = deviceobj[0]["type"]
-        node["addr_range"] = [{
-            "base_addr": deviceobj[0]["base_addr"],
-            "size_byte": deviceobj[0]["size"]
-        }]
+        node["addr_range"] = [
+            OrderedDict([("base_addr", deviceobj[0]["base_addr"]),
+                         ("size_byte", deviceobj[0]["size"])])
+        ]
         node["xbar"] = True if device in xbar_list else False
         process_pipeline_var(node)
 
@@ -580,8 +579,9 @@
                 format(e))
 
 
-def merge_top(topcfg, ipobjs, xbarobjs):
-    gencfg = deepcopy(topcfg)
+def merge_top(topcfg: OrderedDict, ipobjs: OrderedDict,
+              xbarobjs: OrderedDict) -> OrderedDict:
+    gencfg = topcfg
 
     # Combine ip cfg into topcfg
     for ip in ipobjs:
diff --git a/util/topgen/validate.py b/util/topgen/validate.py
index 2e1f447..b9947a0 100644
--- a/util/topgen/validate.py
+++ b/util/topgen/validate.py
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: Apache-2.0
 import logging as log
 from enum import Enum
+from collections import OrderedDict
 
 from reggen.validate import check_keys, val_types
 
@@ -50,7 +51,8 @@
 top_optional = {
     'interrupt_modules': ['l', 'list of the modules that connects to rv_plic'],
     'interrupt': ['lnw', 'interrupts (generated)'],
-    'alert_modules': ['l', 'list of the modules that connects to alert_handler'],
+    'alert_modules':
+    ['l', 'list of the modules that connects to alert_handler'],
     'alert': ['lnw', 'alerts (generated)'],
     'alert_async': ['l', 'async alerts (generated)'],
     'pinmux': ['g', 'pinmux definition if doesn\'t exist, tool uses defaults'],
@@ -111,7 +113,7 @@
 # If it does, return a dictionary of instance names to index in ip/xbarobjs
 def check_target(top, objs, tgtobj):
     error = 0
-    idxs = {}
+    idxs = OrderedDict()
 
     for i in range(len(objs)):
         log.info("%d Order is %s" % (i, objs[i]['name'].lower()))
@@ -327,7 +329,7 @@
     if not "pinmux" in top:
         log.warning("Top {} has no 'pinmux' field. Please consider specifying \
                         pinmux and pads configuration")
-        top["pinmux"] = {}
+        top["pinmux"] = OrderedDict()
     # checking pinmux after pads as dio connects to PAD
 
     error += check_pinmux(top, component)