fuzzers: Add fuzzer for register REGSET and SD

Signed-off-by: David Shah <davey1576@gmail.com>
diff --git a/fuzzers/005-reg_config/empty.ncl b/fuzzers/005-reg_config/empty.ncl
new file mode 100644
index 0000000..b9caf7a
--- /dev/null
+++ b/fuzzers/005-reg_config/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+      architecture sa5p00;
+      device LFE5U-25F;
+      package CABGA381;
+      performance "8";
+   }
+
+}
diff --git a/fuzzers/005-reg_config/fuzzer.py b/fuzzers/005-reg_config/fuzzer.py
new file mode 100644
index 0000000..3326bab
--- /dev/null
+++ b/fuzzers/005-reg_config/fuzzer.py
@@ -0,0 +1,30 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2REG", family="ECP5", device="LFE5U-25F", ncl="empty.ncl", tiles=["R19C33:PLC2"])
+
+
+def main():
+    pytrellis.load_database("../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "reg.ncl"
+
+    def per_slice(slicen):
+        for r in range(2):
+            def get_substs(regset="RESET", sd="0"):
+                return dict(slice=slicen, r=str(r), regset=regset, sd=sd, gsr="DISABLED")
+
+            nonrouting.fuzz_enum_setting(cfg, "SLICE{}.REG{}.REGSET".format(slicen, r), ["RESET", "SET"], lambda x: get_substs(regset=x),
+                                         empty_bitfile)
+            nonrouting.fuzz_enum_setting(cfg, "SLICE{}.REG{}.SD".format(slicen, r), ["0", "1"], lambda x: get_substs(sd=x),
+                                         empty_bitfile)
+    fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/005-reg_config/reg.ncl b/fuzzers/005-reg_config/reg.ncl
new file mode 100644
index 0000000..03032dc
--- /dev/null
+++ b/fuzzers/005-reg_config/reg.ncl
@@ -0,0 +1,31 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+      architecture sa5p00;
+      device LFE5U-25F;
+      package CABGA381;
+      performance "8";
+   }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "REG${r}:::REGSET=${regset}:SD=${sd} "
+                 "Q${r}:Q "
+                 "GSR:${gsr} "
+                 "CLKMUX:CLK "
+                 "CEMUX:1 "
+                 "LSRMUX:LSR "
+                 "SRMODE:LSR_OVER_CE "
+                 "M0MUX:M0 ";
+         primitive REG${r} q_6;
+      }
+      site R19C33${slice};
+   }
+
+}
diff --git a/libtrellis/include/BitDatabase.hpp b/libtrellis/include/BitDatabase.hpp
index 8a2b9ee..8687240 100644
--- a/libtrellis/include/BitDatabase.hpp
+++ b/libtrellis/include/BitDatabase.hpp
@@ -169,6 +169,10 @@
     map<string, BitGroup> options;
     boost::optional<string> defval;
 
+    // Needed for Python
+    void set_defval(string val);
+    string get_defval() const;
+
     // Get the value of the enumeration, returning empty if not set or set to default, if default is non-empty
     boost::optional<string>
     get_value(const CRAMView &tile, boost::optional<BitSet &> coverage = boost::optional<BitSet &>()) const;
diff --git a/libtrellis/src/BitDatabase.cpp b/libtrellis/src/BitDatabase.cpp
index 150e566..472535e 100644
--- a/libtrellis/src/BitDatabase.cpp
+++ b/libtrellis/src/BitDatabase.cpp
@@ -181,6 +181,19 @@
     return in;
 }
 
+void EnumSettingBits::set_defval(string val) {
+    defval = val;
+}
+
+
+string EnumSettingBits::get_defval() const {
+    if (defval)
+        return *defval;
+    else
+        return "";
+}
+
+
 boost::optional<string> EnumSettingBits::get_value(const CRAMView &tile, boost::optional<BitSet &> coverage) const {
     auto found = find_if(options.begin(), options.end(), [tile](const pair<string, BitGroup> &kv) {
         return kv.second.match(tile);
diff --git a/libtrellis/src/PyTrellis.cpp b/libtrellis/src/PyTrellis.cpp
index 6e381cc..365a92d 100644
--- a/libtrellis/src/PyTrellis.cpp
+++ b/libtrellis/src/PyTrellis.cpp
@@ -203,7 +203,7 @@
     class_<EnumSettingBits>("EnumSettingBits")
             .def_readwrite("name", &EnumSettingBits::name)
             .def_readwrite("options", &EnumSettingBits::options)
-            .def_readwrite("defval", &EnumSettingBits::defval)
+            .add_property("defval", &EnumSettingBits::get_defval, &EnumSettingBits::set_defval)
             .def("get_value", &EnumSettingBits::get_value)
             .def("set_value", &EnumSettingBits::set_value);
 
diff --git a/util/fuzz/nonrouting.py b/util/fuzz/nonrouting.py
index 43ea0ca..7101291 100644
--- a/util/fuzz/nonrouting.py
+++ b/util/fuzz/nonrouting.py
@@ -53,3 +53,59 @@
         if not is_empty[t]:
             tile_dbs[t].add_setting_word(wsb[t])
             tile_dbs[t].save()
+
+
+def fuzz_enum_setting(config, name, values, get_ncl_substs, empty_bitfile = None):
+    """
+    Fuzz a setting with multiple possible values
+
+    :param config: FuzzConfig instance containing target device and tile of interest
+    :param name: name of the setting to store in the database
+    :param values: list of values taken by the enum
+    :param get_ncl_substs: a callback function, that is first called with an array of bits to create a design with that setting
+    :param empty_bitfile: a path to a bit file without the parameter included, optional, which is used to determine the
+    default value
+    """
+    prefix = "thread{}_".format(threading.get_ident())
+    tile_dbs = {tile: pytrellis.get_tile_bitdata(
+        pytrellis.TileLocator(config.family, config.device, tiles.type_from_fullname(tile))) for tile in
+        config.tiles}
+    if empty_bitfile is not None:
+        none_chip = pytrellis.Bitstream.read_bit(empty_bitfile).deserialise_chip()
+    else:
+        none_chip = None
+
+    changed_bits = set()
+    chips = {}
+    tiles_changed = set()
+    for val in values:
+        bit_bitf = config.build_design(config.ncl, get_ncl_substs(val), prefix)
+        bit_chip = pytrellis.Bitstream.read_bit(bit_bitf).deserialise_chip()
+        for prev in chips.values():
+            diff = bit_chip - prev
+            for tile in config.tiles:
+                if tile in diff:
+                    tiles_changed.add(tile)
+                    for bit in diff[tile]:
+                        changed_bits.add((tile, bit.frame, bit.bit))
+        chips[val] = bit_chip
+
+    for tile in tiles_changed:
+        esb = pytrellis.EnumSettingBits()
+        esb.name = name
+        for val in values:
+            bg = pytrellis.BitGroup()
+            for (btile, bframe, bbit) in changed_bits:
+                if btile == tile:
+                    state = chips[val].tiles[tile].cram.bit(bframe, bbit)
+                    cb = pytrellis.ConfigBit()
+                    cb.frame = bframe
+                    cb.bit = bbit
+                    cb.inv = (state == 0)
+                    bg.bits.append(cb)
+            esb.options[val] = bg
+            if none_chip is not None and bg.match(none_chip.tiles[tile].cram):
+                esb.defval = val
+        tile_dbs[tile].add_setting_enum(esb)
+        tile_dbs[tile].save()
+