[reggen] Define a Window type to represent (memory) windows

This also removes the Window class from reggen/data.py: we'll use the
new Window class everywhere.

There's a bit of code to handle window tags at the bottom of
uvm_reg.sv.tpl that goes away completely (windows don't have tags, so
this was dead code).

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/util/shared/mem_layout.py b/hw/ip/otbn/util/shared/mem_layout.py
index e86b2ad..23d4b60 100644
--- a/hw/ip/otbn/util/shared/mem_layout.py
+++ b/hw/ip/otbn/util/shared/mem_layout.py
@@ -20,41 +20,30 @@
 
 from typing import Dict, List, Optional, Tuple
 
-from .otbn_reggen import HjsonDict, load_registers
+from .otbn_reggen import load_registers, Window
 
 # A window is represented as (offset, size)
 _Window = Tuple[int, int]
 
 
 def extract_windows(reg_byte_width: int,
-                    registers: List[HjsonDict]) -> Dict[str, _Window]:
+                    registers: List[object]) -> Dict[str, _Window]:
     '''Make sense of the list of register definitions and extract memories'''
 
     # Conveniently, reggen's validate method stores 'genoffset' (the offset to
     # the start) for each window, so we can just look that up.
     windows = {}
 
-    for reg in registers:
-        if not isinstance(reg, dict):
+    for entry in registers:
+        if not isinstance(entry, Window):
             continue
 
-        window = reg.get('window')
-        if window is None:
-            continue
+        name = entry.name or 'Window at +{:#x}'.format(entry.offset)
 
-        assert isinstance(window, dict)
+        # Should be guaranteed by RegBlock constructor
+        assert name not in windows
 
-        offset = window['genoffset']
-        assert isinstance(offset, int)
-
-        items = int(window['items'])
-        window_name = window.get('name', 'Window at +{:#x}'.format(offset))
-        assert isinstance(window_name, str)
-        if window_name in windows:
-            raise ValueError('Duplicate window entry with name {!r}.'
-                             .format(window_name))
-
-        windows[window_name] = (offset, items * reg_byte_width)
+        windows[name] = (entry.offset, entry.size_in_bytes)
 
     return windows
 
diff --git a/hw/ip/otbn/util/shared/otbn_reggen.py b/hw/ip/otbn/util/shared/otbn_reggen.py
index 2c72ab0..588b1e4 100644
--- a/hw/ip/otbn/util/shared/otbn_reggen.py
+++ b/hw/ip/otbn/util/shared/otbn_reggen.py
@@ -6,13 +6,10 @@
 
 import os
 import sys
-from typing import List, Mapping, Optional, Tuple
+from typing import List, Optional, Tuple
 
 import hjson  # type: ignore
 
-# An hjson dict is actually an OrderedDict, but typing/mypy support for that is
-# a little spotty, so we'll use a generic Mapping type.
-HjsonDict = Mapping[str, object]
 
 # We use reggen to read the hjson file. Since that lives somewhere completely
 # different from this script (and there aren't __init__.py files scattered all
@@ -23,8 +20,9 @@
                               '..', '..', '..', '..', '..', 'util')
     sys.path = [_UTIL_PATH] + _OLD_SYS_PATH
     from reggen.validate import checking_dict, validate   # type: ignore
-    import reggen.register  # type: ignore
     import reggen.field  # type: ignore
+    import reggen.register  # type: ignore
+    import reggen.window  # type: ignore
 finally:
     sys.path = _OLD_SYS_PATH
 
@@ -32,14 +30,16 @@
 # transitively without having to mess around with sys.path.
 Register = reggen.register.Register
 Field = reggen.field.Field
+Window = reggen.window.Window
 
-_LR_RETVAL = None  # type: Optional[Tuple[int, List[HjsonDict]]]
+_LR_RETVAL = None  # type: Optional[Tuple[int, List[object]]]
 
 
-def load_registers() -> Tuple[int, List[HjsonDict]]:
+def load_registers() -> Tuple[int, List[object]]:
     '''Load otbn.hjson with reggen
 
-    Return its register width and list of registers. Memoized.
+    Returns (width, regs) where width is the register width and regs is a
+    list of Register, MultiRegister or Window objects. Memoized.
 
     '''
     global _LR_RETVAL
@@ -74,6 +74,5 @@
     # dictionaries, so we can assert the type safely.
     registers = obj['registers']
     assert isinstance(registers, list)
-
     _LR_RETVAL = (reg_byte_width, registers)
     return _LR_RETVAL
diff --git a/util/reggen/data.py b/util/reggen/data.py
index 3c03b7c..db05f31 100644
--- a/util/reggen/data.py
+++ b/util/reggen/data.py
@@ -18,15 +18,6 @@
     return name[0:match.start()]
 
 
-class Window():
-    def __init__(self):
-        self.base_addr = 0
-        self.byte_write = 0
-        self.limit_addr = 0
-        self.n_bits = 0
-        self.tags = []
-
-
 class Block():
     def __init__(self):
         self.width = 32
diff --git a/util/reggen/gen_cheader.py b/util/reggen/gen_cheader.py
index 87b2d8c..29e7023 100644
--- a/util/reggen/gen_cheader.py
+++ b/util/reggen/gen_cheader.py
@@ -14,6 +14,7 @@
 
 from .register import Register
 from .multi_register import MultiRegister
+from .window import Window
 
 
 def genout(outfile, msg):
@@ -143,15 +144,15 @@
 
 
 def gen_cdefine_window(outstr, win, comp, regwidth, rnames, existing_defines):
-    wname = win['name']
-    offset = win['genoffset']
+    wname = win.name or "Window at + {:#x}".format(win.offset)
+    offset = win.offset
 
-    genout(outstr, format_comment('Memory area: ' + first_line(win['desc'])))
+    genout(outstr, format_comment('Memory area: ' + first_line(win.desc)))
     defname = as_define(comp + '_' + wname)
     genout(
         outstr,
         gen_define(defname + '_REG_OFFSET', [], hex(offset), existing_defines))
-    items = int(win['items'])
+    items = win.items
     genout(
         outstr,
         gen_define(defname + '_SIZE_WORDS', [], str(items), existing_defines))
@@ -160,7 +161,7 @@
         outstr,
         gen_define(defname + '_SIZE_BYTES', [], str(items), existing_defines))
 
-    wid = win['genvalidbits']
+    wid = win.validbits
     if (wid != regwidth):
         mask = (1 << wid) - 1
         genout(outstr,
@@ -339,9 +340,8 @@
                                  existing_defines)
             continue
 
-        assert isinstance(x, dict)
-        if 'window' in x:
-            gen_cdefine_window(outstr, x['window'], component, regwidth,
+        if isinstance(x, Window):
+            gen_cdefine_window(outstr, x, component, regwidth,
                                rnames, existing_defines)
             continue
 
diff --git a/util/reggen/gen_html.py b/util/reggen/gen_html.py
index 18d289a..9e3a978 100644
--- a/util/reggen/gen_html.py
+++ b/util/reggen/gen_html.py
@@ -9,6 +9,7 @@
 
 from .multi_register import MultiRegister
 from .register import Register
+from .window import Window
 
 
 def genout(outfile, msg):
@@ -217,18 +218,27 @@
 
 
 def gen_html_window(outfile, win, comp, regwidth, rnames, toc, toclvl):
-    wname = win['name']
-    offset = win['genoffset']
-    genout(
-        outfile, '<table class="regdef" id="Reg_' + wname.lower() + '">\n'
-        '<tr><th class="regdef"><div>' + comp + '.' + wname + ' @ + ' +
-        hex(offset) + '</div><div>' + win['items'] + ' item ' +
-        win['swaccess'] + ' window</div><div>Byte writes are ' +
-        ('' if win['genbyte-write'] else '<i>not</i> ') +
-        'supported</div></th></tr>\n')
+    wname = win.name or '(unnamed window)'
+    offset = win.offset
+    genout(outfile,
+           '<table class="regdef" id="Reg_{lwname}">\n'
+           '  <tr>\n'
+           '    <th class="regdef">\n'
+           '      <div>{comp}.{wname} @ + {off:#x}</div>\n'
+           '      <div>{items} item {swaccess} window</div>\n'
+           '      <div>Byte writes are {byte_writes}supported</div>\n'
+           '    </th>\n'
+           '  </tr>\n'
+           .format(comp=comp,
+                   wname=wname,
+                   lwname=wname.lower(),
+                   off=offset,
+                   items=win.items,
+                   swaccess=win.swaccess.key,
+                   byte_writes=('' if win.byte_write else '<i>not</i> ')))
     genout(outfile, '<tr><td><table class="regpic">')
     genout(outfile, '<tr><td width="10%"></td>')
-    wid = win['genvalidbits']
+    wid = win.validbits
 
     for x in range(regwidth - 1, -1, -1):
         if x == regwidth - 1 or x == wid - 1 or x == 0:
@@ -236,7 +246,7 @@
         else:
             genout(outfile, '<td class="bitnum"></td>')
     genout(outfile, '</tr>')
-    tblmax = int(win['items']) - 1
+    tblmax = win.items - 1
     for x in [0, 1, 2, tblmax - 1, tblmax]:
         if x == 2:
             genout(
@@ -260,7 +270,7 @@
             genout(outfile, '</tr>')
     genout(outfile, '</td></tr></table>')
     genout(outfile,
-           '<tr>{}</tr>'.format(render_td(win['desc'], rnames, 'regde')))
+           '<tr>{}</tr>'.format(render_td(win.desc, rnames, 'regde')))
     genout(outfile, "</table>\n<br>\n")
     if toc is not None:
         toc.append((toclvl, comp + "." + wname, "Reg_" + wname.lower()))
@@ -289,10 +299,8 @@
                 gen_html_register(outfile, reg, component, regwidth, rnames,
                                   toclist, toclevel)
             continue
-
-        assert isinstance(x, dict)
-        if 'window' in x:
-            gen_html_window(outfile, x['window'], component, regwidth, rnames,
+        if isinstance(x, Window):
+            gen_html_window(outfile, x, component, regwidth, rnames,
                             toclist, toclevel)
             continue
 
diff --git a/util/reggen/gen_rtl.py b/util/reggen/gen_rtl.py
index 4196529..7a35652 100644
--- a/util/reggen/gen_rtl.py
+++ b/util/reggen/gen_rtl.py
@@ -11,9 +11,10 @@
 from pkg_resources import resource_filename
 
 from .access import HwAccess, SwRdAccess, SwWrAccess
-from .data import Block, Window
+from .data import Block
 from .register import Register
 from .multi_register import MultiRegister
+from .window import Window
 
 
 def escape_name(name):
@@ -27,22 +28,6 @@
         return default
 
 
-def parse_win(obj, width):
-    # Convert register window fields into Window class
-    # base_addr : genoffset
-    # limit_addr : genoffset + items*width
-    win = Window()
-    win.name = obj["name"]
-    win.base_addr = obj["genoffset"]
-    win.byte_write = obj["genbyte-write"]
-    win.limit_addr = obj["genoffset"] + int(obj["items"]) * (width // 8)
-    win.dvrights = obj["swaccess"]
-    win.n_bits = obj["genvalidbits"]
-
-    # TODO: Generate warnings of `unusual`
-    return win
-
-
 def json_to_reg(obj):
     """Converts JSON OrderedDict into structure having useful information for
     Template to use.
@@ -74,11 +59,9 @@
         if isinstance(r, Register) or isinstance(r, MultiRegister):
             block.regs.append(r)
             continue
-        assert isinstance(r, dict)
-        if 'window' in r:
-            win = parse_win(r['window'], block.width)
-            if win is not None:
-                block.wins.append(win)
+
+        if isinstance(r, Window):
+            block.wins.append(r)
             continue
 
     # Last offset and calculate space
diff --git a/util/reggen/gen_selfdoc.py b/util/reggen/gen_selfdoc.py
index 9df2da3..8e46ac9 100644
--- a/util/reggen/gen_selfdoc.py
+++ b/util/reggen/gen_selfdoc.py
@@ -6,7 +6,9 @@
 
 """
 from .access import SWACCESS_PERMITTED, HWACCESS_PERMITTED
-from reggen import validate, enum_entry, field, register, multi_register
+from reggen import (validate,
+                    enum_entry, field,
+                    register, multi_register, window)
 
 
 def genout(outfile, msg):
@@ -291,12 +293,10 @@
 
     genout(outfile, window_intro)
     doc_tbl_head(outfile, 1)
-    for x in validate.window_required:
-        doc_tbl_line(outfile, x, 'r', validate.window_required[x])
-    for x in validate.window_optional:
-        doc_tbl_line(outfile, x, 'o', validate.window_optional[x])
-    for x in validate.window_added:
-        doc_tbl_line(outfile, x, 'a', validate.window_added[x])
+    for k, v in window.REQUIRED_FIELDS.items():
+        doc_tbl_line(outfile, k, 'r', v)
+    for k, v in window.OPTIONAL_FIELDS.items():
+        doc_tbl_line(outfile, k, 'o', v)
 
     genout(outfile, multi_intro)
     doc_tbl_head(outfile, 1)
diff --git a/util/reggen/lib.py b/util/reggen/lib.py
index 7b27077..8253c03 100644
--- a/util/reggen/lib.py
+++ b/util/reggen/lib.py
@@ -226,6 +226,12 @@
 def expand_parameter(params: List[Dict[str, object]],
                      value: str,
                      when: str) -> int:
+    # Check whether the 'parameter' is already an integer: if so, return that.
+    try:
+        return int(value, 0)
+    except ValueError:
+        pass
+
     found = None
     for param in params:
         if param['name'] == value:
diff --git a/util/reggen/reg_pkg.sv.tpl b/util/reggen/reg_pkg.sv.tpl
index 58ef7b5..75da1a1 100644
--- a/util/reggen/reg_pkg.sv.tpl
+++ b/util/reggen/reg_pkg.sv.tpl
@@ -264,8 +264,13 @@
 % if len(block.wins) > 0:
   // Window parameter
 % for i,w in enumerate(block.wins):
-  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)};
+<%
+    win_pfx = '{}_{}'.format(ublock, w.name.upper())
+    base_txt_val = "{}'h {:x}".format(block.addr_width, w.offset)
+    size_txt_val = "{}'h {:x}".format(block.addr_width, w.size_in_bytes)
+%>\
+  parameter logic [BlockAw-1:0] ${win_pfx}_OFFSET = ${base_txt_val};
+  parameter logic [BlockAw-1:0] ${win_pfx}_SIZE   = ${size_txt_val};
 % endfor
 
 % endif
diff --git a/util/reggen/reg_top.sv.tpl b/util/reggen/reg_top.sv.tpl
index 2fbdf9b..4eb75c4 100644
--- a/util/reggen/reg_top.sv.tpl
+++ b/util/reggen/reg_top.sv.tpl
@@ -109,11 +109,15 @@
 
     // TODO: Can below codes be unique case () inside ?
   % for i,w in enumerate(block.wins):
-      % if w.limit_addr == 2**block.addr_width:
-    if (tl_i.a_address[AW-1:0] >= ${w.base_addr}) begin
-      // Exceed or meet the address range. Removed the comparison of limit addr ${"'h %x" % w.limit_addr}
+<%
+    base_addr = w.offset
+    limit_addr = w.offset + w.size_in_bytes
+%>\
+      % if limit_addr == 2**block.addr_width:
+    if (tl_i.a_address[AW-1:0] >= ${base_addr}) begin
+      // Exceed or meet the address range. Removed the comparison of limit addr 'h ${'{:x}'.format(limit_addr)}
       % else:
-    if (tl_i.a_address[AW-1:0] >= ${w.base_addr} && tl_i.a_address[AW-1:0] < ${w.limit_addr}) begin
+    if (tl_i.a_address[AW-1:0] >= ${base_addr} && tl_i.a_address[AW-1:0] < ${limit_addr}) begin
       % endif
       reg_steer = ${i};
     end
diff --git a/util/reggen/uvm_reg.sv.tpl b/util/reggen/uvm_reg.sv.tpl
index d26746d..28174f9 100644
--- a/util/reggen/uvm_reg.sv.tpl
+++ b/util/reggen/uvm_reg.sv.tpl
@@ -155,9 +155,9 @@
 % for w in block.wins:
 <%
   mem_name = w.name.lower()
-  mem_right = w.dvrights.upper()
-  mem_n_bits = w.n_bits
-  mem_size = int((w.limit_addr - w.base_addr) / (mem_n_bits / 8))
+  mem_right = w.swaccess.dv_rights()
+  mem_n_bits = w.validbits
+  mem_size = w.items
 %>\
   // Class: ${gen_dv.mcname(block, w)}
   class ${gen_dv.mcname(block, w)} extends ${dv_base_prefix}_mem;
@@ -286,28 +286,16 @@
 % for w in block.wins:
 <%
   mem_name = w.name.lower()
-  mem_right = w.dvrights.upper()
-  mem_offset = str(block.width) + "'h" + "%x" % w.base_addr
-  mem_n_bits = w.n_bits
-  mem_size = int((w.limit_addr - w.base_addr) / (mem_n_bits / 8))
-  mem_tags = w.tags
+  mem_right = w.swaccess.dv_rights()
+  mem_offset = str(block.width) + "'h" + "%x" % w.offset
+  mem_n_bits = w.validbits
+  mem_size = w.items
 %>\
       ${mem_name} = ${gen_dv.mcname(block, w)}::type_id::create("${mem_name}");
       ${mem_name}.configure(.parent(this));
       default_map.add_mem(.mem(${mem_name}),
                           .offset(${mem_offset}),
                           .rights("${mem_right}"));
-  % if mem_tags:
-      // create memory tags
-    % for mem_tag in mem_tags:
-<%
-  tag = mem_tag.split(":")
-%>\
-      % if tag[0] == "excl":
-      csr_excl.add_excl(${mem_name}.get_full_name(), ${tag[2]}, ${tag[1]});
-      % endif
-    % endfor
-  % endif
 % endfor
     endfunction : build
 
diff --git a/util/reggen/validate.py b/util/reggen/validate.py
index 7314cc7..210f05c 100644
--- a/util/reggen/validate.py
+++ b/util/reggen/validate.py
@@ -9,11 +9,12 @@
 import re
 from collections import OrderedDict
 
-from .access import SWAccess, HWAccess, SWACCESS_PERMITTED
+from .access import SWAccess, HWAccess
 from .bits import Bits
 from .field import Field
 from .multi_register import MultiRegister
 from .register import Register
+from .window import Window
 
 
 # Routine that can be used for Hjson object_pairs_hook
@@ -377,94 +378,10 @@
      "from a base instance."]
 }
 
-# Register keys
-reg_added = {
-    'genresval': ['pi', "reset value generated from resval and fields"],
-    'genresmask': ['pi', "mask of bits with valid reset value (not x)"],
-    'genbitsused': ['pi', "mask of bits defined in the register"],
-    'genoffset': ['pi', "offset address of the register"],
-    'gendvrights': ['s', "SW Rights used in UVM reg class"]
-}
-
-# Window keys
-window_required = {
-    'name': ['s', "Name of the window"],
-    'desc': ['t', "description of the window"],
-    'items': ['d', "size in fieldaccess width words of the window"],
-    'swaccess': ['s', "software access permitted"],
-}
-
-# TODO potential for additional optional to give more type info?
-# eg sram-hw-port: "none", "sync", "async"
-window_optional = {
-    'byte-write': [
-        's', "True if byte writes are supported. "
-        "Defaults to false if not present."
-    ],
-    'validbits': [
-        'd', "Number of valid data bits within "
-        "regwidth sized word. "
-        "Defaults to regwidth. If "
-        "smaller than the regwidth then in each "
-        "word of the window bits "
-        "[regwidth-1:validbits] are unused and "
-        "bits [validbits-1:0] are valid."
-    ],
-    'unusual': [
-        's', "True if window has unusual parameters "
-        "(set to prevent Unusual: errors)."
-        "Defaults to false if not present."
-    ]
-}
-
-window_added = {
-    'genbyte-write': ['pb', "generated boolean for byte-write"],
-    'genvalidbits': ['pi', "valid data width"],
-    'genoffset':
-    ['pi', "base offset address of the window (aligned for size)"],
-    'genswaccess': ['pe', "Software access (gen enum)"],
-    'genswwraccess': ['pe', "Software write access (gen enum)"],
-    'genswrdaccess': ['pe', "Software read access (gen enum)"]
-}
-
-# Field keys
-# special case in the code, no name and no desc if only field
-field_added = {
-    'genrsvdenum': ['pb', "enum did not cover every possible value"],
-    'genresval': [
-        'pi', "resval for field constructed by the tool. "
-        "Will be set to 0 for x."
-    ],
-    'genresvalx': ['pb', "Indicates if resval is x"],
-    'genswaccess': ['pe', "Software access (generated enum)"],
-    'genswwraccess': ['pe', "Software write access (generated enum)"],
-    'genswrdaccess': ['pe', "Software read access (generated enum)"],
-    'genhwaccess': ['pe', "Hardware access (generated Enum)"],
-    'genhwqe': ['pb', "Hardware qualifier enable signal needed"],
-    'genhwre': ['pb', "Hardware read enable signal needed"],
-    'bitinfo': ['T', "tuple (bitfield_mask, field width, lsb)"]
-}
-
 key_use = {'r': "required", 'o': "optional", 'a': "added by tool"}
 
 
-# if not int, check in param_list
-def resolve_value(entry, param_list):
-    val, not_int = check_int(entry, "", True)
-    err = 0
-
-    if not_int:
-        param, err = search_param(param_list, entry)
-        val = param['default']
-        if param['local'] != "true":
-            log.warning(
-                "It is recommended to define {} as localparam,"
-                " since it should not be changed in the design".format(entry))
-
-    return int(val), err
-
-
-def _upd_gennames(regs, offset, register):
+def _upd_regnames(regs, offset, register):
     genrnames = regs['genrnames']
     rname = register.name.lower()
     err = 0
@@ -562,7 +479,7 @@
                    fields=fields,
                    update_err_alert=None,
                    storage_err_alert=None)
-    _upd_gennames(regs, offset, reg)
+    _upd_regnames(regs, offset, reg)
     return reg
 
 
@@ -600,92 +517,6 @@
     return alert_regs, 0
 
 
-def validate_window(win, offset, regwidth, top):
-    error = 0
-
-    if 'name' not in win:
-        name = "Window at +" + hex(offset)
-    else:
-        name = win['name']
-        if name.lower() in top['genrnames']:
-            error += 1
-            log.error("Window at +" + hex(offset) + " duplicate name " + name)
-        else:
-            top['genrnames'].append(name.lower())
-
-    error += check_keys(win, window_required, window_optional, window_added,
-                        name)
-
-    # if there was an error before this then can't trust anything!
-    if error > 0:
-        log.debug(name + "@" + hex(offset) + " " + str(error) +
-                  " top level errors. Window will be ignored.")
-        return error, offset
-
-    # optional flags
-    unusual = 'unusual' in win and win['unusual'].lower() == "true"
-    win['genbyte-write'] = ('byte-write' in win and
-                            win['byte-write'].lower() == "true")
-
-    if 'validbits' in win:
-        wid, err = check_int(win['validbits'], name + " validbits")
-        if err:
-            error += err
-            wid = regwidth
-        if wid > regwidth:
-            error += 1
-            log.error(name + ": validbits " + str(wid) +
-                      " is greater than regwidth (" + str(regwidth) + ").")
-            wid = regwidth
-        win['genvalidbits'] = wid
-    else:
-        win['genvalidbits'] = regwidth
-
-    param, err = resolve_value(win['items'], top['param_list'])
-    error += err
-
-    winitems, err = check_int(param, name + " items")
-
-    if err:
-        error += err
-        winitems = 4
-
-    win['items'] = str(winitems)
-
-    # convert items to bytes
-    winsize = winitems * (regwidth // 8)
-    # if size is not a power of two, po2_size is next po2 larger
-    po2_size = 1 << (winsize.bit_length() - 1)
-    if winsize != po2_size:
-        # the -1 above was wrong if not a power of two
-        po2_size = po2_size << 1
-        if not unusual:
-            log.warn(name + ": Unusual: Size " + str(winitems) +
-                     " is not a power of 2.")
-
-    # Align to ensure base address of first item in window has
-    # all zeros in the low bits
-    if (offset & (po2_size - 1)) != 0:
-        genoff = (offset | (po2_size - 1)) + 1
-    else:
-        genoff = offset
-    nextoff = genoff + winsize
-    win['genoffset'] = genoff
-
-    swaccess = win['swaccess']
-    if swaccess not in SWACCESS_PERMITTED:
-        log.warn(name + ": Bad window swaccess value " + swaccess)
-        swaccess = "wo"
-    swacc_info = SWACCESS_PERMITTED[swaccess]
-    win['genswaccess'] = swacc_info[1]
-    win['genswwraccess'] = swacc_info[2]
-    win['genswrdaccess'] = swacc_info[3]
-    if not swacc_info[4] and not unusual:
-        log.warn(name + ": Unusual: access type for a window " + swaccess)
-
-    return error, nextoff
-
-
 """ Check that terms specified for regwen exist
 
 Regwen are all assumed to be individual registers.
@@ -953,9 +784,24 @@
             continue
 
         if 'window' in x:
-            err, offset = validate_window(x['window'], offset, fullwidth, regs)
-            error += err
-            vld_regs.append(x)
+            try:
+                window = Window.from_raw(offset, fullwidth,
+                                         regs.get('param_list', []),
+                                         x['window'])
+                vld_regs.append(window)
+                offset = window.offset + window.size_in_bytes
+
+                if window.name is not None:
+                    if window.name in regs['genrnames']:
+                        log.error('Duplicate window name {!r} at offset {:#x}.'
+                                  .format(offset, window.name))
+                        error += 1
+                    regs['genrnames'].append(window.name.lower())
+            except ValueError as err:
+                log.error('Error in window at offset {:#x}: {}'
+                          .format(offset, err))
+                error += 1
+
             continue
 
         if 'multireg' in x:
@@ -965,7 +811,7 @@
                                           x['multireg'])
                 vld_regs.append(multi_reg)
                 for reg in multi_reg.regs:
-                    error += _upd_gennames(regs, offset, reg)
+                    error += _upd_regnames(regs, offset, reg)
                 offset += addrsep * len(multi_reg.regs)
             except ValueError as err:
                 log.error('Error in multireg at offset {:#x}: {}'
@@ -978,7 +824,7 @@
                                     regs.get('param_list', []),
                                     x)
             vld_regs.append(reg)
-            error += _upd_gennames(regs, offset, reg)
+            error += _upd_regnames(regs, offset, reg)
         except ValueError as err:
             log.error('Error in register at offset {:#x}: {}'
                       .format(offset, err))
diff --git a/util/reggen/window.py b/util/reggen/window.py
new file mode 100644
index 0000000..b151d85
--- /dev/null
+++ b/util/reggen/window.py
@@ -0,0 +1,162 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+from typing import Dict, List, Optional
+
+from .access import SWAccess
+from .lib import (check_keys, check_str, check_bool, check_int,
+                  expand_parameter)
+
+REQUIRED_FIELDS = {
+    'desc': ['t', "description of the window"],
+    'items': ['d', "size in fieldaccess width words of the window"],
+    'swaccess': ['s', "software access permitted"],
+}
+
+# TODO potential for additional optional to give more type info?
+# eg sram-hw-port: "none", "sync", "async"
+OPTIONAL_FIELDS = {
+    'name': ['s', "Name of the window"],
+    'byte-write': [
+        's', "True if byte writes are supported. "
+        "Defaults to false if not present."
+    ],
+    'validbits': [
+        'd', "Number of valid data bits within "
+        "regwidth sized word. "
+        "Defaults to regwidth. If "
+        "smaller than the regwidth then in each "
+        "word of the window bits "
+        "[regwidth-1:validbits] are unused and "
+        "bits [validbits-1:0] are valid."
+    ],
+    'unusual': [
+        's', "True if window has unusual parameters "
+        "(set to prevent Unusual: errors)."
+        "Defaults to false if not present."
+    ]
+}
+
+
+class Window:
+    '''A class representing a memory window'''
+    def __init__(self,
+                 name: Optional[str],
+                 desc: str,
+                 unusual: bool,
+                 byte_write: bool,
+                 validbits: int,
+                 items: int,
+                 size_in_bytes: int,
+                 offset: int,
+                 swaccess: SWAccess):
+        assert 0 < validbits
+        assert 0 < items <= size_in_bytes
+
+        self.name = name
+        self.desc = desc
+        self.unusual = unusual
+        self.byte_write = byte_write
+        self.validbits = validbits
+        self.items = items
+        self.size_in_bytes = size_in_bytes
+        self.offset = offset
+        self.swaccess = swaccess
+
+        # Check that offset has been adjusted so that the first item in the
+        # window has all zeros in the low bits.
+        po2_size = 1 << (self.size_in_bytes - 1).bit_length()
+        assert not (offset & (po2_size - 1))
+
+    @staticmethod
+    def from_raw(offset: int,
+                 reg_width: int,
+                 params: List[Dict[str, object]],
+                 raw: object) -> 'Window':
+        rd = check_keys(raw, 'window',
+                        list(REQUIRED_FIELDS.keys()),
+                        list(OPTIONAL_FIELDS.keys()))
+
+        r_name = rd.get('name')
+        wind_desc = 'window at offset {:#x}'.format(offset)
+        if r_name is None:
+            name = None
+        else:
+            name = check_str(r_name, wind_desc)
+            wind_desc = '{!r} {}'.format(name, wind_desc)
+
+        desc = check_str(rd['desc'], 'desc field for ' + wind_desc)
+
+        unusual = check_bool(rd.get('unusual', False),
+                             'unusual field for ' + wind_desc)
+        byte_write = check_bool(rd.get('byte-write', False),
+                                'byte-write field for ' + wind_desc)
+
+        validbits = check_int(rd.get('validbits', reg_width),
+                              'validbits field for ' + wind_desc)
+        if validbits <= 0:
+            raise ValueError('validbits field for {} is not positive.'
+                             .format(wind_desc))
+        if validbits > reg_width:
+            raise ValueError('validbits field for {} is {}, '
+                             'which is greater than {}, the register width.'
+                             .format(wind_desc, validbits, reg_width))
+
+        r_items = check_str(rd['items'], 'items field for ' + wind_desc)
+        items = expand_parameter(params, r_items,
+                                 'expanding items field for ' + wind_desc)
+        if items <= 0:
+            raise ValueError("Items field for {} is {}, "
+                             "which isn't positive."
+                             .format(wind_desc, items))
+
+        assert reg_width % 8 == 0
+        size_in_bytes = items * (reg_width // 8)
+
+        # Round size_in_bytes up to the next power of 2. The calculation is
+        # like clog2 calculations in SystemVerilog, where we start with the
+        # last index, rather than the number of elements.
+        assert size_in_bytes > 0
+        po2_size = 1 << (size_in_bytes - 1).bit_length()
+
+        # A size that isn't a power of 2 is not allowed unless the unusual flag
+        # is set.
+        if po2_size != size_in_bytes and not unusual:
+            raise ValueError('Items field for {} is {}, which gives a size of '
+                             '{} bytes. This is not a power of 2 (next power '
+                             'of 2 is {}). If you want to do this even so, '
+                             'set the "unusual" flag.'
+                             .format(wind_desc, items,
+                                     size_in_bytes, po2_size))
+
+        # Adjust offset if necessary to make sure the base address of the first
+        # item in the window has all zeros in the low bits.
+        addr_mask = po2_size - 1
+        if offset & addr_mask:
+            offset = (offset | addr_mask) + 1
+        offset = offset
+
+        swaccess = SWAccess(wind_desc, rd['swaccess'])
+        if not (swaccess.value[4] or unusual):
+            raise ValueError('swaccess field for {} is {}, which is an '
+                             'unusual access type for a window. If you want '
+                             'to do this, set the "unusual" flag.'
+                             .format(wind_desc, swaccess.key))
+
+        return Window(name, desc, unusual, byte_write,
+                      validbits, items, size_in_bytes, offset, swaccess)
+
+    def _asdict(self) -> Dict[str, object]:
+        rd = {
+            'desc': self.desc,
+            'items': self.items,
+            'swaccess': self.swaccess.key,
+            'byte-write': self.byte_write,
+            'validbits': self.validbits,
+            'unusual': self.unusual
+        }
+        if self.name is not None:
+            rd['name'] = self.name
+
+        return {'window': rd}
diff --git a/util/topgen.py b/util/topgen.py
index ed48deb..52cb48e 100755
--- a/util/topgen.py
+++ b/util/topgen.py
@@ -19,10 +19,10 @@
 from mako.template import Template
 
 import tlgen
-from reggen import gen_dv, gen_rtl, validate
+from reggen import access, gen_dv, gen_rtl, validate, window
 from topgen import amend_clocks, get_hjsonobj_xbars
 from topgen import intermodule as im
-from topgen import merge_top, search_ips, check_flash, validate_top
+from topgen import merge_top, search_ips, validate_top
 from topgen.c import TopGenC
 
 # Common header for generated files
@@ -830,21 +830,28 @@
     for ip_obj in ip_objs:
         top_block.blocks.append(gen_rtl.json_to_reg(ip_obj))
 
+    assert top_block.width % 8 == 0
+    reg_width_in_bytes = top_block.width // 8
+
     # add memories
-    if "memory" in top.keys():
-        for item in list(top["memory"]):
-            mem = gen_rtl.Window()
-            mem.name = item["name"]
-            mem.base_addr = int(item["base_addr"], 0)
-            mem.limit_addr = int(item["base_addr"], 0) + int(item["size"], 0)
-            mem.byte_write = ('byte_write' in item and
-                              item["byte_write"].lower() == "true")
-            if "swaccess" in item.keys():
-                mem.dvrights = item["swaccess"]
-            else:
-                mem.dvrights = "RW"
-            mem.n_bits = top_block.width
-            top_block.wins.append(mem)
+    for item in list(top.get("memory", [])):
+        byte_write = ('byte_write' in item and
+                      item["byte_write"].lower() == "true")
+        size_in_bytes = int(item['size'], 0)
+        num_regs = size_in_bytes // reg_width_in_bytes
+        swaccess = access.SWAccess('top-level memory',
+                                   item.get('swaccess', 'rw'))
+
+        mem = window.Window(name=item['name'],
+                            desc='(generated from top-level)',
+                            unusual=False,
+                            byte_write=byte_write,
+                            validbits=top_block.width,
+                            items=num_regs,
+                            size_in_bytes=size_in_bytes,
+                            offset=int(item["base_addr"], 0),
+                            swaccess=swaccess)
+        top_block.wins.append(mem)
 
     # get sub-block base addresses, instance names from top cfg
     for block in top_block.blocks:
@@ -854,7 +861,7 @@
 
     # sort by the base_addr of 1st instance of the block
     top_block.blocks.sort(key=lambda block: next(iter(block.base_addr))[1])
-    top_block.wins.sort(key=lambda win: win.base_addr)
+    top_block.wins.sort(key=lambda win: win.offset)
 
     # generate the top ral model with template
     gen_dv.gen_ral(top_block, dv_base_prefix, str(out_path))