[ipgen] Support object template parameters

Some ipgen templates require complex data structures as template
parameters: nested combinations of lists, dicts, string and integer
values. The parameters passed to the clkmgr are a good example: they
consist of information about all clocks, their associated resets, and
much more. Having individual string or int template parameters is
infeasible.

This commit introduces a "catch-all" template parameter type called
"object". It can be any data structure, as long as it can be serialized
to Hjson. We actually check for that to ensure round-trip safety even if
the template parameters are not written to a config file and reloaded,
but passed to the template directly.

The checking is using the JSON serializer, as extended by the Hjson
library. This makes it possible to simplify the implementation
considerably, at the cost of a slightly less beautiful and less
performand implementation (since we do a object -> json -> object
round-trip with associated string parsing).

Signed-off-by: Philipp Wagner <phw@lowrisc.org>
diff --git a/util/ipgen/renderer.py b/util/ipgen/renderer.py
index 7ffa76d..b4840ac 100644
--- a/util/ipgen/renderer.py
+++ b/util/ipgen/renderer.py
@@ -60,7 +60,7 @@
             if name not in self.ip_template.params:
                 raise KeyError("No parameter named {!r} exists.".format(name))
 
-    def get_template_parameter_values(self) -> Dict[str, Union[str, int]]:
+    def get_template_parameter_values(self) -> Dict[str, Union[str, int, object]]:
         """ Get a typed mapping of all template parameters and their values.
         """
         ret = {}
@@ -75,12 +75,14 @@
             assert template_param.param_type in TemplateParameter.VALID_PARAM_TYPES
             try:
                 if template_param.param_type == 'string':
-                    val_typed = str(val)  # type: Union[int, str]
+                    val_typed = str(val)  # type: Union[int, str, object]
                 elif template_param.param_type == 'int':
                     if not isinstance(val, int):
                         val_typed = int(val, 0)
                     else:
                         val_typed = val
+                elif template_param.param_type == 'object':
+                    val_typed = val
             except (ValueError, TypeError):
                 raise TemplateRenderError(
                     "For parameter {} cannot convert value {!r} "