[reggen] Define a class wrapping the top-level IP block

The bulk of this patch is in ip_block.py, which defines the IpBlock class.
This object replaces the top-level dictionary that we were parsing.
Client code then replaces something like this:

    obj = hjson.load(hjson_file.open('r'),
                     use_decimal=True,
                     object_pairs_hook=OrderedDict)
    if validate.validate(obj, params=[]) != 0:
        log.info("Parsing %s configuration failed." % hjson_file)
        sys.exit(1)

with

    obj = IpBlock.from_path(str(hjson_file), [])

where obj is now an IpBlock object instead of a dict.

Other than some pesky rewrites in the various gen_FOO scripts and
template files, the other big change on the reggen side was to replace
the hierarchical "Block" class that was defined in data.py. Now, we
have a Top class (created by topgen code) and a Top can contain
multiple blocks. We've also now got some validation logic to make sure
that the sub-blocks and memories don't overlap: I'm not sure that was
there before.

As well as changing how we load files (as described above), topgen
also needed a bit of work. We now have to convert various objects to
dicts in the merge stage. (Before, we cloned the dictionaries and
added some keys; now we construct the new dictionary explicitly).

The idea is that in time we'll start to generate objects instead of
dicts in topgen as well. As a bonus, we should be able to get rid of
some of the spurious "dump & load" logic found there.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/util/reggen/gen_cfg_html.py b/util/reggen/gen_cfg_html.py
index a69dfc6..bd47e66 100644
--- a/util/reggen/gen_cfg_html.py
+++ b/util/reggen/gen_cfg_html.py
@@ -13,16 +13,17 @@
 
 
 def name_width(x):
-    if 'width' not in x or x['width'] == '1':
-        return x['name']
-    return x['name'] + '[' + str(int(x['width'], 0) - 1) + ':0]'
+    if x.bits.width() == 1:
+        return x.name
+
+    return '{}[{}:0]'.format(x.name, x.bits.msb)
 
 
 # Must have called cfg_validate, so should have no errors
 
 
 def gen_cfg_html(cfgs, outfile):
-    rnames = cfgs['genrnames']
+    rnames = list(cfgs.regs.name_to_offset.keys())
 
     genout(outfile, "<p>Referring to the \n")
     genout(
@@ -32,31 +33,31 @@
     genout(outfile,
            "Comportable guideline for peripheral device functionality</a>,\n")
     genout(outfile,
-           "the module <b><code>" + cfgs['name'] + "</code></b> has \n")
+           "the module <b><code>" + cfgs.name + "</code></b> has \n")
     genout(outfile, "the following hardware interfaces defined.</p>\n")
     # clocks
     genout(
-        outfile, "<p><i>Primary Clock:</i> <b><code>" + cfgs['clock_primary'] +
+        outfile, "<p><i>Primary Clock:</i> <b><code>" + cfgs.clock_signals[0] +
         "</code></b></p>\n")
-    if 'other_clock_list' in cfgs:
+    if len(cfgs.clock_signals) > 1:
         genout(outfile, "<p><i>Other Clocks:</i></p>\n")
     else:
         genout(outfile, "<p><i>Other Clocks: none</i></p>\n")
     # bus interfaces
     genout(
         outfile, "<p><i>Bus Device Interface:</i> <b><code>" +
-        cfgs['bus_device'] + "</code></b></p>\n")
-    if 'bus_host' in cfgs:
+        (cfgs.bus_device or '') + "</code></b></p>\n")
+    if cfgs.bus_host is not None:
         genout(
             outfile, "<p><i>Bus Host Interface:</i> <b><code>" +
-            cfgs['bus_host'] + "</code></b></p>\n")
+            cfgs.bus_host + "</code></b></p>\n")
     else:
         genout(outfile, "<p><i>Bus Host Interface: none</i></p>\n")
 
     # IO
-    ios = ([('input', x) for x in cfgs.get('available_input_list', [])] +
-           [('output', x) for x in cfgs.get('available_output_list', [])] +
-           [('inout', x) for x in cfgs.get('available_inout_list', [])])
+    ios = ([('input', x) for x in cfgs.xputs[1]] +
+           [('output', x) for x in cfgs.xputs[2]] +
+           [('inout', x) for x in cfgs.xputs[0]])
     if ios:
         genout(outfile, "<p><i>Peripheral Pins for Chip IO:</i></p>\n")
         genout(
@@ -68,37 +69,35 @@
                    '<tr><td>{}</td><td>{}</td>{}</tr>'
                    .format(name_width(x),
                            direction,
-                           render_td(x['desc'], rnames, None)))
+                           render_td(x.desc, rnames, None)))
         genout(outfile, "</table>\n")
     else:
         genout(outfile, "<p><i>Peripheral Pins for Chip IO: none</i></p>\n")
 
-    interrupts = cfgs.get('interrupt_list', [])
-    if not interrupts:
+    if not cfgs.interrupts:
         genout(outfile, "<p><i>Interrupts: none</i></p>\n")
     else:
         genout(outfile, "<p><i>Interrupts:</i></p>\n")
         genout(
             outfile, "<table class=\"cfgtable\"><tr><th>Interrupt Name</th>" +
             "<th>Description</th></tr>\n")
-        for x in interrupts:
+        for x in cfgs.interrupts:
             genout(outfile,
                    '<tr><td>{}</td>{}</tr>'
                    .format(name_width(x),
-                           render_td(x['desc'], rnames, None)))
+                           render_td(x.desc, rnames, None)))
         genout(outfile, "</table>\n")
 
-    alerts = cfgs.get('alert_list', [])
-    if not alerts:
+    if not cfgs.alerts:
         genout(outfile, "<p><i>Security Alerts: none</i></p>\n")
     else:
         genout(outfile, "<p><i>Security Alerts:</i></p>\n")
         genout(
             outfile, "<table class=\"cfgtable\"><tr><th>Alert Name</th>" +
             "<th>Description</th></tr>\n")
-        for x in alerts:
+        for x in cfgs.alerts:
             genout(outfile,
                    '<tr><td>{}</td>{}</tr>'
-                   .format(x['name'],
-                           render_td(x['desc'], rnames, None)))
+                   .format(x.name,
+                           render_td(x.desc, rnames, None)))
         genout(outfile, "</table>\n")