[reggen] Automatically generate alert test register

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/util/reggen/validate.py b/util/reggen/validate.py
index c0ea0f9..5b698e2 100644
--- a/util/reggen/validate.py
+++ b/util/reggen/validate.py
@@ -1112,8 +1112,9 @@
 
     return error
 
+
 # simplify the descriptions and enums for non-first entries in multireg
-def _multi_simplify (field, cname, idx):
+def _multi_simplify(field, cname, idx):
     # description and enum already showed once, skip
     if idx > 0:
         field.pop('enum', None)
@@ -1275,16 +1276,21 @@
     return error, rnum
 
 
-def make_intr_reg(regs, name, offset, swaccess, hwaccess, desc):
-    intrs = regs['interrupt_list']
+def make_intr_alert_reg(regs, name, offset, swaccess, hwaccess, desc):
+    if name == 'ALERT_TEST':
+        signal_list = regs['alert_list']
+    else:
+        signal_list = regs['interrupt_list']
+    # these names will be converted into test registers
+    testreg_names = ['INTR_TEST', 'ALERT_TEST']
     genreg = {}
     genreg['name'] = name
     genreg['desc'] = desc
-    genreg['hwext'] = 'true' if name == 'INTR_TEST' else 'false'
-    genreg['hwqe'] = 'true' if name == 'INTR_TEST' else 'false'
+    genreg['hwext'] = 'true' if name in testreg_names else 'false'
+    genreg['hwqe'] = 'true' if name in testreg_names else 'false'
     genreg['hwre'] = 'false'
     # Add tags.
-    if name == 'INTR_TEST':
+    if name in testreg_names:
         # intr_test csr is WO which - it reads back 0s
         genreg['tags'] = ["excl:CsrNonInitTests:CsrExclWrite"]
     elif name == 'INTR_STATE':
@@ -1296,7 +1302,7 @@
     bits_used = 0
     genfields = []
     cur_bit = 0
-    for (field_idx, bit) in enumerate(intrs):
+    for (field_idx, bit) in enumerate(signal_list):
         newf = {}
         newf['name'] = bit['name']
         w = 1
@@ -1311,8 +1317,8 @@
         # Put the automatically generated information back into
         # `interrupt_list`, so that it can be used to generate C preprocessor
         # definitions if needed.
-        intrs[field_idx]['bits'] = newf['bits']
-        intrs[field_idx]['bitinfo'] = newf['bitinfo']
+        signal_list[field_idx]['bits'] = newf['bits']
+        signal_list[field_idx]['bitinfo'] = newf['bitinfo']
 
         if name == 'INTR_ENABLE':
             newf['desc'] = 'Enable interrupt when ' + \
@@ -1322,6 +1328,8 @@
             newf['desc'] = 'Write 1 to force ' + \
                            ('corresponding bit in ' if w > 1 else '') + \
                            '!!INTR_STATE.' + newf['name'] + ' to 1'
+        elif name == 'ALERT_TEST':
+            newf['desc'] = 'Write 1 to trigger one alert event of this kind.'
         else:
             newf['desc'] = bit['desc']
         newf['swaccess'] = swaccess
@@ -1332,7 +1340,7 @@
         newf['hwaccess'] = hwaccess
         hwacc_info = hwaccess_permitted[hwaccess]
         newf['genhwaccess'] = hwacc_info[1]
-        newf['genhwqe'] = True if name == 'INTR_TEST' else False
+        newf['genhwqe'] = True if name in testreg_names else False
         newf['genhwre'] = False
         newf['genresval'] = 0
         newf['genresvalx'] = False
@@ -1359,23 +1367,37 @@
 def make_intr_regs(regs, offset, addrsep, fullwidth):
     iregs = []
     intrs = regs['interrupt_list']
-    if sum([int(x['width'], 0) if 'width' in x else 1
-            for x in intrs]) > fullwidth:
+    num_intrs = sum([int(x.get('width', '1'), 0) for x in intrs])
+    if num_intrs > fullwidth:
         log.error('More than ' + str(fullwidth) + ' interrupts in list')
         return iregs, 1
 
-    iregs.append(
-        make_intr_reg(regs, 'INTR_STATE', offset, 'rw1c', 'hrw',
-                      'Interrupt State Register'))
-    iregs.append(
-        make_intr_reg(regs, 'INTR_ENABLE', offset + addrsep, 'rw', 'hro',
-                      'Interrupt Enable Register'))
-    iregs.append(
-        make_intr_reg(regs, 'INTR_TEST', offset + 2 * addrsep, 'wo', 'hro',
-                      'Interrupt Test Register'))
+    new_reg = make_intr_alert_reg(regs, 'INTR_STATE', offset, 'rw1c', 'hrw',
+                                  'Interrupt State Register')
+    iregs.append(new_reg)
+    new_reg = make_intr_alert_reg(regs, 'INTR_ENABLE', offset + addrsep, 'rw', 'hro',
+                                  'Interrupt Enable Register')
+    iregs.append(new_reg)
+    new_reg = make_intr_alert_reg(regs, 'INTR_TEST', offset + 2 * addrsep, 'wo', 'hro',
+                                  'Interrupt Test Register')
+    iregs.append(new_reg)
     return iregs, 0
 
 
+def make_alert_regs(regs, offset, addrsep, fullwidth):
+    alert_regs = []
+    alerts = regs['alert_list']
+    num_alerts = sum([int(x.get('width', '1'), 0) for x in alerts])
+    if num_alerts > fullwidth:
+        log.error('More than ' + str(fullwidth) + ' alerts in list')
+        return alert_regs, 1
+
+    new_reg = make_intr_alert_reg(regs, 'ALERT_TEST', offset, 'wo', 'hro',
+                                  'Alert Test Register')
+    alert_regs.append(new_reg)
+    return alert_regs, 0
+
+
 def validate_window(win, offset, regwidth, top):
     error = 0
 
@@ -1597,27 +1619,44 @@
     # auto header generation would go here and update autoregs
 
     if 'no_auto_intr_regs' in regs:
-        no_autoi, err = check_bool(regs['no_auto_intr_regs'],
-                                   'no_auto_intr_regs')
+        no_auto_intr, err = check_bool(regs['no_auto_intr_regs'],
+                                       'no_auto_intr_regs')
         if err:
             error += 1
     else:
-        no_autoi = False
-    if 'interrupt_list' in regs and 'genautoregs' not in regs and not no_autoi:
+        no_auto_intr = False
+
+    if 'no_auto_alert_regs' in regs:
+        no_auto_alerts, err = check_bool(regs['no_auto_alert_regs'],
+                                         'no_auto_alert_regs')
+        if err:
+            error += 1
+    else:
+        no_auto_alerts = False
+
+    if 'interrupt_list' in regs and 'genautoregs' not in regs and not no_auto_intr:
         iregs, err = make_intr_regs(regs, offset, addrsep, fullwidth)
         error += err
         autoregs.extend(iregs)
         offset += addrsep * len(iregs)
 
     # Generate a NumAlerts parameter for provided alert_list.
-    if 'alert_list' in regs:
-        num_alerts = len(regs['alert_list'])
-        # Some modules have alerts of width > 1.
-        for a in regs['alert_list']:
-            if 'width' in a:
-                a_width = int(a['width'])
-                if a_width > 1:
-                    num_alerts += (a_width - 1)
+    if regs.setdefault('alert_list', []):
+        # Generate alert test registers.
+        if 'genautoregs' not in regs and not no_auto_alerts:
+            aregs, err = make_alert_regs(regs, offset, addrsep, fullwidth)
+            error += err
+            autoregs.extend(aregs)
+            offset += addrsep * len(aregs)
+
+        num_alerts = 0
+        for alert in regs['alert_list']:
+            alert_width = int(alert.get('width', '1'), 0)
+            num_alerts += alert_width
+            if alert_width > 1:
+                log.warning("{}: Consider naming each alert individually instead of "
+                            "declaring an alert signal with width > 1.".format(alert['name']))
+
         if num_alerts != 0:
             param = ''
             for p in regs['param_list']: