[dvsim] Add support for second-level indirection

This adds support for a second level of indirection with the key-value
pairs in the HJson.

For example: lets say we supply the dict:
`{var: '{{foo}_xyz{bar}}', foo: p, bar: q, p_xyz_q: baz}`

Then after the substitutions above: `{var: '{p_xyz_q}', ...}`
We need to now substitute {p_xyz_q}, so that the final value of
`{var: 'baz', ...}`.

We will use this mechanism to set compile time values to keys that need
to be differentiated for each build. An example of where this will be
required to be done is setting different coverage collection hierarchy
files to different builds for the same DUT. In a subsequent PR, CSR
tests will have its own build and the coverage collected on those tests
will be significantly reduced. That PR will make this change probably a
bit clearer.

Signed-off-by: Srikrishna Iyer <sriyer@google.com>
diff --git a/util/dvsim/Deploy.py b/util/dvsim/Deploy.py
index b0dde6a..4b90626 100644
--- a/util/dvsim/Deploy.py
+++ b/util/dvsim/Deploy.py
@@ -129,8 +129,14 @@
                 sys.exit(1)
 
         # Recursively search and replace wildcards
-        self.__dict__ = find_and_substitute_wildcards(self.__dict__,
-                                                      self.__dict__)
+        # First pass: search within self dict. We ignore errors since some
+        # substitions may be available in the second pass.
+        self.__dict__ = find_and_substitute_wildcards(
+            self.__dict__, self.__dict__, [], True)
+
+        # Second pass: search in sim_cfg dict, this time not ignoring errors.
+        self.__dict__ = find_and_substitute_wildcards(
+            self.__dict__, self.sim_cfg.__dict__, [], False)
 
         # Set identifier.
         self.identifier = self.sim_cfg.name + ":" + self.name
@@ -785,10 +791,19 @@
         for bld in self.sim_cfg.builds:
             self.cov_db_dirs += bld.cov_db_dir + " "
 
-        # Recursively search and replace wildcards, ignoring cov_db_dirs for now.
+        # Recursively search and replace wildcards, ignoring cov_db_dirs.
         # We need to resolve it later based on cov_db_dirs value set below.
+
+        # First pass: search within self dict. We ignore errors since some
+        # substitions may be available in the second pass.
         self.__dict__ = find_and_substitute_wildcards(
-            self.__dict__, self.__dict__, ignored_wildcards=["cov_db_dirs"])
+            self.__dict__, self.__dict__, ignored_wildcards=["cov_db_dirs"],
+            ignore_error=True)
+
+        # Second pass: search in sim_cfg dict, this time not ignoring errors.
+        self.__dict__ = find_and_substitute_wildcards(
+            self.__dict__, self.sim_cfg.__dict__,
+            ignored_wildcards=["cov_db_dirs"], ignore_error=False)
 
         # Prune previous merged cov directories.
         prev_cov_db_dirs = self.odir_limiter(odir=self.cov_merge_db_dir)
diff --git a/util/dvsim/utils.py b/util/dvsim/utils.py
index 769a265..c8141a7 100644
--- a/util/dvsim/utils.py
+++ b/util/dvsim/utils.py
@@ -108,9 +108,12 @@
     else:
         match = re.findall(r"{([A-Za-z0-9\_]+)}", var)
         if len(match) > 0:
-            subst_list = {}
+            ignored_wildcards_found = False
+            no_substitutions_found = True
             for item in match:
-                if item not in ignored_wildcards:
+                if item in ignored_wildcards:
+                    ignored_wildcards_found = True
+                else:
                     log.debug("Found wildcard \"%s\" in \"%s\"", item, var)
                     found = subst(item, mdict)
                     if found is not None:
@@ -132,19 +135,34 @@
 
                         elif type(found) is bool:
                             found = int(found)
-                        subst_list[item] = found
+                        var = var.replace("{" + item + "}", str(found))
+                        no_substitutions_found = False
                     else:
                         # Check if the wildcard exists as an environment variable
                         env_var = os.environ.get(item)
                         if env_var is not None:
-                            subst_list[item] = env_var
+                            var = var.replace("{" + item + "}", str(env_var))
+                            no_substitutions_found = False
                         elif not ignore_error:
                             log.error(
                                 "Substitution for the wildcard \"%s\" not found",
                                 item)
                             sys.exit(1)
-            for item in subst_list:
-                var = var.replace("{" + item + "}", str(subst_list[item]))
+
+            # If items were found for substitution, but if they were a part
+            # of ignored_wildcards list or if substitutions for them were not
+            # found, then, we return. If all substitutions were made, then check
+            # for second level of indirection:
+            #
+            # For example: lets say we supply the dict:
+            # {var: '{{foo}_xyz{bar}}', foo: p, bar: q, p_xyz_q: baz}
+            #
+            # Then after the substitutions above: {var: '{p_xyz_q}', ...}
+            # We need to now substitute {p_xyz_q}, so that the final value of
+            # var is 'baz'.
+            if not (ignored_wildcards_found or no_substitutions_found):
+                var =  subst_wildcards(var, mdict, ignored_wildcards,
+                                       ignore_error)
     return var