[topgen] Rework pinmux datastructure and templatize tops

This is a complete overhaul of the pinmux / padctrl configuration
datastructure in the top Hjson.

The new datastructures are used to provide more control over the
DIO/MIO configuration, pinout and target-specific differences
(ASIC vs FPGA vs Verilator).

The chip-level files are now generated from one common template,
and all regular DIO/MIO connections are made automatically.

Signed-off-by: Michael Schaffner <msf@google.com>
diff --git a/util/topgen/templates/toplevel_pkg.sv.tpl b/util/topgen/templates/toplevel_pkg.sv.tpl
index a4346e3..ffd60ed 100644
--- a/util/topgen/templates/toplevel_pkg.sv.tpl
+++ b/util/topgen/templates/toplevel_pkg.sv.tpl
@@ -4,30 +4,8 @@
 ${gencmd}
 <%
 import topgen.lib as lib
-
-
-def camelcase(str):
-    """Turns a string from 'snake_case' to 'CamelCase'."""
-    return "".join([part.capitalize() for part in str.split("_")])
-
-
-def get_dio_pin_enum_literal(sig, start_idx):
-    """Returns the DIO pin enum literal with value assignment"""
-    if "width" in sig and sig["width"] > 1:
-        return_str = ""
-        for i in range(int(sig["width"])):
-            if i > 0:
-              return_str += "\n    "
-            return_str += camelcase("top_{}_dio_pin_{}_{} = {},".format(
-            top["name"], sig["name"], i, start_idx + i))
-        return return_str
-    return camelcase("top_{}_dio_pin_{} = {},".format(
-        top["name"], sig["name"], start_idx))
-
-top_name = top["name"]
-
 %>\
-package top_${top_name}_pkg;
+package top_${top["name"]}_pkg;
 % for (inst_name, if_name), region in helper.devices():
 <%
     if_desc = inst_name if if_name is None else '{} device on {}'.format(if_name, inst_name)
@@ -35,12 +13,12 @@
     hex_size_bytes = "32'h{:X}".format(region.size_bytes)
 %>\
   /**
-   * Peripheral base address for ${if_desc} in top ${top_name}.
+   * Peripheral base address for ${if_desc} in top ${top["name"]}.
    */
   parameter int unsigned ${region.base_addr_name().as_c_define()} = ${hex_base_addr};
 
   /**
-   * Peripheral size in bytes for ${if_desc} in top ${top_name}.
+   * Peripheral size in bytes for ${if_desc} in top ${top["name"]}.
    */
   parameter int unsigned ${region.size_bytes_name().as_c_define()} = ${hex_size_bytes};
 
@@ -51,25 +29,82 @@
     hex_size_bytes = "32'h{:x}".format(region.size_bytes)
 %>\
   /**
-   * Memory base address for ${name} in top ${top_name}.
+   * Memory base address for ${name} in top ${top["name"]}.
    */
   parameter int unsigned ${region.base_addr_name().as_c_define()} = ${hex_base_addr};
 
   /**
-   * Memory size for ${name} in top ${top_name}.
+   * Memory size for ${name} in top ${top["name"]}.
    */
   parameter int unsigned ${region.size_bytes_name().as_c_define()} = ${hex_size_bytes};
 
 % endfor
-  // Enumeration for DIO pins.
+
+  // Enumeration of IO power domains.
+  // Only used in ASIC target.
   typedef enum {
-  <% pin_cnt = 0 %>\
-  % for sig in reversed(top["pinmux"]["dio"]):
-  ${get_dio_pin_enum_literal(sig, pin_cnt)}
-  <% pin_cnt += int(sig["width"]) if "width" in sig else 1 %>\
-  % endfor
-  ${camelcase("top_{}_dio_pin_count".format(top_name))} = ${pin_cnt}
-  } top_${top_name}_dio_pin_e;
+% for bank in top["pinout"]["banks"]:
+    ${lib.Name(['io', 'bank', bank]).as_camel_case()} = ${loop.index},
+% endfor
+    IoBankCount = ${len(top["pinout"]["banks"])}
+  } pwr_dom_e;
+
+  // Enumeration for MIO signals on the top-level.
+  typedef enum {
+% for sig in top["pinmux"]["ios"]:
+  % if sig['type'] in ['inout', 'input'] and sig['connection'] == 'muxed':
+    ${lib.get_io_enum_literal(sig, 'mio_in')} = ${sig['glob_idx']},
+  % endif
+% endfor
+<% total = top["pinmux"]['io_counts']['muxed']['inouts'] + \
+           top["pinmux"]['io_counts']['muxed']['inputs'] %>\
+    ${lib.Name.from_snake_case("mio_in_count").as_camel_case()} = ${total}
+  } mio_in_e;
+
+  typedef enum {
+% for sig in top["pinmux"]["ios"]:
+  % if sig['type'] in ['inout', 'output'] and sig['connection'] == 'muxed':
+    ${lib.get_io_enum_literal(sig, 'mio_out')} = ${sig['glob_idx']},
+  % endif
+% endfor
+<% total = top["pinmux"]['io_counts']['muxed']['inouts'] + \
+           top["pinmux"]['io_counts']['muxed']['outputs'] %>\
+    ${lib.Name.from_snake_case("mio_out_count").as_camel_case()} = ${total}
+  } mio_out_e;
+
+  // Enumeration for DIO signals, used on both the top and chip-levels.
+  typedef enum {
+% for sig in top["pinmux"]["ios"]:
+  % if sig['connection'] != 'muxed':
+    ${lib.get_io_enum_literal(sig, 'dio')} = ${sig['glob_idx']},
+  % endif
+% endfor
+<% total = top["pinmux"]['io_counts']['dedicated']['inouts'] + \
+           top["pinmux"]['io_counts']['dedicated']['inputs'] + \
+           top["pinmux"]['io_counts']['dedicated']['outputs'] %>\
+    ${lib.Name.from_snake_case("dio_count").as_camel_case()} = ${total}
+  } dio_e;
+
+  // Raw MIO/DIO input array indices on chip-level.
+  // TODO: Does not account for target specific stubbed/added pads.
+  // Need to make a target-specific package for those.
+  typedef enum {
+% for pad in top["pinout"]["pads"]:
+  % if pad["connection"] == "muxed":
+    ${lib.Name.from_snake_case("mio_pad_" + pad["name"]).as_camel_case()} = ${pad["idx"]},
+  % endif
+% endfor
+    ${lib.Name.from_snake_case("mio_pad_count").as_camel_case()}
+  } mio_pad_e;
+
+  typedef enum {
+% for pad in top["pinout"]["pads"]:
+  % if pad["connection"] != "muxed":
+    ${lib.Name.from_snake_case("dio_pad_" + pad["name"]).as_camel_case()} = ${pad["idx"]},
+  % endif
+% endfor
+    ${lib.Name.from_snake_case("dio_pad_count").as_camel_case()}
+  } dio_pad_e;
 
   // TODO: Enumeration for PLIC Interrupt source peripheral.
   // TODO: Enumeration for PLIC Interrupt Ids.