[reggen] Delete duplicate Register/MultiRegister types in data.py

We've recently moved to representing registers, fields etc. in
classes, rather than dicts. This duplicated some work done in
gen_rtl.py and data.py and this commit removes the duplicated Register
and MultiRegister types.

There's a certain amount of tidying up in reg_pkg.sv.tpl, which I
think makes the implementation a bit easier to read (as much as is
possible for template code like this).

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/util/reggen/data.py b/util/reggen/data.py
index 477b007..3c03b7c 100644
--- a/util/reggen/data.py
+++ b/util/reggen/data.py
@@ -3,136 +3,19 @@
 # SPDX-License-Identifier: Apache-2.0
 
 from collections import OrderedDict
+import re
 
-from .field import Field
+from .multi_register import MultiRegister
+from .register import Register
 
 
 # helper funtion that strips trailing _number (used as multireg suffix) from name
 # TODO: this is a workaround, should solve this in validate.py
 def get_basename(name):
-    for (k, c) in enumerate(name[::-1]):
-        if not str.isdigit(c):
-            if c == "_":
-                return name[0:len(name) - (k + 1)]
-            else:
-                break
-    return ""
-
-
-class Reg():
-    def __init__(self, name=""):
-        self.name = name
-        self.offset = 0
-        self.hwqe = False
-        self.hwre = False
-        self.hwext = False  # External register
-        self.resval = 0
-        self.dvrights = "RO"  # Used by UVM REG only
-        self.regwen = ""
-        self.fields = []
-        self.width = 0
-        self.ishomog = 0
-        self.tags = []
-        self.shadowed = False
-        self.update_err_alert = ""  # Used by shadow reg DV
-        self.storage_err_alert = ""  # Used by shadow reg DV
-
-    def is_multi_reg(self):
-        """Returns true if this is a multireg"""
-        return False
-
-    def get_n_bits(self, bittype=["q"]):
-        """Returns number of bits in this register (including all multiregs and
-        fields). By default this function counts read data bits (bittype "q"),
-        but other bits such as "d", qe", "re", "de" can be counted as well by
-        specifying them in the bittype list argument.
-        """
-        n_bits = 0
-        for f in self.fields:
-            if isinstance(f, Reg):
-                n_bits += f.get_n_bits(bittype)
-            else:
-                n_bits += f.get_n_bits(self.hwext, bittype)
-        return n_bits
-
-    def get_fields_flat(self):
-        """Returns a flat list of all the fields in this register"""
-        ret = []
-        for f in self.fields:
-            if isinstance(f, Reg):
-                ret += f.get_fields_flat()
-            else:
-                ret.append(f)
-
-        return ret
-
-    def get_field_flat(self, linear_idx):
-        """Returns a specific field at a linear index position in
-        the flattened field list"""
-        fields_flat = self.get_fields_flat()
-        return fields_flat[linear_idx]
-
-    def get_n_fields_flat(self):
-        """Returns the number of fields contained in the flat field list"""
-        return len(self.get_fields_flat())
-
-    def get_regs_flat(self):
-        """Returns a flat list containing all
-        registers and subregisters"""
-        if isinstance(self.fields[0], Field):
-            return [self]
-        else:
-            regs = []
-            for r in self.fields:
-                regs += r.get_regs_flat()
-            return regs
-
-    def get_reg_flat(self, linear_index):
-        """Returns a specific register at a linear index position in
-        the flattened regiser list"""
-        regs_flat = self.get_regs_flat()
-        return regs_flat[linear_index]
-
-    def get_n_regs_flat(self):
-        """Returns the number of registers contained in
-        the flat register list"""
-        return len(self.get_regs_flat())
-
-    def get_nested_dims(self):
-        """Recursively get dimensions of nested registers (outputs a list)"""
-        # return length of flattened field array if this is a regular register,
-        # or if this is the last multiregister level in a nested multiregister
-        if not isinstance(self, MultiReg):
-            dims = [len(self.get_fields_flat())]
-        if isinstance(self, MultiReg) and\
-           not isinstance(self.fields[0], MultiReg):
-            if self.ishomog:
-                dims = [len(self.get_fields_flat())]
-            else:
-                dims = [len(self.fields)]
-        else:
-            # nested multiregister case
-            dims = [len(self.fields)] + self.fields[0].get_nested_dims()
-        return dims
-
-    def get_nested_params(self):
-        """Recursively get parameters of nested registers (outputs a list)"""
-        params = []
-        if isinstance(self, MultiReg):
-            params += [self.param]
-        if isinstance(self.fields[0], MultiReg):
-            params += self.fields[0].get_nested_params()
-        return params
-
-
-class MultiReg(Reg):
-    def __init__(self, name):
-        Reg.__init__(self, name)
-        self.param = ""
-
-    def is_multi_reg(self):
-        """Returns true if this is a multireg"""
-        return True
+    match = re.search(r'_[0-9]+$', name)
+    assert match
+    assert match.start() > 0
+    return name[0:match.start()]
 
 
 class Window():
@@ -163,7 +46,12 @@
         """
         regs = []
         for r in self.regs:
-            regs += r.get_regs_flat()
+            if isinstance(r, Register):
+                regs.append(r)
+            else:
+                assert isinstance(r, MultiRegister)
+                regs += r.regs
+
         return regs
 
     def get_n_bits(self, bittype=["q"]):
@@ -179,11 +67,3 @@
 
     def get_n_regs_flat(self):
         return len(self.get_regs_flat())
-
-    def contains_multiregs(self):
-        """Returns true if there are multiregs in this block
-        """
-        for r in self.regs:
-            if isinstance(r, MultiReg):
-                return True
-        return False
diff --git a/util/reggen/field.py b/util/reggen/field.py
index 291e997..1a8791a 100644
--- a/util/reggen/field.py
+++ b/util/reggen/field.py
@@ -184,7 +184,29 @@
         return (self.enum is not None and
                 len(self.enum) != 1 + self.bits.max_value())
 
-    def get_n_bits(self, hwext: bool, bittype: List[str] = ["q"]) -> int:
+    def get_n_bits(self, hwext: bool, bittype: List[str]) -> int:
+        '''Get the size of this field in bits
+
+        bittype should be a list of the types of signals to count. The elements
+        should come from the following list:
+
+        - 'q': A signal for the value of the field. Only needed if HW can read
+          its contents.
+
+        - 'd': A signal for the next value of the field. Only needed if HW can
+          write its contents.
+
+        - 'qe': A write enable signal for bus accesses. Only needed if HW can
+          read the field's contents and the field has the hwqe flag.
+
+        - 're': A read enable signal for bus accesses. Only needed if HW can
+          read the field's contents and the field has the hwre flag.
+
+        - 'de': A write enable signal for hardware accesses. Only needed if HW
+          can write the field's contents and the register data is stored in the
+          register block (true if the hwext flag is false).
+
+        '''
         n_bits = 0
         if "q" in bittype and self.hwaccess.allows_read():
             n_bits += self.bits.width()
diff --git a/util/reggen/fpv_csr.sv.tpl b/util/reggen/fpv_csr.sv.tpl
index 66a54a5..22f8c53 100644
--- a/util/reggen/fpv_csr.sv.tpl
+++ b/util/reggen/fpv_csr.sv.tpl
@@ -9,6 +9,8 @@
 <%
   from reggen import (gen_fpv)
   from reggen.data import get_basename
+  from reggen.register import Register
+  from reggen.multi_register import MultiRegister
 
   from topgen import lib
 %>\
@@ -161,50 +163,52 @@
   has_q  = r.get_n_bits(["q"]) > 0
   has_d  = r.get_n_bits(["d"]) > 0
   has_de = r.get_n_bits(["de"]) > 0
+
+  if isinstance(r, Register):
+    r0 = r
+    flat_regs = [r]
+  else:
+    assert isinstance(r, MultiRegister)
+    r0 = r.reg
+    flat_regs = r.regs
 %>\
-  % if not r.shadowed:
-  % if r.is_multi_reg():
+  % if not r0.shadowed:
+  % if isinstance(r, MultiRegister):
 <%
-      mreg_name          = r.name
+      mreg_name          = r0.name.lower()
       mreg_width_list    = list()
       mreg_fpv_name_list = list()
       mreg_dut_path_list = list()
       mreg_has_q_list    = list()
       mreg_has_d_list    = list()
       mreg_has_de_list   = list()
-      mreg_num_regs      = r.get_n_fields_flat()
+      fields             = r.get_field_list()
+      mreg_num_regs      = len(fields)
 
       mreg_msb = -1
       mreg_lsb = 0
       i        = 0
+
+      if not r.is_homogeneous():
+        for field in r0.fields:
+          mreg_fpv_name_list.append(mreg_name + "_" + field.name.lower())
+          mreg_dut_path_list.append(mreg_name + "[s]." + field.name.lower())
+          mreg_width_list.append(field.bits.width())
+          mreg_has_q_list.append(field.get_n_bits(r0.hwext, ["q"]) > 0)
+          mreg_has_d_list.append(field.get_n_bits(r0.hwext, ["d"]) > 0)
+          mreg_has_de_list.append(field.get_n_bits(r0.hwext, ["de"]) > 0)
+        mreg_num_base_fields = len(mreg_fpv_name_list)
+        mreg_num_regs        = mreg_num_regs / mreg_num_base_fields
+      else:
+        f = fields[0]
+        mreg_num_base_fields = 1
+        mreg_fpv_name_list.append(mreg_name)
+        mreg_dut_path_list.append(mreg_name + "[s]")
+        mreg_width_list.append(f.bits.width())
+        mreg_has_q_list.append(has_q)
+        mreg_has_d_list.append(has_d)
+        mreg_has_de_list.append(has_de)
 %>\
-   % if not r.ishomog:
-     % for field in r.get_reg_flat(0).fields:
-<%
-  mreg_fpv_name_list.append(mreg_name + "_" + get_basename(field.name.lower()))
-  mreg_dut_path_list.append(mreg_name + "[s]." + get_basename(field.name.lower()))
-  mreg_width_list.append(field.bits.width())
-  mreg_has_q_list.append(field.get_n_bits(r.hwext, ["q"]) > 0)
-  mreg_has_d_list.append(field.get_n_bits(r.hwext, ["d"]) > 0)
-  mreg_has_de_list.append(field.get_n_bits(r.hwext, ["de"]) > 0)
-%>\
-     % endfor
-<%
-  mreg_num_base_fields = len(mreg_fpv_name_list)
-  mreg_num_regs        = mreg_num_regs / mreg_num_base_fields
-%>\
-   % else:
-<%
-  f = r.get_field_flat(0)
-  mreg_num_base_fields = 1
-  mreg_fpv_name_list.append(mreg_name)
-  mreg_dut_path_list.append(mreg_name + "[s]")
-  mreg_width_list.append(f.bits.width())
-  mreg_has_q_list.append(has_q)
-  mreg_has_d_list.append(has_d)
-  mreg_has_de_list.append(has_de)
-%>\
-   % endif
 
   // define local fpv variable for multi-reg
    % if r.get_n_bits(["q", "d"]):
@@ -219,21 +223,17 @@
    % endif
   % endif
 
-  % for reg_flat in r.get_regs_flat():
+  % for reg_flat in flat_regs:
 <%
-  reg_name    = reg_flat.name
-  reg_offset  =  str(reg_width) + "'h" + "%x" % reg_flat.offset
-  reg_msb     = reg_flat.width - 1
-  regwen      = reg_flat.regwen
+  reg_name    = reg_flat.name.lower()
+  reg_offset  = "{}'h{:x}".format(reg_width, reg_flat.offset)
+  reg_msb     = reg_flat.get_width() - 1
+  reg_wen     = ('`REGWEN_PATH.{}_qs'.format(reg_flat.regwen.lower())
+                 if reg_flat.regwen is not None else '1')
   reg_wr_mask = 0
 %>\
-  // assertions for register: ${reg_name}
-    % if regwen:
-<% reg_wen = "`REGWEN_PATH." + regwen + "_qs" %>\
-    % else:
-<% reg_wen = "1" %>\
-    % endif
-    % for f in reg_flat.get_fields_flat():
+  // assertions for register: ${reg_name.lower()}
+    % for f in reg_flat.fields:
 <%
       field_name      = f.name.lower()
       assert_path     = reg_name + "." + field_name
@@ -245,12 +245,12 @@
       reg_wr_mask_h   = format(reg_wr_mask, 'x')
       lsb             = f.bits.lsb
       sw_rdaccess     = f.swaccess.swrd().name
-      had_q           = f.get_n_bits(r.hwext, ["q"]) > 0
-      has_d           = f.get_n_bits(r.hwext, ["d"]) > 0
-      has_de          = f.get_n_bits(r.hwext, ["de"]) > 0
+      had_q           = f.get_n_bits(r0.hwext, ["q"]) > 0
+      has_d           = f.get_n_bits(r0.hwext, ["d"]) > 0
+      has_de          = f.get_n_bits(r0.hwext, ["de"]) > 0
 %>\
-      % if not r.ishomog:
-        % if r.is_multi_reg():
+      % if not r.is_homogeneous():
+        % if isinstance(r, MultiRegister):
   // this is a non-homog multi-reg
 <%
       mreg_lsb = i * mreg_width_list[loop.index]
@@ -262,15 +262,14 @@
         % endif
       % endif
     % endfor
-    % if r.is_multi_reg():
 <%
-      mreg_lsb = i * (mreg_msb - mreg_lsb + 1)
-      mreg_msb = mreg_lsb + reg_msb
-      i += 1
+      if isinstance(r, MultiRegister):
+        mreg_lsb = i * (mreg_msb - mreg_lsb + 1)
+        mreg_msb = mreg_lsb + reg_msb
+        i += 1
 %>\
-    % endif
-    % if r.ishomog:
-      % if r.is_multi_reg():
+    % if r.is_homogeneous():
+      % if isinstance(r, MultiRegister):
 ${gen_multi_reg_asserts_by_category(reg_name, mreg_name, mreg_msb, mreg_lsb, reg_flat.hwext, reg_wr_mask_h)}\
       % else:
 ${gen_asserts_by_category(reg_name, reg_name, reg_flat.hwext, reg_wr_mask_h)}\
@@ -330,7 +329,7 @@
   % else:
 <% wr_property = "P" %>\
   % endif
-  % if not r.ishomog:
+  % if not r.is_homogeneous():
 <% shift_index = lsb %>\
   % else:
 <% shift_index = 0 %>\
@@ -350,7 +349,7 @@
   % else:
 <% rd_property = "rd_P" %>\
   % endif
-  % if not r.ishomog:
+  % if not r.is_homogeneous():
 <% shift_index = lsb %>\
   % else:
 <% shift_index = 0 %>\
diff --git a/util/reggen/gen_dv.py b/util/reggen/gen_dv.py
index c44559f..6053d0b 100644
--- a/util/reggen/gen_dv.py
+++ b/util/reggen/gen_dv.py
@@ -21,7 +21,7 @@
 
 def rcname(b, r):
     '''Get the name of the dv_base_reg subclass for this register'''
-    return b.name + "_reg_" + r.name
+    return b.name + "_reg_" + r.name.lower()
 
 
 def mcname(b, m):
diff --git a/util/reggen/gen_rtl.py b/util/reggen/gen_rtl.py
index ea546fc..4196529 100644
--- a/util/reggen/gen_rtl.py
+++ b/util/reggen/gen_rtl.py
@@ -11,7 +11,7 @@
 from pkg_resources import resource_filename
 
 from .access import HwAccess, SwRdAccess, SwWrAccess
-from .data import Block, MultiReg, Reg, Window
+from .data import Block, Window
 from .register import Register
 from .multi_register import MultiRegister
 
@@ -27,72 +27,6 @@
         return default
 
 
-def parse_reg(obj):
-    """Convert Register into a Reg object."""
-    assert isinstance(obj, Register)
-    reg = Reg(escape_name(obj.name))
-    reg.offset = obj.offset
-    reg.fields = []
-    reg.hwext = obj.hwext
-    reg.hwqe = obj.hwqe
-    reg.hwre = obj.hwre
-    reg.resval = obj.resval
-    reg.dvrights = obj.dv_rights()
-    reg.regwen = (obj.regwen or '').lower()
-    reg.ishomog = len(obj.fields) == 1
-    reg.tags = obj.tags
-    reg.shadowed = obj.shadowed
-    # For DV only: TODO: any good way we can move it to gen_dv?
-    reg.update_err_alert = obj.update_err_alert or ''
-    reg.storage_err_alert = obj.storage_err_alert or ''
-
-    reg.fields = obj.fields
-    for field in reg.fields:
-        reg.width = max(reg.width, field.bits.msb + 1)
-
-    # TODO: Field bitfield overlapping check
-    log.info("R[0x%04x]: %s ", reg.offset, reg.name)
-    for f in reg.fields:
-        log.info("  F[%2d:%2d]: %s", f.bits.msb, f.bits.lsb, f.name)
-
-    return reg
-
-
-def parse_multireg(obj):
-    '''Convert MultiRegister into a MultiReg object'''
-    assert isinstance(obj, MultiRegister)
-    regs = [parse_reg(r) for r in obj.regs]
-    reg0 = regs[0].get_reg_flat(0)
-    # get register properties of the first register in the multireg and
-    # copy them to the parent
-    # since all regs in a multireg have the same props
-    reg = MultiReg(escape_name(obj.reg.name))
-    reg.offset = reg0.offset
-    reg.hwext = reg0.hwext
-    reg.hwqe = reg0.hwqe
-    reg.hwre = reg0.hwre
-
-    # since this is a multireg, the list of fields can
-    # contain regs or multiregs
-    reg.fields = regs
-
-    # Fields that don't really make sense for a multireg
-    reg.resval = None
-    reg.dvrights = None
-    reg.regwen = None
-
-    # a homogenous multireg contains only one single field that is replicated
-    reg.ishomog = len(obj.reg.fields) == 1
-
-    reg.tags = reg0.tags
-    reg.shadowed = reg0.shadowed
-
-    # TODO: need to reference proper param here such that it can be used
-    # in the package template for the array declaration
-    # reg.param = ...
-    return reg
-
-
 def parse_win(obj, width):
     # Convert register window fields into Window class
     # base_addr : genoffset
@@ -137,11 +71,8 @@
     block.hier_path = obj["hier_path"] if "hier_path" in obj else ""
 
     for r in obj["registers"]:
-        if isinstance(r, Register):
-            block.regs.append(parse_reg(r))
-            continue
-        if isinstance(r, MultiRegister):
-            block.regs.append(parse_multireg(r))
+        if isinstance(r, Register) or isinstance(r, MultiRegister):
+            block.regs.append(r)
             continue
         assert isinstance(r, dict)
         if 'window' in r:
diff --git a/util/reggen/multi_register.py b/util/reggen/multi_register.py
index 71ddc62..12c1b72 100644
--- a/util/reggen/multi_register.py
+++ b/util/reggen/multi_register.py
@@ -5,9 +5,11 @@
 from typing import Dict, List
 
 from reggen import register
-from .register import Register
+from .field import Field
 from .lib import (check_keys, check_str, check_name,
                   check_bool, expand_parameter)
+from .reg_block import RegBlock
+from .register import Register
 
 REQUIRED_FIELDS = {
     'name': ['s', "base name of the registers"],
@@ -40,13 +42,14 @@
 })
 
 
-class MultiRegister:
+class MultiRegister(RegBlock):
     def __init__(self,
                  offset: int,
                  addrsep: int,
                  reg_width: int,
                  params: List[Dict[str, object]],
                  raw: object):
+        super().__init__(offset)
 
         rd = check_keys(raw, 'multireg',
                         list(REQUIRED_FIELDS.keys()),
@@ -115,6 +118,18 @@
                                       min_reg_idx, max_reg_idx, self.cname)
             self.regs.append(reg)
 
+    def get_n_bits(self, bittype: List[str] = ["q"]) -> int:
+        return sum(reg.get_n_bits(bittype) for reg in self.regs)
+
+    def get_field_list(self) -> List[Field]:
+        ret = []
+        for reg in self.regs:
+            ret += reg.get_field_list()
+        return ret
+
+    def is_homogeneous(self) -> bool:
+        return self.reg.is_homogeneous()
+
     def _asdict(self) -> Dict[str, object]:
         rd = self.reg._asdict()
         rd['count'] = str(self.count)
diff --git a/util/reggen/reg_block.py b/util/reggen/reg_block.py
new file mode 100644
index 0000000..ad8fc31
--- /dev/null
+++ b/util/reggen/reg_block.py
@@ -0,0 +1,45 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+from typing import List
+
+from .field import Field
+
+
+class RegBlock:
+    '''An abstract class inherited by Register and MultiRegister
+
+    This represents a block of one or more registers with a base address.
+
+    '''
+    def __init__(self, offset: int):
+        self.offset = offset
+
+    def get_n_bits(self, bittype: List[str]) -> int:
+        '''Get the size of this register / these registers in bits
+
+        See Field.get_n_bits() for the precise meaning of bittype.
+
+        '''
+        raise NotImplementedError()
+
+    def get_field_list(self) -> List[Field]:
+        '''Get an ordered list of the fields in the register(s)
+
+        Registers are ordered from low to high address. Within a register,
+        fields are ordered as Register.fields: from LSB to MSB.
+
+        '''
+        raise NotImplementedError()
+
+    def is_homogeneous(self) -> bool:
+        '''True if every field in the block is identical
+
+        For a single register, this is true if it only has one field. For a
+        multireg, it is true if the generating register has just one field.
+        Note that if the compact flag is set, the generated registers might
+        have multiple (replicated) fields.
+
+        '''
+        raise NotImplementedError()
diff --git a/util/reggen/reg_pkg.sv.tpl b/util/reggen/reg_pkg.sv.tpl
index dce5c4f..2a7860a 100644
--- a/util/reggen/reg_pkg.sv.tpl
+++ b/util/reggen/reg_pkg.sv.tpl
@@ -6,7 +6,8 @@
 
 <%
   from topgen import lib # TODO: Split lib to common lib module
-  from reggen.data import get_basename
+  from reggen.register import Register
+  from reggen.multi_register import MultiRegister
 
   num_regs = block.get_n_regs_flat()
   max_regs_char = len("{}".format(num_regs-1))
@@ -27,71 +28,123 @@
   // Typedefs for registers //
   ////////////////////////////
 % for r in block.regs:
-  ## in this case we have a homogeneous multireg, with only one replicated field
-  % if r.get_n_bits(["q"]) and r.ishomog:
+  % if r.get_n_bits(["q"]):
+<%
+    if isinstance(r, Register):
+      r0 = r
+      type_suff = 'reg_t'
+    else:
+      assert isinstance(r, MultiRegister)
+      r0 = r.reg
+      type_suff = 'mreg_t'
+
+    reg2hw_name = ('{}_reg2hw_{}_{}'
+                   .format(block.name, r0.name.lower(), type_suff))
+%>\
   typedef struct packed {
-    logic ${lib.bitarray(r.get_field_flat(0).get_n_bits(r.hwext, ["q"]),2)} q;
-    % if r.get_field_flat(0).hwqe:
+    % if r.is_homogeneous():
+      ## If we have a homogeneous register or multireg, there is just one field
+      ## (possibly replicated many times). The typedef is for one copy of that
+      ## field.
+<%
+      field = r.get_field_list()[0]
+      field_q_width = field.get_n_bits(r0.hwext, ['q'])
+      field_q_bits = lib.bitarray(field_q_width, 2)
+%>\
+    logic ${field_q_bits} q;
+      % if field.hwqe:
     logic        qe;
-    % endif
-    % if r.get_field_flat(0).hwre or (r.shadowed and r.hwext):
+      % endif
+      % if field.hwre or (r0.shadowed and r0.hwext):
     logic        re;
-    % endif
-    % if r.shadowed and not r.hwext:
+      % endif
+      % if r0.shadowed and not r0.hwext:
     logic        err_update;
     logic        err_storage;
-    % endif
-  } ${block.name + "_reg2hw_" + r.name + ("_mreg_t" if r.is_multi_reg() else "_reg_t")};
+      % endif
+    % else:
+      ## We are inhomogeneous, which means there is more than one different
+      ## field. Generate a reg2hw typedef that packs together all the fields of
+      ## the register.
+      % for f in r0.fields:
+        % if f.get_n_bits(r0.hwext, ["q"]) >= 1:
+<%
+          field_q_width = f.get_n_bits(r0.hwext, ['q'])
+          field_q_bits = lib.bitarray(field_q_width, 2)
 
-  ## in this case we have an inhomogeneous multireg, with several different fields per register
-  % elif r.get_n_bits(["q"]) and not r.ishomog:
-  typedef struct packed {
-    % for f in r.get_reg_flat(0).fields:
-      % if f.get_n_bits(r.hwext, ["q"]) >= 1:
+          struct_name = f.name.lower()
+%>\
     struct packed {
-      logic ${lib.bitarray(f.get_n_bits(r.hwext, ["q"]),2)} q;
-      % if f.hwqe:
+      logic ${field_q_bits} q;
+          % if f.hwqe:
       logic        qe;
-      % endif
-      % if f.hwre or (r.shadowed and r.hwext):
+          % endif
+          % if f.hwre or (r0.shadowed and r0.hwext):
       logic        re;
-      % endif
-      % if r.shadowed and not r.hwext:
+          % endif
+          % if r0.shadowed and not r0.hwext:
       logic        err_update;
       logic        err_storage;
-      % endif
-    } ${get_basename(f.name.lower()) if r.is_multi_reg() else f.name.lower()};
-      %endif
-    %endfor
-  } ${block.name + "_reg2hw_" + r.name + ("_mreg_t" if r.is_multi_reg() else "_reg_t")};
+          % endif
+    } ${struct_name};
+        %endif
+      %endfor
+    %endif
+  } ${reg2hw_name};
 
   %endif
 % endfor
 
 % for r in block.regs:
- ## in this case we have a homogeneous multireg, with only one replicated field
-  % if r.get_n_bits(["d"]) and r.ishomog:
-  typedef struct packed {
-    logic ${lib.bitarray(r.get_field_flat(0).get_n_bits(r.hwext, ["d"]),2)} d;
-    % if not r.hwext:
-    logic        de;
-    % endif
-  } ${block.name + "_hw2reg_" + r.name + ("_mreg_t" if r.is_multi_reg() else "_reg_t")};
+  % if r.get_n_bits(["d"]):
+<%
+    if isinstance(r, Register):
+      r0 = r
+      type_suff = 'reg_t'
+    else:
+      assert isinstance(r, MultiRegister)
+      r0 = r.reg
+      type_suff = 'mreg_t'
 
-  ## in this case we have an inhomogeneous multireg, with several different fields per register
-  % elif r.get_n_bits(["d"]) and not r.ishomog:
+    hw2reg_name = ('{}_hw2reg_{}_{}'
+                   .format(block.name, r0.name.lower(), type_suff))
+%>\
   typedef struct packed {
-    % for f in r.get_reg_flat(0).fields:
-      % if f.get_n_bits(r.hwext, ["d"]) >= 1:
-    struct packed {
-      logic ${lib.bitarray(f.get_n_bits(r.hwext, ["d"]),2)} d;
-      % if not r.hwext:
-      logic        de;
+    % if r.is_homogeneous():
+      ## If we have a homogeneous register or multireg, there is just one field
+      ## (possibly replicated many times). The typedef is for one copy of that
+      ## field.
+<%
+      field = r.get_field_list()[0]
+      field_d_width = field.get_n_bits(r0.hwext, ['d'])
+      field_d_bits = lib.bitarray(field_d_width, 2)
+%>\
+    logic ${field_d_bits} d;
+      % if not r0.hwext:
+    logic        de;
       % endif
-    } ${get_basename(f.name.lower()) if r.is_multi_reg() else f.name.lower()};
-      %endif
-    %endfor
-  } ${block.name + "_hw2reg_" + r.name + ("_mreg_t" if r.is_multi_reg() else "_reg_t")};
+    % else:
+      ## We are inhomogeneous, which means there is more than one different
+      ## field. Generate a hw2reg typedef that packs together all the fields of
+      ## the register.
+      % for f in r0.fields:
+        % if f.get_n_bits(r0.hwext, ["d"]) >= 1:
+<%
+          field_d_width = f.get_n_bits(r0.hwext, ['d'])
+          field_d_bits = lib.bitarray(field_d_width, 2)
+
+          struct_name = f.name.lower()
+%>\
+    struct packed {
+      logic ${field_d_bits} d;
+          % if not r0.hwext:
+      logic        de;
+          % endif
+    } ${struct_name};
+        %endif
+      %endfor
+    %endif
+  } ${hw2reg_name};
 
   % endif
 % endfor
@@ -100,26 +153,32 @@
   // Register to internal design logic //
   ///////////////////////////////////////
 <%
-nbits = block.get_n_bits(["q","qe","re"])
+nbits = block.get_n_bits(["q", "qe", "re"])
 packbit = 0
 %>\
 % if nbits > 0:
   typedef struct packed {
 % for r in block.regs:
-  ######################## multiregister ###########################
-  % if r.is_multi_reg() and r.get_n_bits(["q"]):
+  % if r.get_n_bits(["q"]):
 <%
-  array_dims = ""
-  for d in r.get_nested_dims():
-    array_dims += "[%d:0]" % (d-1)
+    if isinstance(r, MultiRegister):
+      r0 = r.reg
+      repl_count = r.count
+      type_suff = 'mreg_t [{}:0]'.format(repl_count - 1)
+    else:
+      r0 = r
+      repl_count = 1
+      type_suff = 'reg_t'
+
+    struct_type = ('{}_reg2hw_{}_{}'
+                   .format(block.name, r0.name.lower(), type_suff))
+
+    struct_width = r0.get_n_bits(['q', 'qe', 're']) * repl_count
+    msb = nbits - packbit - 1
+    lsb = msb - struct_width + 1
+    packbit += struct_width
 %>\
-    ${block.name + "_reg2hw_" + r.name + "_mreg_t"} ${array_dims} ${r.name}; // [${nbits - packbit - 1}:${nbits - (packbit + r.get_n_bits(["q", "qe", "re"]))}]<% packbit += r.get_n_bits(["q", "qe", "re"]) %>\
-
-  ######################## register ###########################
-  % elif r.get_n_bits(["q"]):
-    ## Only one field, should use register name as it is
-    ${block.name + "_reg2hw_" + r.name + "_reg_t"} ${r.name}; // [${nbits - packbit - 1}:${nbits - (packbit + r.get_n_bits(["q", "qe", "re"]))}]<% packbit += r.get_n_bits(["q", "qe", "re"]) %>\
-
+    ${struct_type} ${r0.name.lower()}; // [${msb}:${lsb}]
   % endif
 % endfor
   } ${block.name}_reg2hw_t;
@@ -129,64 +188,75 @@
   // Internal design logic to register //
   ///////////////////////////////////////
 <%
-nbits = block.get_n_bits(["d","de"])
+nbits = block.get_n_bits(["d", "de"])
 packbit = 0
 %>\
 % if nbits > 0:
   typedef struct packed {
 % for r in block.regs:
-<%  reg_d_bits = r.get_n_bits(["d"]) %>\
-  % if reg_d_bits:
+  % if r.get_n_bits(["d"]):
 <%
-    if r.is_multi_reg():
-      array_dims = "".join("[%d:0]" % (d-1) for d in r.get_nested_dims())
-      reg_type = "{}_hw2reg_{}_mreg_t {}".format(block.name, r.name, array_dims)
+    if isinstance(r, MultiRegister):
+      r0 = r.reg
+      repl_count = r.count
+      type_suff = 'mreg_t [{}:0]'.format(repl_count - 1)
     else:
-      reg_type = "{}_hw2reg_{}_reg_t".format(block.name, r.name)
+      r0 = r
+      repl_count = 1
+      type_suff = 'reg_t'
 
-    reg_width = r.get_n_bits(["d", "de"])
+    struct_type = ('{}_hw2reg_{}_{}'
+                   .format(block.name, r0.name.lower(), type_suff))
+
+    struct_width = r0.get_n_bits(['d', 'de']) * repl_count
     msb = nbits - packbit - 1
-    lsb = nbits - (packbit + reg_width)
-    packbit += reg_width
+    lsb = msb - struct_width + 1
+    packbit += struct_width
 %>\
-    ${reg_type} ${r.name}; // [${msb}:${lsb}]
+    ${struct_type} ${r0.name.lower()}; // [${msb}:${lsb}]
   % endif
 % endfor
   } ${block.name}_hw2reg_t;
 % endif
 
   // Register Address
+<%
+ublock = block.name.upper()
+%>\
 % for r in block.get_regs_flat():
-  parameter logic [BlockAw-1:0] ${block.name.upper()}_${r.name.upper()}_OFFSET = ${block.addr_width}'h ${"%x" % r.offset};
+  parameter logic [BlockAw-1:0] ${ublock}_${r.name.upper()}_OFFSET = ${block.addr_width}'h ${"%x" % r.offset};
 % endfor
 
 % if len(block.wins) > 0:
   // Window parameter
 % endif
 % for i,w in enumerate(block.wins):
-  parameter logic [BlockAw-1:0] ${block.name.upper()}_${w.name.upper()}_OFFSET = ${block.addr_width}'h ${"%x" % w.base_addr};
-  parameter logic [BlockAw-1:0] ${block.name.upper()}_${w.name.upper()}_SIZE   = ${block.addr_width}'h ${"%x" % (w.limit_addr - w.base_addr)};
+  parameter logic [BlockAw-1:0] ${ublock}_${w.name.upper()}_OFFSET = ${block.addr_width}'h ${"%x" % w.base_addr};
+  parameter logic [BlockAw-1:0] ${ublock}_${w.name.upper()}_SIZE   = ${block.addr_width}'h ${"%x" % (w.limit_addr - w.base_addr)};
 % endfor
 
   // Register Index
   typedef enum int {
 % for r in block.get_regs_flat():
-    ${block.name.upper()}_${r.name.upper()}${"" if loop.last else ","}
+    ${ublock}_${r.name.upper()}${"" if loop.last else ","}
 % endfor
   } ${block.name}_id_e;
 
   // Register width information to check illegal writes
-  parameter logic [3:0] ${block.name.upper()}_PERMIT [${block.get_n_regs_flat()}] = '{
+  parameter logic [3:0] ${ublock}_PERMIT [${block.get_n_regs_flat()}] = '{
 % for i,r in enumerate(block.get_regs_flat()):
-<% index_str = "{}".format(i).rjust(max_regs_char) %>\
-  % if r.width > 24:
-    4'b 1111${" " if i == num_regs-1 else ","} // index[${index_str}] ${block.name.upper()}_${r.name.upper()}
-  % elif r.width > 16:
-    4'b 0111${" " if i == num_regs-1 else ","} // index[${index_str}] ${block.name.upper()}_${r.name.upper()}
-  % elif r.width > 8:
-    4'b 0011${" " if i == num_regs-1 else ","} // index[${index_str}] ${block.name.upper()}_${r.name.upper()}
+<%
+  index_str = "{}".format(i).rjust(max_regs_char)
+  width = r.get_width()
+%>\
+  % if width > 24:
+    4'b 1111${" " if i == num_regs-1 else ","} // index[${index_str}] ${ublock}_${r.name.upper()}
+  % elif width > 16:
+    4'b 0111${" " if i == num_regs-1 else ","} // index[${index_str}] ${ublock}_${r.name.upper()}
+  % elif width > 8:
+    4'b 0011${" " if i == num_regs-1 else ","} // index[${index_str}] ${ublock}_${r.name.upper()}
   % else:
-    4'b 0001${" " if i == num_regs-1 else ","} // index[${index_str}] ${block.name.upper()}_${r.name.upper()}
+    4'b 0001${" " if i == num_regs-1 else ","} // index[${index_str}] ${ublock}_${r.name.upper()}
   % endif
 % endfor
   };
diff --git a/util/reggen/reg_top.sv.tpl b/util/reggen/reg_top.sv.tpl
index 75bafaf..2fbdf9b 100644
--- a/util/reggen/reg_top.sv.tpl
+++ b/util/reggen/reg_top.sv.tpl
@@ -5,6 +5,8 @@
 // Register Top module auto-generated by `reggen`
 <%
   from reggen.data import get_basename
+  from reggen.register import Register
+  from reggen.multi_register import MultiRegister
 
   num_wins = len(block.wins)
   num_wins_width = ((num_wins+1).bit_length()) - 1
@@ -146,10 +148,10 @@
   //        or <reg>_{wd|we|qs} if field == 1 or 0
   % for r in regs_flat:
     % if len(r.fields) == 1:
-${sig_gen(r.fields[0], r.name, r.hwext, r.shadowed)}\
+${sig_gen(r.fields[0], r.name.lower(), r.hwext, r.shadowed)}\
     % else:
       % for f in r.fields:
-${sig_gen(f, r.name + "_" + f.name.lower(), r.hwext, r.shadowed)}\
+${sig_gen(f, r.name.lower() + "_" + f.name.lower(), r.hwext, r.shadowed)}\
       % endfor
     % endif
   % endfor
@@ -157,37 +159,36 @@
   // Register instances
   % for r in block.regs:
   ######################## multiregister ###########################
-    % if r.is_multi_reg():
+    % if isinstance(r, MultiRegister):
 <%
-      mreg_flat = r.get_regs_flat()
       k = 0
 %>
-      % for sr in mreg_flat:
-  // Subregister ${k} of Multireg ${r.name}
-  // R[${sr.name}]: V(${str(sr.hwext)})
+      % for sr in r.regs:
+  // Subregister ${k} of Multireg ${r.reg.name.lower()}
+  // R[${sr.name.lower()}]: V(${str(sr.hwext)})
         % if len(sr.fields) == 1:
 <%
           f = sr.fields[0]
-          finst_name = sr.name
-          fsig_name = r.name + "[%d]" % k
+          finst_name = sr.name.lower()
+          fsig_name = r.reg.name.lower() + "[%d]" % k
           k = k + 1
 %>
 ${finst_gen(f, finst_name, fsig_name, sr.hwext, sr.regwen, sr.shadowed)}
         % else:
           % for f in sr.fields:
 <%
-            finst_name = sr.name + "_" + f.name.lower()
-            if r.ishomog:
-              fsig_name = r.name + "[%d]" % k
+            finst_name = sr.name.lower() + "_" + f.name.lower()
+            if r.is_homogeneous():
+              fsig_name = r.reg.name.lower() + "[%d]" % k
               k = k + 1
             else:
-              fsig_name = r.name + "[%d]" % k + "." + get_basename(f.name.lower())
+              fsig_name = r.reg.name.lower() + "[%d]" % k + "." + get_basename(f.name.lower())
 %>
   // F[${f.name.lower()}]: ${f.bits.msb}:${f.bits.lsb}
 ${finst_gen(f, finst_name, fsig_name, sr.hwext, sr.regwen, sr.shadowed)}
           % endfor
 <%
-          if not r.ishomog:
+          if not r.is_homogeneous():
             k += 1
 %>
         % endif
@@ -195,20 +196,20 @@
       % endfor
 ######################## register with single field ###########################
     % elif len(r.fields) == 1:
-  // R[${r.name}]: V(${str(r.hwext)})
+  // R[${r.name.lower()}]: V(${str(r.hwext)})
 <%
         f = r.fields[0]
-        finst_name = r.name
-        fsig_name = r.name
+        finst_name = r.name.lower()
+        fsig_name = r.name.lower()
 %>
 ${finst_gen(f, finst_name, fsig_name, r.hwext, r.regwen, r.shadowed)}
 ######################## register with multiple fields ###########################
     % else:
-  // R[${r.name}]: V(${str(r.hwext)})
+  // R[${r.name.lower()}]: V(${str(r.hwext)})
       % for f in r.fields:
 <%
-        finst_name = r.name + "_" + f.name.lower()
-        fsig_name = r.name + "." + f.name.lower()
+        finst_name = r.name.lower() + "_" + f.name.lower()
+        fsig_name = r.name.lower() + "." + f.name.lower()
 %>
   //   F[${f.name.lower()}]: ${f.bits.msb}:${f.bits.lsb}
 ${finst_gen(f, finst_name, fsig_name, r.hwext, r.regwen, r.shadowed)}
@@ -239,10 +240,10 @@
   end
   % for i, r in enumerate(regs_flat):
     % if len(r.fields) == 1:
-${we_gen(r.fields[0], r.name, r.hwext, r.shadowed, i)}\
+${we_gen(r.fields[0], r.name.lower(), r.hwext, r.shadowed, i)}\
     % else:
       % for f in r.fields:
-${we_gen(f, r.name + "_" + f.name.lower(), r.hwext, r.shadowed, i)}\
+${we_gen(f, r.name.lower() + "_" + f.name.lower(), r.hwext, r.shadowed, i)}\
       % endfor
     % endif
   % endfor
@@ -254,13 +255,13 @@
       % for i, r in enumerate(regs_flat):
         % if len(r.fields) == 1:
       addr_hit[${i}]: begin
-${rdata_gen(r.fields[0], r.name)}\
+${rdata_gen(r.fields[0], r.name.lower())}\
       end
 
         % else:
       addr_hit[${i}]: begin
           % for f in r.fields:
-${rdata_gen(f, r.name + "_" + f.name.lower())}\
+${rdata_gen(f, r.name.lower() + "_" + f.name.lower())}\
           % endfor
       end
 
@@ -322,7 +323,7 @@
     % if field.swaccess.allows_write():
       % if regwen:
     // qualified with register enable
-    .we     (${finst_name}_we & ${regwen}_qs),
+    .we     (${finst_name}_we & ${regwen.lower()}_qs),
       % else:
     .we     (${finst_name}_we),
       % endif
@@ -384,7 +385,7 @@
       % if field.swaccess.allows_write(): ## non-RO types
         % if regwen:
     // from register interface (qualified with register enable)
-    .we     (${finst_name}_we & ${regwen}_qs),
+    .we     (${finst_name}_we & ${regwen.lower()}_qs),
         % else:
     // from register interface
     .we     (${finst_name}_we),
diff --git a/util/reggen/register.py b/util/reggen/register.py
index c9d4025..d56c352 100644
--- a/util/reggen/register.py
+++ b/util/reggen/register.py
@@ -8,6 +8,7 @@
 from .field import Field
 from .lib import (check_keys, check_str, check_name, check_bool,
                   check_list, check_str_list, check_int)
+from .reg_block import RegBlock
 
 REQUIRED_FIELDS = {
     'name': ['s', "name of the register"],
@@ -72,7 +73,7 @@
 }
 
 
-class Register:
+class Register(RegBlock):
     '''Code representing a register for reggen'''
     def __init__(self,
                  offset: int,
@@ -90,7 +91,7 @@
                  fields: List[Field],
                  update_err_alert: Optional[str],
                  storage_err_alert: Optional[str]):
-        self.offset = offset
+        super().__init__(offset)
         self.name = name
         self.desc = desc
 
@@ -270,6 +271,27 @@
     def dv_rights(self) -> str:
         return self.swaccess.dv_rights()
 
+    def get_n_bits(self, bittype: List[str]) -> int:
+        return sum(field.get_n_bits(self.hwext, bittype)
+                   for field in self.fields)
+
+    def get_field_list(self) -> List[Field]:
+        return self.fields
+
+    def is_homogeneous(self) -> bool:
+        return len(self.fields) == 1
+
+    def get_width(self) -> int:
+        '''Get the width of the fields in the register in bits
+
+        This counts dead space between and below fields, so it's calculated as
+        one more than the highest msb.
+
+        '''
+        # self.fields is ordered by (increasing) LSB, so we can find the MSB of
+        # the register by taking the MSB of the last field.
+        return 1 + self.fields[-1].bits.msb
+
     def make_multi(self,
                    reg_width: int,
                    offset: int,
diff --git a/util/reggen/uvm_reg.sv.tpl b/util/reggen/uvm_reg.sv.tpl
index d8672f5..d26746d 100644
--- a/util/reggen/uvm_reg.sv.tpl
+++ b/util/reggen/uvm_reg.sv.tpl
@@ -44,7 +44,7 @@
 % for r in regs_flat:
 <%
   reg_width = block.width
-  reg_name = r.name
+  reg_name = r.name.lower()
   is_ext = 0
   reg_shadowed = r.shadowed
 %>\
@@ -192,7 +192,7 @@
     // registers
 % endif
 % for r in regs_flat:
-    rand ${gen_dv.rcname(block, r)} ${r.name};
+    rand ${gen_dv.rcname(block, r)} ${r.name.lower()};
 % endfor
 % if block.wins:
     // memories
@@ -243,8 +243,8 @@
 % endif
 % for r in regs_flat:
 <%
-  reg_name = r.name
-  reg_right = r.dvrights
+  reg_name = r.name.lower()
+  reg_right = r.dv_rights()
   reg_width = block.width
   reg_offset =  str(reg_width) + "'h" + "%x" % r.offset
   reg_tags = r.tags
@@ -275,7 +275,7 @@
       // assign locked reg to its regwen reg
 % for r in regs_flat:
   % if r.regwen:
-      ${r.regwen}.add_locked_reg(${r.name});
+      ${r.regwen.lower()}.add_locked_reg(${r.name.lower()});
   % endif
 % endfor