[topgen] New Helper for Defining C Enums

This adds a new helper for defining simple C enums, and uses it to
define the PLIC target enum.

Signed-off-by: Sam Elliott <selliott@lowrisc.org>
diff --git a/util/topgen/c.py b/util/topgen/c.py
index 0175307..3d202c0 100644
--- a/util/topgen/c.py
+++ b/util/topgen/c.py
@@ -5,6 +5,8 @@
 `top_{name}.h`.
 """
 
+from mako.template import Template
+
 
 class Name(object):
     """We often need to format names in specific ways, this class does so."""
@@ -17,13 +19,32 @@
 
     def __init__(self, parts):
         self.parts = list(parts)
+        for p in parts:
+            assert len(p) > 0, "cannot add zero-length name piece"
 
     def as_snake_case(self):
         return "_".join([p.lower() for p in self.parts])
 
+    def as_camel_case(self):
+        out = ""
+        for p in self.parts:
+            # If we're about to join two parts which would introduce adjacent
+            # numbers, put an underscore between them.
+            if out[-1:].isnumeric() and p[:1].isnumeric():
+                out += "_" + p
+            else:
+                out += p.capitalize()
+        return out
+
     def as_c_define(self):
         return "_".join([p.upper() for p in self.parts])
 
+    def as_c_enum(self):
+        return "k" + self.as_camel_case()
+
+    def as_c_type(self):
+        return self.as_snake_case() + "_t"
+
 
 class MemoryRegion(object):
     def __init__(self, name, base_addr, size_bytes):
@@ -38,10 +59,47 @@
         return self.name + Name(["size", "bytes"])
 
 
+class CEnum(object):
+    def __init__(self, name):
+        self.name = name
+        self.enum_counter = 0
+        self.finalized = False
+
+        self.constants = []
+
+    def add_constant(self, constant_name, docstring=""):
+        assert not self.finalized
+
+        full_name = self.name + constant_name
+
+        value = self.enum_counter
+        self.enum_counter += 1
+
+        self.constants.append((full_name, value, docstring))
+
+    def add_last_constant(self, docstring=""):
+        assert not self.finalized
+
+        full_name = self.name + Name(["last"])
+
+        _, last_val, _ = self.constants[-1]
+
+        self.constants.append((full_name, last_val, r"\internal " + docstring))
+        self.finalized = True
+
+    def render(self):
+        template = ("typedef enum ${enum.name.as_snake_case()} {\n"
+                    "% for name, value, docstring in enum.constants:\n"
+                    "  ${name.as_c_enum()} = ${value}, /**< ${docstring} */\n"
+                    "% endfor\n"
+                    "} ${enum.name.as_c_type()};")
+        return Template(template).render(enum=self)
+
+
 class TopGenC(object):
     def __init__(self, top_info):
-        self._top_name = Name(["top"]) + Name.from_snake_case(top_info["name"])
         self.top = top_info
+        self._top_name = Name(["top"]) + Name.from_snake_case(top_info["name"])
 
     def modules(self):
         return [(m["name"],
@@ -54,3 +112,14 @@
                  MemoryRegion(self._top_name + Name.from_snake_case(m["name"]),
                               m["base_addr"], m["size"]))
                 for m in self.top["memory"]]
+
+    def plic_targets(self):
+        enum = CEnum(self._top_name + Name(["plic", "target"]))
+
+        for core_id in range(int(self.top["num_cores"])):
+            enum.add_constant(Name(["ibex", str(core_id)]),
+                              docstring="Ibex Core {}".format(core_id))
+
+        enum.add_last_constant("Final PLIC target")
+
+        return enum