Merge pull request #148 from cr1901/facade

Initial MachXO2 Support
diff --git a/database b/database
index 09d1bbd..70daef1 160000
--- a/database
+++ b/database
@@ -1 +1 @@
-Subproject commit 09d1bbd72f1a2350881139473a814614435429b4
+Subproject commit 70daef119890f14dcfa7e061ce98196e53babf4f
diff --git a/devices.json b/devices.json
index 61eaaa8..fece67c 100644
--- a/devices.json
+++ b/devices.json
@@ -120,6 +120,83 @@
                 "max_row" : 95,
                 "max_col" : 126,
                 "col_bias" : 0,
+                "fuzz": 0
+            }
+        }
+    },
+
+    "MachXO2" : {
+        "devices" : {
+            "LCMXO2-256HC": {
+                "packages": ["QFN32"],
+                "idcode": "0x012b8043",
+                "frames": 186,
+                "bits_per_frame": 504,
+                "pad_bits_after_frame": 0,
+                "pad_bits_before_frame": 0,
+                "max_row" : 7,
+                "max_col" : 9,
+                "col_bias" : 1,
+                "fuzz": 1
+            },
+            "LCMXO2-640HC": {
+                "packages": ["QFN48"],
+                "idcode": "0x012b9043",
+                "frames": 211,
+                "bits_per_frame": 888,
+                "pad_bits_after_frame": 0,
+                "pad_bits_before_frame": 0,
+                "max_row" : 8,
+                "max_col" : 17,
+                "col_bias" : 1,
+                "fuzz": 1
+            },
+            "LCMXO2-1200HC": {
+                "packages": ["QFN32"],
+                "idcode": "0x012ba043",
+                "frames": 333,
+                "bits_per_frame": 1080,
+                "pad_bits_after_frame": 0,
+                "pad_bits_before_frame": 0,
+                "max_row" : 12,
+                "max_col" : 21,
+                "col_bias" : 1,
+                "fuzz": 1
+            },
+            "LCMXO2-2000HC": {
+                "packages": ["TQFP100"],
+                "idcode": "0x012bb043",
+                "frames": 420,
+                "bits_per_frame": 1272,
+                "pad_bits_after_frame": 0,
+                "pad_bits_before_frame": 0,
+                "max_row" : 15,
+                "max_col" : 25,
+                "col_bias" : 1,
+                "fuzz": 1
+            },
+            "LCMXO2-4000HC": {
+                "packages": ["TQFP144"],
+                "idcode": "0x012bc043",
+                "frames": 623,
+                "bits_per_frame": 1560,
+                "pad_bits_after_frame": 0,
+                "pad_bits_before_frame": 0,
+                "max_row" : 22,
+                "max_col" : 31,
+                "col_bias" : 1,
+                "fuzz": 1
+            },
+            "LCMXO2-7000HC": {
+                "packages": ["TQFP144"],
+                "idcode": "0x012bd043",
+                "frames": 770,
+                "bits_per_frame": 1992,
+                "pad_bits_after_frame": 0,
+                "pad_bits_before_frame": 0,
+                "max_row" : 26,
+                "max_col" : 40,
+                "col_bias" : 1,
                 "fuzz": 1
             }
         }
diff --git a/diamond.sh b/diamond.sh
index b5ab3e5..6a69b2e 100755
--- a/diamond.sh
+++ b/diamond.sh
@@ -129,6 +129,11 @@
 		DEVICE="LCMXO2-256HC"
 		LSE_ARCH="MachXO2"
 		;;
+	LCMXO2-640HC)
+		PACKAGE="${DEV_PACKAGE:-QFN48}"
+		DEVICE="LCMXO2-640HC"
+		LSE_ARCH="MachXO2"
+		;;
 	LCMXO2-1200HC)
 		PACKAGE="${DEV_PACKAGE:-QFN32}"
 		DEVICE="LCMXO2-1200HC"
@@ -139,6 +144,11 @@
 		DEVICE="LCMXO2-2000HC"
 		LSE_ARCH="MachXO2"
 		;;
+	LCMXO2-4000HC)
+		PACKAGE="${DEV_PACKAGE:-TQFP144}"
+		DEVICE="LCMXO2-4000HC"
+		LSE_ARCH="MachXO2"
+		;;
 	LCMXO2-7000HC)
 		PACKAGE="${DEV_PACKAGE:-TQFP144}"
 		DEVICE="LCMXO2-7000HC"
diff --git a/examples/tinyfpga_ax/.gitignore b/examples/tinyfpga_ax/.gitignore
new file mode 100644
index 0000000..26ec132
--- /dev/null
+++ b/examples/tinyfpga_ax/.gitignore
@@ -0,0 +1,7 @@
+*.dump
+*.twr
+*.txt
+*.ncl
+*.tmp
+*.jed
+*.hex
diff --git a/examples/tinyfpga_ax/Makefile b/examples/tinyfpga_ax/Makefile
new file mode 100644
index 0000000..140e6e4
--- /dev/null
+++ b/examples/tinyfpga_ax/Makefile
@@ -0,0 +1,48 @@
+PROJ ?= blinky
+TRELLIS_ROOT ?= ../..
+export DEV_PACKAGE ?= QFN32
+export JEDEC_BITSTREAM ?= 1
+export COMPRESSED_BITSTREAM ?= 1
+
+all: ${PROJ}.bit ${PROJ}-nextpnr.bit ${PROJ}.txt ${PROJ}-nextpnr.txt
+
+# Get proportion of known bits.
+stats:
+	@python3 -c "import sys; print(\"{:.3f}\".format(int(sys.argv[1]) / int(sys.argv[2])))" \
+		`grep -e arc -e word -e enum ${PROJ}.txt | wc -l` \
+		`grep -e arc -e word -e enum -e unknown  ${PROJ}.txt | wc -l`
+
+# Avoid intermediate files from being deleted.
+.PRECIOUS: %.txt %.bit %-roundtrip.txt %-roundtrip.bit %-comp.txt %-comp-roundtrip.bit
+
+# Test that a bitstream from Diamond survives a round-trip from .bit to .txt
+# back to .bit and .txt again.
+%-roundtrip.bit: %.txt
+	ecppack --db ${TRELLIS_ROOT}/database $< $@
+
+%-comp-roundtrip.bit: %-comp.txt
+	ecppack --db ${TRELLIS_ROOT}/database --compress $< $@
+
+%.txt: %.bit
+	ecpunpack --db ${TRELLIS_ROOT}/database --input $< --textcfg $@
+
+# Diamond rules.
+%.bit: %.v %.lpf
+	${TRELLIS_ROOT}/diamond.sh LCMXO2-1200HC ${PROJ}.v
+
+# FOSS rules.
+# yosys
+%.json: %.v %.lpf
+	@true
+
+# ecppack --db ${TRELLIS_ROOT}/database --input $< $@
+%-nextpnr.bit: %-nextpnr.txt
+	@true
+
+# nextpnr-generic
+${PROJ}-nextpnr.txt: ${PROJ}.json
+	@true
+
+clean:
+	rm -rf ${PROJ}.tmp ${PROJ}_out.ncl ${PROJ}*.bit ${PROJ}.jed ${PROJ}.dump \
+		${PROJ}.twr ${PROJ}*.txt ${PROJ}.json ${PROJ}-nextpnr.* ${PROJ}*.hex
diff --git a/examples/tinyfpga_ax/blinky.lpf b/examples/tinyfpga_ax/blinky.lpf
new file mode 100644
index 0000000..18782c5
--- /dev/null
+++ b/examples/tinyfpga_ax/blinky.lpf
@@ -0,0 +1,3 @@
+BLOCK RESETPATHS ;
+BLOCK ASYNCPATHS ;
+LOCATE COMP "pin1" SITE "13" ;
diff --git a/examples/tinyfpga_ax/blinky.v b/examples/tinyfpga_ax/blinky.v
new file mode 100644
index 0000000..49ed054
--- /dev/null
+++ b/examples/tinyfpga_ax/blinky.v
@@ -0,0 +1,26 @@
+// Modified from:
+// https://github.com/tinyfpga/TinyFPGA-A-Series/tree/master/template_a2
+
+module TinyFPGA_A2 (
+  inout pin1
+);
+
+
+  wire clk;
+
+  OSCH #(
+    .NOM_FREQ("2.08")
+  ) internal_oscillator_inst (
+    .STDBY(1'b0),
+    .OSC(clk)
+  );
+
+  reg [23:0] led_timer;
+
+  always @(posedge clk) begin
+    led_timer <= led_timer + 1;
+  end
+
+  // left side of board
+  assign pin1 = led_timer[23];
+endmodule
diff --git a/examples/tinyfpga_ax/uart.lpf b/examples/tinyfpga_ax/uart.lpf
new file mode 100644
index 0000000..48e84d4
--- /dev/null
+++ b/examples/tinyfpga_ax/uart.lpf
@@ -0,0 +1,10 @@
+BLOCK RESETPATHS ;
+BLOCK ASYNCPATHS ;
+LOCATE COMP "serial_rx" SITE "13" ;
+LOCATE COMP "serial_tx" SITE "14" ;
+LOCATE COMP "user_led" SITE "16" ;
+LOCATE COMP "user_led_1" SITE "17" ;
+LOCATE COMP "user_led_2" SITE "20" ;
+LOCATE COMP "user_led_3" SITE "21" ;
+LOCATE COMP "user_led_4" SITE "23" ;
+LOCATE COMP "clk12" SITE "25" ;
diff --git a/examples/tinyfpga_ax/uart.v b/examples/tinyfpga_ax/uart.v
new file mode 100644
index 0000000..0d76baa
--- /dev/null
+++ b/examples/tinyfpga_ax/uart.v
@@ -0,0 +1,207 @@
+/* Example UART derived from: https://github.com/cr1901/migen_uart.
+   Requires 12MHz clock and runs at 19,200 baud. */
+
+/* Machine-generated using Migen */
+module top(
+	input serial_rx,
+	output serial_tx,
+	output user_led,
+	output user_led_1,
+	output user_led_2,
+	output user_led_3,
+	output user_led_4,
+	input clk12
+);
+
+wire [7:0] out_data;
+wire [7:0] in_data;
+wire tx;
+wire rx;
+reg wr = 1'd0;
+reg rd = 1'd0;
+wire tx_empty;
+wire rx_empty;
+wire tx_ov;
+wire rx_ov;
+wire sout_load;
+wire [7:0] sout_out_data;
+wire sout_shift;
+reg sout_empty = 1'd1;
+reg sout_overrun = 1'd0;
+reg [3:0] sout_count = 4'd0;
+reg [9:0] sout_reg = 10'd0;
+reg sout_tx;
+wire sin_rx;
+wire sin_shift;
+wire sin_take;
+reg [7:0] sin_in_data = 8'd0;
+wire sin_edge;
+reg sin_empty = 1'd1;
+reg sin_busy = 1'd0;
+reg sin_overrun = 1'd0;
+reg sin_sync_rx = 1'd0;
+reg [8:0] sin_reg = 9'd0;
+reg sin_rx_prev = 1'd0;
+reg [3:0] sin_count = 4'd0;
+wire out_active;
+wire in_active;
+reg shift_out_strobe = 1'd0;
+reg shift_in_strobe = 1'd0;
+reg [9:0] in_counter = 10'd0;
+reg [9:0] out_counter = 10'd0;
+wire sys_clk;
+wire sys_rst;
+wire por_clk;
+reg int_rst = 1'd1;
+
+
+// Adding a dummy event (using a dummy signal 'dummy_s') to get the simulator
+// to run the combinatorial process once at the beginning.
+// synthesis translate_off
+reg dummy_s;
+initial dummy_s <= 1'd0;
+// synthesis translate_on
+
+assign user_led_1 = (~serial_tx);
+assign user_led = (~serial_rx);
+assign user_led_2 = sout_load;
+assign user_led_3 = sin_take;
+assign user_led_4 = sin_empty;
+assign serial_tx = tx;
+assign rx = serial_rx;
+assign out_data = in_data;
+assign in_data = sin_in_data;
+assign sout_out_data = out_data;
+assign sin_take = rd;
+assign sout_load = wr;
+assign tx = sout_tx;
+assign sin_rx = rx;
+assign tx_empty = sout_empty;
+assign rx_empty = sin_empty;
+assign tx_ov = sout_overrun;
+assign rx_ov = sin_overrun;
+assign sout_shift = shift_out_strobe;
+assign sin_shift = shift_in_strobe;
+assign out_active = (~sout_empty);
+assign in_active = sin_busy;
+
+// synthesis translate_off
+reg dummy_d;
+// synthesis translate_on
+always @(*) begin
+	sout_tx <= 1'd0;
+	if (sout_empty) begin
+		sout_tx <= 1'd1;
+	end else begin
+		sout_tx <= sout_reg[0];
+	end
+// synthesis translate_off
+	dummy_d <= dummy_s;
+// synthesis translate_on
+end
+assign sin_edge = ((sin_rx_prev == 1'd1) & (sin_sync_rx == 1'd0));
+assign sys_clk = clk12;
+assign por_clk = clk12;
+assign sys_rst = int_rst;
+
+always @(posedge por_clk) begin
+	int_rst <= 1'd0;
+end
+
+always @(posedge sys_clk) begin
+	wr <= 1'd0;
+	rd <= 1'd0;
+	if ((~sin_empty)) begin
+		wr <= 1'd1;
+		rd <= 1'd1;
+	end
+	if (sout_load) begin
+		if (sout_empty) begin
+			sout_reg[0] <= 1'd0;
+			sout_reg[8:1] <= sout_out_data;
+			sout_reg[9] <= 1'd1;
+			sout_empty <= 1'd0;
+			sout_overrun <= 1'd0;
+			sout_count <= 1'd0;
+		end else begin
+			sout_overrun <= 1'd1;
+		end
+	end
+	if (((~sout_empty) & sout_shift)) begin
+		sout_reg[8:0] <= sout_reg[9:1];
+		sout_reg[9] <= 1'd0;
+		if ((sout_count == 4'd9)) begin
+			sout_empty <= 1'd1;
+			sout_count <= 1'd0;
+		end else begin
+			sout_count <= (sout_count + 1'd1);
+		end
+	end
+	sin_sync_rx <= sin_rx;
+	sin_rx_prev <= sin_sync_rx;
+	if (sin_take) begin
+		sin_empty <= 1'd1;
+		sin_overrun <= 1'd0;
+	end
+	if (((~sin_busy) & sin_edge)) begin
+		sin_busy <= 1'd1;
+	end
+	if ((sin_shift & sin_busy)) begin
+		sin_reg[8] <= sin_sync_rx;
+		sin_reg[7:0] <= sin_reg[8:1];
+		if ((sin_count == 4'd9)) begin
+			sin_in_data <= sin_reg[8:1];
+			sin_count <= 1'd0;
+			sin_busy <= 1'd0;
+			if ((~sin_empty)) begin
+				sin_overrun <= 1'd1;
+			end else begin
+				sin_empty <= 1'd0;
+			end
+		end else begin
+			sin_count <= (sin_count + 1'd1);
+		end
+	end
+	out_counter <= 1'd0;
+	in_counter <= 1'd0;
+	if (in_active) begin
+		shift_in_strobe <= 1'd0;
+		in_counter <= (in_counter + 1'd1);
+		if ((in_counter == 9'd311)) begin
+			shift_in_strobe <= 1'd1;
+		end
+		if ((in_counter == 10'd623)) begin
+			in_counter <= 1'd0;
+		end
+	end
+	if (out_active) begin
+		shift_out_strobe <= 1'd0;
+		out_counter <= (out_counter + 1'd1);
+		if ((out_counter == 10'd623)) begin
+			out_counter <= 1'd0;
+			shift_out_strobe <= 1'd1;
+		end
+	end
+	if (sys_rst) begin
+		wr <= 1'd0;
+		rd <= 1'd0;
+		sout_empty <= 1'd1;
+		sout_overrun <= 1'd0;
+		sout_count <= 4'd0;
+		sout_reg <= 10'd0;
+		sin_in_data <= 8'd0;
+		sin_empty <= 1'd1;
+		sin_busy <= 1'd0;
+		sin_overrun <= 1'd0;
+		sin_sync_rx <= 1'd0;
+		sin_reg <= 9'd0;
+		sin_rx_prev <= 1'd0;
+		sin_count <= 4'd0;
+		shift_out_strobe <= 1'd0;
+		shift_in_strobe <= 1'd0;
+		in_counter <= 10'd0;
+		out_counter <= 10'd0;
+	end
+end
+
+endmodule
diff --git a/experiments/.gitignore b/experiments/.gitignore
index eead56a..3887734 100644
--- a/experiments/.gitignore
+++ b/experiments/.gitignore
@@ -6,4 +6,5 @@
 *.bin
 *.tmp/
 work/
-*_out.txt
\ No newline at end of file
+*_out.txt
+*_filter.txt
diff --git a/experiments/machxo2/ccu2_mux/ccu2_mux.py b/experiments/machxo2/ccu2_mux/ccu2_mux.py
new file mode 100644
index 0000000..316a43f
--- /dev/null
+++ b/experiments/machxo2/ccu2_mux/ccu2_mux.py
@@ -0,0 +1,48 @@
+import diamond
+from string import Template
+import pytrellis
+import shutil
+import os
+
+# With this experiment, I concluded that CCU2 doesn't really control any bits.
+# This experiment doesn't run as-is- add.bit/sub.bit are missing. I may re-add
+# them later.
+
+device = "LCMXO2-1200HC"
+
+def run_get_tiles(muxcfg):
+    with open("ccu2_template.ncl", "r") as inf:
+        with open("work/ccu2.ncl", "w") as ouf:
+            ouf.write(Template(inf.read()).substitute(muxcfg=muxcfg))
+    diamond.run(device, "work/ccu2.ncl")
+    bs = pytrellis.Bitstream.read_bit("work/ccu2.bit")
+    chip = bs.deserialise_chip()
+    return chip.tiles
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    shutil.rmtree("work", ignore_errors=True)
+    os.mkdir("work")
+    baseline = run_get_tiles("::B0=0,C0=0,D0=0,A1=0,B1=0,C1=0,D1=0 ")
+
+    # baseline = pytrellis.Bitstream.read_bit("../../../minitests/math/add.bit").deserialise_chip().tiles
+    # modified = pytrellis.Bitstream.read_bit("../../../minitests/math/sub.bit").deserialise_chip().tiles
+
+    with open("ccu2_diff.txt", "w") as f:
+        for m in ["::A0=0,B0=0,C0=0,D0=0,B1=0,C1=0,D1=0 "]:
+            modified = run_get_tiles(m)
+
+        tile_keys = []
+        for t in modified:
+            tile_keys.append(t.key())
+
+        for k in tile_keys:
+            diff = modified[k].cram - baseline[k].cram
+            diff_str = ["{}F{}B{}".format("!" if b.delta < 0 else "", b.frame, b.bit) for b in diff]
+            print("{0: <18}{1}".format(k, " ".join(diff_str)), file=f)
+            f.flush()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/experiments/machxo2/ccu2_mux/ccu2_template.ncl b/experiments/machxo2/ccu2_mux/ccu2_template.ncl
new file mode 100644
index 0000000..d49ac03
--- /dev/null
+++ b/experiments/machxo2/ccu2_mux/ccu2_template.ncl
@@ -0,0 +1,25 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:CCU2 "
+                 "CCU2::S0=0xfaaa,S1=0xfaaa${muxcfg}"
+                 "FCO:FCO ";
+         primitive CCU2 "CCU";
+      }
+      site R10C11A;
+   }
+
+}
diff --git a/experiments/machxo2/center_mux/center_mux.py b/experiments/machxo2/center_mux/center_mux.py
new file mode 100644
index 0000000..2071eb7
--- /dev/null
+++ b/experiments/machxo2/center_mux/center_mux.py
@@ -0,0 +1,70 @@
+import diamond
+from string import Template
+import pytrellis
+import shutil
+import os
+
+device = "LCMXO2-1200HC"
+
+routes = [
+    ("R1C13_JCLK0", "R6C13_JPCLKCIBVIQT0"),
+    ("R6C13_JPCLKCIBVIQT0", "R6C13_PCLKCIBVIQT0"),
+    ("R6C13_PCLKCIBVIQT0", "R6C13_VPRXCLKI0"),
+    ("R6C13_VPRXCLKI0", "R6C13_CLKI0_DCC"),
+    ("R6C13_CLKI0_DCC", "R6C13_CLKO0_DCC"),
+    ("R6C13_CLKO0_DCC", "R6C13_VPRX0000"),
+    ("R6C13_VPRX0000", "R6C8_HPSX0000"),
+    ("R6C13_VPRX0000", "R6C18_HPSX0000"),
+    ("R6C13_JLPLLCLK1", "R6C13_VPRXCLKI0"),
+    ("R6C8_HPSX0000", "R6C10_CLKI0B_DCC"),
+    ("R6C10_CLKI0B_DCC", "R6C10_CLKO0B_DCC"),
+    ("R6C14_CLKI0B_DCC", "R6C14_CLKO0B_DCC"),
+    ("R6C13_JTCDIVX1", "R6C13_VPRXCLKI5"),
+    ("R6C13_PCLKCIBMID2", "R6C13_VPRXCLK60"),
+    ("R6C13_PCLKCIBMID3", "R6C13_VPRXCLK61"),
+    ("R6C13_PCLKCIBMID2", "R6C13_VPRXCLK71"),
+    ("R6C13_PCLKCIBMID3", "R6C13_VPRXCLK70"),
+    ("R6C13_JLPLLCLK0", "R6C13_EBRG0CLK0"),
+    ("R6C13_JPCLKT20", "R6C13_EBRG0CLK0")
+]
+
+def run_get_tiles(mux_driver, sink):
+    route = ""
+    if mux_driver != "":
+        route = "route\n\t\t\t" + mux_driver + "." + sink + ";"
+
+    with open("center_mux_template.ncl", "r") as inf:
+        with open("work/center_mux.ncl", "w") as ouf:
+            ouf.write(Template(inf.read()).substitute(route=route))
+    diamond.run(device, "work/center_mux.ncl")
+    bs = pytrellis.Bitstream.read_bit("work/center_mux.bit")
+    chip = bs.deserialise_chip()
+    return chip.tiles
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    shutil.rmtree("work", ignore_errors=True)
+    os.mkdir("work")
+    baseline = run_get_tiles("", "")
+
+    with open("center_mux_diff.txt", "w") as f:
+        for r in routes:
+            modified = run_get_tiles(r[0], r[1])
+
+            tile_keys = []
+            for t in modified:
+                tile_keys.append(t.key())
+
+            print("{0} -> {1}".format(r[0], r[1]), file=f)
+            for k in tile_keys:
+                diff = modified[k].cram - baseline[k].cram
+                diff_str = ["{}F{}B{}".format("!" if b.delta < 0 else "", b.frame, b.bit) for b in diff]
+                if not diff_str:
+                    continue
+                print("{0: <30}{1}".format(k, " ".join(diff_str)), file=f)
+                f.flush()
+            print("", file=f)
+
+if __name__ == "__main__":
+    main()
diff --git a/experiments/machxo2/center_mux/center_mux_template.ncl b/experiments/machxo2/center_mux/center_mux_template.ncl
new file mode 100644
index 0000000..3f9d75c
--- /dev/null
+++ b/experiments/machxo2/center_mux/center_mux_template.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, CLK);
+      ${route}
+   }
+}
diff --git a/experiments/machxo2/findnets/findnets.py b/experiments/machxo2/findnets/findnets.py
new file mode 100644
index 0000000..1db6ca9
--- /dev/null
+++ b/experiments/machxo2/findnets/findnets.py
@@ -0,0 +1,195 @@
+import sys
+from collections import defaultdict
+
+from fuzzconfig import FuzzConfig
+import pytrellis
+import isptcl
+import nets
+import argparse
+import re
+import itertools
+import pprint
+import inspect
+
+def net_mode(args):
+    pytrellis.load_database("../../../database")
+
+    cfg = FuzzConfig(job="FINDNETS_NETS_{}".format(args.nets[0]), family="MachXO2", device="LCMXO2-1200HC", ncl="plc2route.ncl", tiles=[])
+    cfg.setup()
+
+    arcs = isptcl.get_arcs_on_wires(cfg.ncd_prf, args.nets, False, defaultdict(lambda : str("mark")))
+
+    with open("{}_out.txt".format(args.nets[0]), "w") as fp:
+        for (k, v) in arcs.items():
+            print("{}:".format(k), file=fp)
+            for c in v:
+                if isinstance(c, isptcl.AmbiguousArc):
+                    print(str(c), file=fp)
+                else:
+                    print("{} --> {}".format(c[0], c[1]), file=fp)
+
+            fp.flush()
+            print("", file=fp)
+
+def pos_mode(args):
+    pytrellis.load_database("../../../database")
+
+    cfg = FuzzConfig(job="FINDNETS_R{}C{}".format(args.row, args.col), family="MachXO2", device="LCMXO2-1200HC", ncl="plc2route.ncl", tiles=[])
+    cfg.setup()
+
+    netdata = isptcl.get_wires_at_position(cfg.ncd_prf, (args.row, args.col))
+    netnames = [x[0] for x in netdata]
+    arcs = isptcl.get_arcs_on_wires(cfg.ncd_prf, netnames, False, defaultdict(lambda : str("mark")))
+
+    with open("r{}c{}_{}out.txt".format(args.row, args.col, "a_" if args.a else ""), "w")  as fp:
+        for (k, v) in arcs.items():
+            print("{}:".format(k), file=fp)
+            for c in v:
+                if isinstance(c, isptcl.AmbiguousArc):
+                    print(str(c), file=fp)
+                else:
+                    if not args.a:
+                        print("{} --> {}".format(c[0], c[1]), file=fp)
+
+            fp.flush()
+            print("", file=fp)
+
+def filter_mode(args):
+    span1_re = re.compile(r'R\d+C\d+_[VH]01[NESWTLBR]\d{4}')
+    location = (args.row, args.col)
+
+    def netname_predicate(net, netnames):
+        """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
+        left out by Tcl"""
+        return net in netnames or nets.machxo2.is_global(net) or span1_re.match(net)
+
+    def arc_predicate(arc, netnames):
+        return True
+
+    def fc_predicate(arc, netnames):
+        return True
+
+    pytrellis.load_database("../../../database")
+
+    cfg = FuzzConfig(job="FINDNETS_FILTER_R{}C{}".format(args.row, args.col), family="MachXO2", device="LCMXO2-1200HC", ncl="plc2route.ncl", tiles=[])
+    cfg.setup()
+
+    # fuzz_interconnect
+    netdata = isptcl.get_wires_at_position(cfg.ncd_prf, (args.row, args.col))
+    netnames = [x[0] for x in netdata]
+
+    extra_netnames = []
+    if args.s:
+        for net in netnames:
+            m = re.match("R(\d+)C(\d+)_V01N(\d{4})", net)
+            if m:
+                row = int(m.group(1))
+                col = int(m.group(2))
+                idn = m.group(3)
+                if row == location[0] + 1 and col == location[1]:
+                    fixednet = "R{}C{}_V01N{}".format(location[0] - 1, col, idn)
+                    print("added {}".format(fixednet))
+                    extra_netnames.append(fixednet)
+        netnames = extra_netnames + netnames
+
+    if args.f and not args.n:
+        netnames = list(filter(lambda x: netname_predicate(x, netnames), netnames))
+
+    # fuzz_interconnect_with_netnames
+    arcs = isptcl.get_arcs_on_wires(cfg.ncd_prf, netnames, not args.f, defaultdict(lambda : str("mark")))
+
+    def per_netname(net):
+        assoc_arcs = arcs[net]
+
+        if args.n:
+            filt_net_pred = list(itertools.filterfalse(lambda x: netname_predicate(x[0], netnames) and netname_predicate(x[1], netnames),
+                                assoc_arcs.copy()))
+            assoc_arcs = list(filter(lambda x: netname_predicate(x[0], netnames) and netname_predicate(x[1], netnames),
+                                assoc_arcs))
+        else:
+            filt_net_pred = list(itertools.filterfalse(lambda x: netname_predicate(x[0], netnames) or netname_predicate(x[1], netnames),
+                                assoc_arcs.copy()))
+            assoc_arcs = list(filter(lambda x: netname_predicate(x[0], netnames) or netname_predicate(x[1], netnames),
+                                assoc_arcs))
+
+        filt_arcs_pred = list(itertools.filterfalse(lambda x: arc_predicate(x, netnames), assoc_arcs.copy()))
+        fuzz_arcs = list(filter(lambda x: arc_predicate(x, netnames), assoc_arcs))
+
+        filt_fc_pred = list(itertools.filterfalse(lambda x: fc_predicate(x, netnames), fuzz_arcs.copy()))
+
+        return (fuzz_arcs, filt_net_pred, filt_arcs_pred, filt_fc_pred)
+
+    # Write to file, describing which nets did/didn't make the cut.
+    with open("r{}c{}_filter.txt".format(args.row, args.col), "w")  as fp:
+        def print_arc(arc):
+            if isinstance(arc, isptcl.AmbiguousArc):
+                print(str(arc), file=fp)
+            else:
+                print("{} --> {}".format(arc[0], arc[1]), file=fp)
+
+        print("Args: {}".format(vars(args)), file=fp)
+        print("", file=fp)
+
+        print("Extra nets (span 1s):", file=fp)
+        for net in extra_netnames:
+            print("{}:".format(net), file=fp)
+        print("", file=fp)
+
+        print("Filters:", file=fp)
+        print("Netname:\n{}".format(inspect.getsource(netname_predicate)), file=fp)
+        print("Arc:\n{}".format(inspect.getsource(arc_predicate)), file=fp)
+        print("FC:\n{}".format(inspect.getsource(fc_predicate)), file=fp)
+        print("", file=fp)
+
+        for net in netnames:
+            print("{}:".format(net), file=fp)
+
+            (fuzz, filt_net, filt_arc, filt_fc) = per_netname(net)
+            print("Arcs to fuzz:", file=fp)
+            for f in fuzz:
+                print_arc(f)
+            print("Arcs filtered by netname_predicate:", file=fp)
+            for f in filt_net:
+                print_arc(f)
+            print("Arcs filtered by arc_predicate:", file=fp)
+            for f in filt_arc:
+                print_arc(f)
+            print("Would be filtered by fc_predicate (if fixed connection):", file=fp)
+            for f in filt_fc:
+                print_arc(f)
+
+            fp.flush()
+            print("", file=fp)
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Find which nets IspTcl returns in various ways.")
+    subparsers = parser.add_subparsers()
+
+    parser_pos = subparsers.add_parser("pos", help="Return all nets based on position.")
+    parser_net = subparsers.add_parser("net", help="Return connections to one (or more) nets.")
+    parser_filt = subparsers.add_parser("filter", help="Return which nets, arcs, and fixed connections will be filtered out.")
+
+    parser_pos.add_argument("-a", action="store_true", help="Return arcs with ambiguous direction only.")
+    parser_pos.add_argument("row", type=int, help="Tile row.")
+    parser_pos.add_argument("col", type=int, help="Tile column.")
+    parser_pos.set_defaults(func=pos_mode)
+
+    parser_net.add_argument("nets", type=str, nargs="+", help="List of nets to find connections.")
+    parser_net.set_defaults(func=net_mode)
+
+    parser_filt.add_argument("-b", action="store_true", help="Simulate bidir (forcefully unset if -f is specified).")
+    parser_filt.add_argument("-s", action="store_true", help="Enable span-1 fix.")
+    parser_filt.add_argument("-n", action="store_true", help="Simulate netname_filter_union.")
+    parser_filt.add_argument("-f", action="store_true", help="Simulate func_cib.")
+    parser_filt.add_argument("row", type=int, help="Tile row.")
+    parser_filt.add_argument("col", type=int, help="Tile column.")
+    parser_filt.set_defaults(func=filter_mode)
+
+    args = parser.parse_args()
+
+    if len(args.__dict__) <= 1:
+        # No arguments or subcommands were given.
+        parser.print_help()
+        parser.exit()
+
+    args.func(args)
diff --git a/experiments/machxo2/findnets/plc2route.ncl b/experiments/machxo2/findnets/plc2route.ncl
new file mode 100644
index 0000000..7e7370b
--- /dev/null
+++ b/experiments/machxo2/findnets/plc2route.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, A0);
+      ${route}
+   }
+}
diff --git a/experiments/machxo2/interconnect_poc/fuzz_single_mux.py b/experiments/machxo2/interconnect_poc/fuzz_single_mux.py
new file mode 100644
index 0000000..af81853
--- /dev/null
+++ b/experiments/machxo2/interconnect_poc/fuzz_single_mux.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+
+import os
+from os import path
+import shutil
+
+import diamond
+from string import Template
+import pytrellis
+
+device = "LCMXO2-1200HC"
+sink = "R2C2_A0"
+# Drivers found using ispTcl
+drivers = [
+    "R1C2_V02S0501",
+    # "R2C2_H02W0501",
+    # "R2C2_H01E0001",
+    # "R2C3_H02W0701",
+    # "R2C3_H02W0501",
+    # "R2C2_H02W0701",
+    # "R2C2_H02E0501",
+    # "R3C2_V02N0501",
+    # "R1C2_V02S0701",
+    # "R2C2_F5",
+    # "R2C2_H00L0000",
+    # "R2C2_F7",
+    # "R2C2_H02E0701",
+    # "R2C2_V02N0701",
+    # "R2C1_H02E0701",
+    # "R2C3_H01E0001",
+    # "R2C2_V02S0501",
+    # "R3C2_V02N0701",
+    # "R2C2_V02S0701",
+    # "R2C2_V01N0101",
+    # "R2C2_V02N0501",
+    # "R2C1_H02E0501",
+    # "R2C2_H00L0100",
+    # "R2C2_H00R0000"
+]
+
+
+def run_get_bits(mux_driver):
+    route = ""
+    if mux_driver != "":
+        route = "route\n\t\t\t" + mux_driver + "." + sink + ";"
+    with open("mux_template.ncl", "r") as inf:
+        with open("work/mux.ncl", "w") as ouf:
+            ouf.write(Template(inf.read()).substitute(route=route))
+    diamond.run(device, "work/mux.ncl")
+    bs = pytrellis.Bitstream.read_bit("work/mux.bit")
+    chip = bs.deserialise_chip()
+    tile = chip.tiles["R2C2:PLC"]
+    return tile.cram
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    shutil.rmtree("work", ignore_errors=True)
+    os.mkdir("work")
+    baseline = run_get_bits("")
+    with open("a0_mux_out.txt", "w") as f:
+        for d in drivers:
+            bits = run_get_bits(d)
+            diff = bits - baseline
+            diff_str = ["{}F{}B{}".format("!" if b.delta < 0 else "", b.frame, b.bit) for b in diff]
+            print("{0: <18}{1}".format(d, " ".join(diff_str)), file=f)
+            f.flush()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/experiments/machxo2/interconnect_poc/mux_template.ncl b/experiments/machxo2/interconnect_poc/mux_template.ncl
new file mode 100644
index 0000000..7e7370b
--- /dev/null
+++ b/experiments/machxo2/interconnect_poc/mux_template.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, A0);
+      ${route}
+   }
+}
diff --git a/experiments/machxo2/io_params/.gitignore b/experiments/machxo2/io_params/.gitignore
new file mode 100644
index 0000000..90d4bad
--- /dev/null
+++ b/experiments/machxo2/io_params/.gitignore
@@ -0,0 +1 @@
+*_diff.txt
diff --git a/experiments/machxo2/io_params/io_params.py b/experiments/machxo2/io_params/io_params.py
new file mode 100644
index 0000000..e865ef5
--- /dev/null
+++ b/experiments/machxo2/io_params/io_params.py
@@ -0,0 +1,65 @@
+import diamond
+from string import Template
+import pytrellis
+import shutil
+import os
+import argparse
+
+device = "LCMXO2-1200HC"
+
+def run_get_tiles(dir, io_type="LVCMOS33", loc="PB11D"):
+    with open("io_params_template.v", "r") as inf:
+        with open("work/io_params.v", "w") as ouf:
+            ouf.write(Template(inf.read()).substitute(dir=dir,
+                io_type="\"" + io_type + "\"", loc= "\"" + loc + "\""))
+    diamond.run(device, "work/io_params.v")
+    bs = pytrellis.Bitstream.read_bit("work/io_params.bit")
+    chip = bs.deserialise_chip()
+    return chip.tiles
+
+
+def main(args):
+    pytrellis.load_database("../../../database")
+    shutil.rmtree("work", ignore_errors=True)
+    os.mkdir("work")
+    os.environ['DEV_PACKAGE'] = args.p
+    baseline = run_get_tiles("NONE", args.io_type, args.loc)
+
+    dirs = []
+
+    if args.b:
+        dirs.append("BIDIR")
+    if args.i:
+        dirs.append("INPUT")
+    if args.o:
+        dirs.append("OUTPUT")
+
+    with open("io_params_diff.txt", "w") as f:
+        for d in dirs:
+            modified = run_get_tiles(d, args.io_type, args.loc)
+
+            tile_keys = []
+            for t in modified:
+                tile_keys.append(t.key())
+
+            print("{0}".format(d), file=f)
+            for k in tile_keys:
+                diff = modified[k].cram - baseline[k].cram
+                diff_str = ["{}F{}B{}".format("!" if b.delta < 0 else "", b.frame, b.bit) for b in diff]
+                if not diff_str:
+                    continue
+                print("{0: <30}{1}".format(k, " ".join(diff_str)), file=f)
+                f.flush()
+            print("", file=f)
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Test I/O Sites.")
+    parser.add_argument("-b", help="Test bidirectional.", action="store_true")
+    parser.add_argument("-i", help="Test input.", action="store_true")
+    parser.add_argument("-o", help="Test output.", action="store_true")
+    parser.add_argument("-p", type=str, default="QFN32", help="Device package to test.")
+    parser.add_argument(dest="io_type", type=str, default="LVCMOS33", help="I/O standard to test.")
+    parser.add_argument(dest="loc", type=str, default="PB11D", help="Site to test.")
+    args = parser.parse_args()
+
+    main(args)
diff --git a/experiments/machxo2/io_params/io_params_template.v b/experiments/machxo2/io_params/io_params_template.v
new file mode 100644
index 0000000..e71f205
--- /dev/null
+++ b/experiments/machxo2/io_params/io_params_template.v
@@ -0,0 +1,68 @@
+`define ${dir}
+
+`ifdef NONE
+
+module top(input x);
+
+// Minimum legal empty module
+wire dummy;
+
+// Dummy load
+GSR gsr_i(.GSR(dummy));
+
+// Dummy source
+OSCH osc_i(.OSC(dummy));
+
+
+endmodule
+
+`else
+
+module top(inout pad);
+
+`ifdef BIDIR
+
+wire dummyo, dummyi;
+
+(* keep *)
+(* LOC=${loc} *)
+(* IO_TYPE=${io_type} *)
+
+BB b_b(.B(pad), .O(dummyo), .I(1'b1), .T(dummyi));
+
+// Dummy load
+GSR gsr_i(.GSR(dummyo));
+
+// Dummy source
+OSCH osc_i(.OSC(dummyi));
+
+`endif
+
+`ifdef INPUT
+
+wire dummyo;
+
+(* keep *)
+(* LOC=${loc} *)
+(* IO_TYPE=${io_type} *)
+
+IB i_b(.I(pad), .O(dummyo));
+
+// Dummy load
+GSR gsr_i(.GSR(dummyo));
+
+`endif
+
+`ifdef OUTPUT
+
+(* keep *)
+(* LOC=${loc} *)
+(* IO_TYPE=${io_type} *)
+
+OB o_b(.O(pad), .I(1'b1));
+
+`endif
+
+endmodule
+
+`endif
diff --git a/fuzzers/machxo2/001-plc2_routing/fuzzer.py b/fuzzers/machxo2/001-plc2_routing/fuzzer.py
new file mode 100644
index 0000000..20b8117
--- /dev/null
+++ b/fuzzers/machxo2/001-plc2_routing/fuzzer.py
@@ -0,0 +1,40 @@
+from fuzzconfig import FuzzConfig
+import interconnect
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2ROUTE", family="MachXO2", device="LCMXO2-1200HC", ncl="plc2route.ncl", tiles=["R5C10:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+
+    span1_re = re.compile(r'R\d+C\d+_[VH]01[NESWTLBR]\d{4}')
+
+    def nn_filter(net, netnames):
+        """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
+        left out by Tcl"""
+        return net in netnames or nets.machxo2.is_global(net) or span1_re.match(net)
+
+    def arc_filter(arc, netnames):
+        """ Exclude arcs whose sinks are HFSN BRANCHES (HSBX0*0{0,1}). We
+        will deal with them specially in another fuzzer. """
+        return not nets.machxo2.hfsn_branch_re.match(arc[1])
+
+    def fc_filter(arc, netnames):
+        """ Ignore connections between two general routing nets. These are edge buffers which vary based on location
+        and must be excluded from the CIB database.
+        """
+        return not (nets.general_routing_re.match(arc[0]) and nets.general_routing_re.match(arc[1]))
+
+    interconnect.fuzz_interconnect(config=cfg, location=(5, 10),
+                                   netname_predicate=nn_filter,
+                                   arc_predicate=arc_filter,
+                                   fc_predicate=fc_filter,
+                                   netname_filter_union=True,
+                                   enable_span1_fix=True)
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/001-plc2_routing/plc2route.ncl b/fuzzers/machxo2/001-plc2_routing/plc2route.ncl
new file mode 100644
index 0000000..7e7370b
--- /dev/null
+++ b/fuzzers/machxo2/001-plc2_routing/plc2route.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, A0);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/003-lut_init/empty.ncl b/fuzzers/machxo2/003-lut_init/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/003-lut_init/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/003-lut_init/fuzzer.py b/fuzzers/machxo2/003-lut_init/fuzzer.py
new file mode 100644
index 0000000..96d9983
--- /dev/null
+++ b/fuzzers/machxo2/003-lut_init/fuzzer.py
@@ -0,0 +1,46 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2INIT", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def get_lut_function(init_bits):
+    sop_terms = []
+    lut_inputs = ["A", "B", "C", "D"]
+    for i in range(16):
+        if init_bits[i]:
+            p_terms = []
+            for j in range(4):
+                if i & (1 << j) != 0:
+                    p_terms.append(lut_inputs[j])
+                else:
+                    p_terms.append("~" + lut_inputs[j])
+            sop_terms.append("({})".format("*".join(p_terms)))
+    if len(sop_terms) == 0:
+        lut_func = "0"
+    else:
+        lut_func = "+".join(sop_terms)
+    return lut_func
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "lut.ncl"
+
+    def per_slice(slicen):
+        for k in range(2):
+            def get_substs(bits):
+                return dict(slice=slicen, k=str(k), lut_func=get_lut_function(bits))
+            nonrouting.fuzz_word_setting(cfg, "SLICE{}.K{}.INIT".format(slicen, k), 16, get_substs, empty_bitfile)
+
+    fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/003-lut_init/lut.ncl b/fuzzers/machxo2/003-lut_init/lut.ncl
new file mode 100644
index 0000000..3f9fa20
--- /dev/null
+++ b/fuzzers/machxo2/003-lut_init/lut.ncl
@@ -0,0 +1,25 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K${k}::H${k}=${lut_func} "
+                 "F${k}:F ";
+         primitive K${k} i3_4_lut;
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/005-reg_config/empty.ncl b/fuzzers/machxo2/005-reg_config/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/005-reg_config/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/005-reg_config/fuzzer.py b/fuzzers/machxo2/005-reg_config/fuzzer.py
new file mode 100644
index 0000000..4433926
--- /dev/null
+++ b/fuzzers/machxo2/005-reg_config/fuzzer.py
@@ -0,0 +1,49 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2REG", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "reg.ncl"
+
+    def per_slice(slicen):
+        r = 0
+
+        def get_substs(regset="RESET", sd="0", gsr="DISABLED", regmode="FF", clkmode="CLK"):
+            return dict(slice=slicen, r=str(r), regset=regset, sd=sd, gsr=gsr, regmode=regmode, clkmode=clkmode)
+
+        for r in range(2):
+            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)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.GSR".format(slicen), ["DISABLED", "ENABLED"],
+                                     lambda x: get_substs(gsr=x),
+                                     empty_bitfile)
+        # The below will be part of SLICE parameters in yosys models to
+        # decouple latches from registers. However, fuzz here b/c it makes
+        # sense.
+        # CLKMUX appears to be inverted when in latch mode...
+        # i.e. an inverted clock is a positive-level triggered latch.
+        #
+        # I cannot seem to isolate the REGMODE bit without setting the
+        # CLKMUX bit...
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.REGMODE".format(slicen), ["FF", "LATCH"],
+                                     lambda x: get_substs(regmode=x, clkmode="CLK:::CLK=#INV" if "LATCH" else "CLK"),
+                                     empty_bitfile)
+
+    fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/005-reg_config/reg.ncl b/fuzzers/machxo2/005-reg_config/reg.ncl
new file mode 100644
index 0000000..241295f
--- /dev/null
+++ b/fuzzers/machxo2/005-reg_config/reg.ncl
@@ -0,0 +1,32 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "REG${r}:::REGSET=${regset}:SD=${sd} "
+                 "Q${r}:Q "
+                 "GSR:${gsr} "
+                 "REGMODE:${regmode} "
+                 "CLKMUX:${clkmode} "
+                 "CEMUX:1 "
+                 "LSRMUX:LSR "
+                 "SRMODE:LSR_OVER_CE "
+                 "M0MUX:M0 ";
+         primitive REG${r} q_6;
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/007-plc2_cemux/cemux.ncl b/fuzzers/machxo2/007-plc2_cemux/cemux.ncl
new file mode 100644
index 0000000..5619b2b
--- /dev/null
+++ b/fuzzers/machxo2/007-plc2_cemux/cemux.ncl
@@ -0,0 +1,31 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "REG0:::REGSET=RESET:SD=0 "
+                 "Q0:Q "
+                 "GSR:DISABLED "
+                 "CLKMUX:CLK "
+                 "CEMUX:${cemux} "
+                 "LSRMUX:LSR "
+                 "SRMODE:LSR_OVER_CE "
+                 "M0MUX:M0 ";
+         primitive REG0 q_6;
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/007-plc2_cemux/empty.ncl b/fuzzers/machxo2/007-plc2_cemux/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/007-plc2_cemux/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/007-plc2_cemux/fuzzer.py b/fuzzers/machxo2/007-plc2_cemux/fuzzer.py
new file mode 100644
index 0000000..0916add
--- /dev/null
+++ b/fuzzers/machxo2/007-plc2_cemux/fuzzer.py
@@ -0,0 +1,32 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2REG", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "cemux.ncl"
+
+    def per_slice(slicen):
+        def get_substs(cemux):
+            if cemux == "INV":
+                cemux = "CE:::CE=#INV"
+            if cemux == "0":
+                cemux = "1:::1=0"
+            return dict(slice=slicen, cemux=cemux)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.CEMUX".format(slicen), ["0", "1", "CE", "INV"],
+                                     lambda x: get_substs(cemux=x),
+                                     empty_bitfile, False)
+
+    fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/008-plc2_clkmux/clkmux.ncl b/fuzzers/machxo2/008-plc2_clkmux/clkmux.ncl
new file mode 100644
index 0000000..7404fa8
--- /dev/null
+++ b/fuzzers/machxo2/008-plc2_clkmux/clkmux.ncl
@@ -0,0 +1,250 @@
+::FROM-WRITER;
+// designname: top
+// Creation time stamp: 01/31/20  05:21:54
+design top
+{
+   device
+   {
+      architecture xo2c00;
+      device LCMXO2-1200HC;
+      package QFN32;
+      performance "6";
+   }
+
+   // Writing 12 properties.
+   property
+   {
+      LSE_CPS_MAP_FILE string "xxx_lse_sign_file";
+      "PINNAME:0" string "clk";
+      "PINNAME:1" string "d";
+      "PINNAME:2" string "r";
+      "PINNAME:3" string "s";
+      "PINNAME:4" string "q";
+      "PINTYPE:0" string "IN";
+      "PINTYPE:1" string "IN";
+      "PINTYPE:2" string "IN";
+      "PINTYPE:3" string "IN";
+      "PINTYPE:4" string "OUT";
+      "SIGNAME:PUR" string "VCC_net";
+   } // End of property list.
+
+   // The Design macro definitions.
+   // The Design macro instances.
+   // The Design Comps.
+   comp SLICE_0
+   {
+
+      // Writing 2 properties.
+      property
+      {
+         LSE_CPS_ID_1 string "REG0";
+         NGID0 long 2;
+      } // End of property list.
+
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "REG0:::REGSET=SET:SD=0 "
+                 "Q0:Q "
+                 "GSR:ENABLED "
+                 "CLKMUX:${clkmux} "
+                 "CEMUX:1:::1=0 "
+                 "LSRMUX:LSR "
+                 "SRMODE:LSR_OVER_CE "
+                 "LSRONMUX:LSRMUX "
+                 "M0MUX:M0 "
+                 "REGMODE:FF ";
+         primitive REG0 ff;
+      }
+      site R10C6${c};
+   }
+   comp d
+   {
+
+      // Writing 3 properties.
+      property
+      {
+         "#%PAD%PINID" long 1;
+         LSE_CPS_ID_2 string "IOBUF";
+         NGID0 long 3;
+      } // End of property list.
+
+      logical
+      {
+         cellmodel-name PIO;
+         program "PADDI:PADDI "
+                 "IOBUF:::PULLMODE=DOWN,CLAMP=ON "
+                 "VREF:OFF "
+                 "PGMUX:INBUF "
+                 "INRDMUX:PGMUX ";
+         primitive IOBUF d_pad;
+         primitive PAD d;
+      }
+      site "10";
+   }
+   comp q
+   {
+
+      // Writing 3 properties.
+      property
+      {
+         "#%PAD%PINID" long 4;
+         LSE_CPS_ID_3 string "IOBUF";
+         NGID0 long 4;
+      } // End of property list.
+
+      logical
+      {
+         cellmodel-name PIO;
+         program "TRIMUX:PADDT:::PADDT=0 "
+                 "IOBUF:::PULLMODE=DOWN,DRIVE=8, \"
+                    "SLEWRATE=SLOW,HYSTERESIS=NA "
+                 "DATAMUX:PADDO "
+                 "VREF:OFF "
+                 "ODMUX:TRIMUX "
+                 "LVDSMUX:DATAMUX ";
+         primitive IOBUF q_pad;
+         primitive PAD q;
+      }
+      site "9";
+   }
+   comp r
+   {
+
+      // Writing 3 properties.
+      property
+      {
+         "#%PAD%PINID" long 2;
+         LSE_CPS_ID_5 string "IOBUF";
+         NGID0 long 6;
+      } // End of property list.
+
+      logical
+      {
+         cellmodel-name PIO;
+         program "PADDI:PADDI "
+                 "IOBUF:::PULLMODE=DOWN,CLAMP=ON "
+                 "VREF:OFF "
+                 "PGMUX:INBUF "
+                 "INRDMUX:PGMUX ";
+         primitive IOBUF r_pad;
+         primitive PAD r;
+      }
+      site "28";
+   }
+   comp s
+   {
+
+      // Writing 3 properties.
+      property
+      {
+         "#%PAD%PINID" long 3;
+         LSE_CPS_ID_6 string "IOBUF";
+         NGID0 long 7;
+      } // End of property list.
+
+      logical
+      {
+         cellmodel-name PIO;
+         program "PADDI:PADDI "
+                 "IOBUF:::PULLMODE=DOWN,CLAMP=ON "
+                 "VREF:OFF "
+                 "PGMUX:INBUF "
+                 "INRDMUX:PGMUX ";
+         primitive IOBUF s_pad;
+         primitive PAD s;
+      }
+      site "8";
+   }
+   comp GSR_INST
+   {
+
+      // Writing 2 properties.
+      property
+      {
+         LSE_CPS_ID_4 string "GSR";
+         NGID0 long 5;
+      } // End of property list.
+
+      logical
+      {
+         cellmodel-name GSR;
+         program "GSRMODE:ACTIVE_LOW "
+                 "SYNCMODE:ASYNC ";
+      }
+      site GSR;
+   }
+   // The Design Signals.
+   signal d_c
+   {
+      signal-pins
+         // drivers
+         (d, PADDI),
+         // loads
+         (SLICE_0, M0);
+      route
+         R10C6_H00L0200.R10C6_M0,
+         R10C6_V02N0301.R10C6_H00L0200,
+         R11C6_JQ3.R10C6_V02N0301,
+         R12C6_JDID.R11C6_JQ3,
+         R10C6_M0.R10C6_M0_SLICE,
+         R12C6_JPADDID_PIO.R12C6_JDID;
+   }
+   signal s_c
+   {
+      signal-pins
+         // drivers
+         (s, PADDI),
+         // loads
+         (SLICE_0, LSR);
+      route
+         R10C6_V00T0100.R10C6_LSR0,
+         R10C5_H02E0401.R10C6_V00T0100,
+         R10C4_V01N0101.R10C5_H02E0401,
+         R11C4_JQ2.R10C4_V01N0101,
+         R12C4_JDIC.R11C4_JQ2,
+         R10C6_LSR0.R10C6_LSR0_SLICE,
+         R12C4_JPADDIC_PIO.R12C4_JDIC;
+   }
+   signal q_c
+   {
+
+      // Writing 1 properties.
+      property
+      {
+         TW_IS_CONST_SIG boolean true;
+      } // End of property list.
+
+      signal-pins
+         // drivers
+         (SLICE_0, Q0),
+         // loads
+         (q, PADDO);
+      route
+         R11C6_V02N0701.R11C6_JA2,
+         R11C6_V02N0701.R11C6_V02S0700,
+         R10C6_V01S0100.R11C6_V02S0700,
+         R10C6_Q0.R10C6_V01S0100,
+         R11C6_JA2.R12C6_JPADDOC,
+         R12C6_JPADDOC.R12C6_PADDOC_PIO,
+         R10C6_Q0_SLICE.R10C6_Q0;
+   }
+   signal r_c
+   {
+      signal-pins
+         // drivers
+         (r, PADDI),
+         // loads
+         (GSR_INST, GSR);
+      route
+         R1C6_H00R0300.R1C6_JC4,
+         R1C6_V02S0101.R1C6_H00R0300,
+         R1C6_V02N0100.R1C6_V02S0101,
+         R1C9_H06W0103.R1C6_V02N0100,
+         R1C12_JQ2.R1C9_H06W0103,
+         R0C12_JDIC.R1C12_JQ2,
+         R1C6_JC4.R1C4_JGSR_GSR,
+         R0C12_JPADDIC_PIO.R0C12_JDIC;
+   }
+}
diff --git a/fuzzers/machxo2/008-plc2_clkmux/empty.ncl b/fuzzers/machxo2/008-plc2_clkmux/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/008-plc2_clkmux/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/008-plc2_clkmux/fuzzer.py b/fuzzers/machxo2/008-plc2_clkmux/fuzzer.py
new file mode 100644
index 0000000..11b998a
--- /dev/null
+++ b/fuzzers/machxo2/008-plc2_clkmux/fuzzer.py
@@ -0,0 +1,38 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2REG", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C6:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "clkmux.ncl"
+
+    def per_clk(clkn):
+        slices = { "0" : "A",
+                   "1" : "B",
+                   "2" : "C",
+                   "3" : "D"
+                 }
+
+        def get_substs(clkmux):
+            if clkmux == "INV":
+                clkmux = "CLK:::CLK=#INV"
+            if clkmux == "1":
+                clkmux = "0:::0=1"
+            return dict(c=slices[clkn], clkmux=clkmux)
+        nonrouting.fuzz_enum_setting(cfg, "CLK{}.CLKMUX".format(clkn), ["CLK", "INV", "0", "1"],
+                                     lambda x: get_substs(clkmux=x),
+                                     empty_bitfile, True)
+
+    fuzzloops.parallel_foreach(["0", "1", "2", "3"], per_clk)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/009-plc2_lsr/empty.ncl b/fuzzers/machxo2/009-plc2_lsr/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/009-plc2_lsr/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/009-plc2_lsr/fuzzer.py b/fuzzers/machxo2/009-plc2_lsr/fuzzer.py
new file mode 100644
index 0000000..3dd6127
--- /dev/null
+++ b/fuzzers/machxo2/009-plc2_lsr/fuzzer.py
@@ -0,0 +1,41 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2REG", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C6:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "lsr.ncl"
+
+    def per_lsr(lsrn):
+        slices = { "0" : "A",
+                   "1" : "B",
+                   "2" : "C",
+                   "3" : "D"
+                 }
+
+        def get_substs(lsrmux="LSR", srmode="LSR_OVER_CE", lsronmux="0"):
+            if lsrmux == "INV":
+                lsrmux = "LSR:::LSR=#INV"
+            return dict(s=slices[lsrn], l=lsrn, lsrmux=lsrmux, srmode=srmode, lsronmux=lsronmux)
+        nonrouting.fuzz_enum_setting(cfg, "LSR{}.LSRMUX".format(lsrn), ["LSR", "INV"],
+                                     lambda x: get_substs(lsrmux=x),
+                                     empty_bitfile, True)
+        nonrouting.fuzz_enum_setting(cfg, "LSR{}.SRMODE".format(lsrn), ["LSR_OVER_CE", "ASYNC"],
+                                     lambda x: get_substs(srmode=x),
+                                     empty_bitfile, True)
+        nonrouting.fuzz_enum_setting(cfg, "LSR{}.LSRONMUX".format(lsrn), ["0", "LSRMUX"],
+                                     lambda x: get_substs(lsronmux=x),
+                                     empty_bitfile, True)
+    fuzzloops.parallel_foreach(["0", "1", "2", "3"], per_lsr)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/009-plc2_lsr/lsr.ncl b/fuzzers/machxo2/009-plc2_lsr/lsr.ncl
new file mode 100644
index 0000000..8bd295a
--- /dev/null
+++ b/fuzzers/machxo2/009-plc2_lsr/lsr.ncl
@@ -0,0 +1,58 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+    }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "REG0:::REGSET=RESET:SD=0 "
+                 "Q0:Q "
+                 "GSR:DISABLED "
+                 "CLKMUX:CLK "
+                 "CEMUX:CE "
+                 "LSRMUX:${lsrmux} "
+                 "SRMODE:${srmode} "
+                 "LSRONMUX:${lsronmux} "
+                 "M0MUX:M0 ";
+         primitive REG0 q_6;
+      }
+      site R10C6${s};
+   }
+
+   comp lsr
+   {
+      logical
+      {
+         cellmodel-name PIO;
+         program "PADDI:PADDI "
+                 "IOBUF:::PULLMODE=DOWN,CLAMP=ON "
+                 "VREF:OFF "
+                 "PGMUX:INBUF "
+                 "INRDMUX:PGMUX ";
+         primitive IOBUF lsr_pad;
+         primitive PAD lsr;
+      }
+      site "13";
+   }
+
+   signal lsrc_c
+   {
+      signal-pins
+         // drivers
+         (lsr, PADDI),
+         // loads
+         (SLICE_0, LSR);
+      route
+         R10C6_LSR${l}.R10C6_LSR${l}_SLICE;
+   }
+}
diff --git a/fuzzers/machxo2/010-plc2_modes/empty.ncl b/fuzzers/machxo2/010-plc2_modes/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/010-plc2_modes/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/010-plc2_modes/fuzzer.py b/fuzzers/machxo2/010-plc2_modes/fuzzer.py
new file mode 100644
index 0000000..0eabba4
--- /dev/null
+++ b/fuzzers/machxo2/010-plc2_modes/fuzzer.py
@@ -0,0 +1,34 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2MODE", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "modes.ncl"
+
+    def per_slice(slicen):
+        def get_substs(mode):
+            return dict(slice=slicen, mode=mode)
+        if slicen == "A" or slicen == "B":
+            modes = ["LOGIC", "CCU2", "DPRAM"]
+        elif slicen == "C":
+            modes = ["LOGIC", "CCU2", "RAMW"]
+        else:
+            modes = ["LOGIC", "CCU2"]
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.MODE".format(slicen), modes,
+                                     lambda x: get_substs(mode=x),
+                                     empty_bitfile, False)
+
+    fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/010-plc2_modes/modes.ncl b/fuzzers/machxo2/010-plc2_modes/modes.ncl
new file mode 100644
index 0000000..8780d07
--- /dev/null
+++ b/fuzzers/machxo2/010-plc2_modes/modes.ncl
@@ -0,0 +1,23 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:${mode} ";
+         primitive REG0 q_6;
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/011-ccu2_inject/ccu2.ncl b/fuzzers/machxo2/011-ccu2_inject/ccu2.ncl
new file mode 100644
index 0000000..bbb520f
--- /dev/null
+++ b/fuzzers/machxo2/011-ccu2_inject/ccu2.ncl
@@ -0,0 +1,26 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:CCU2 "
+                 "CCU2::S0=0x9009,S1=0x9009:INJECT1_0=${ij1_0}, \"
+                    "INJECT1_1=${ij1_1} "
+                 "FCO:FCO ";
+         primitive CCU2 "CCU";
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/011-ccu2_inject/empty.ncl b/fuzzers/machxo2/011-ccu2_inject/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/011-ccu2_inject/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/011-ccu2_inject/fuzzer.py b/fuzzers/machxo2/011-ccu2_inject/fuzzer.py
new file mode 100644
index 0000000..2b68724
--- /dev/null
+++ b/fuzzers/machxo2/011-ccu2_inject/fuzzer.py
@@ -0,0 +1,30 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2MODE", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "ccu2.ncl"
+
+    def per_slice(slicen):
+        def get_substs(ij1_0="YES", ij1_1="YES"):
+            return dict(slice=slicen, ij1_0=ij1_0, ij1_1=ij1_1)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.CCU2.INJECT1_0".format(slicen), ["YES", "NO"],
+                                     lambda x: get_substs(ij1_0=x),
+                                     empty_bitfile, True)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.CCU2.INJECT1_1".format(slicen), ["YES", "NO"],
+                                     lambda x: get_substs(ij1_1=x),
+                                     empty_bitfile, True)
+    fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/012-ccu2_nmux/ccu2.ncl b/fuzzers/machxo2/012-ccu2_nmux/ccu2.ncl
new file mode 100644
index 0000000..12a8af9
--- /dev/null
+++ b/fuzzers/machxo2/012-ccu2_nmux/ccu2.ncl
@@ -0,0 +1,25 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:CCU2 "
+                 "CCU2::S0=0xfaaa,S1=0xfaaa${muxcfg}"
+                 "FCO:FCO ";
+         primitive CCU2 "CCU";
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/012-ccu2_nmux/empty.ncl b/fuzzers/machxo2/012-ccu2_nmux/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/012-ccu2_nmux/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/012-ccu2_nmux/fuzzer.py b/fuzzers/machxo2/012-ccu2_nmux/fuzzer.py
new file mode 100644
index 0000000..dedfc85
--- /dev/null
+++ b/fuzzers/machxo2/012-ccu2_nmux/fuzzer.py
@@ -0,0 +1,39 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+# At present, I don't believe this affects any bits. Keeping this around
+# just in case I figure out I'm wrong...
+
+cfg = FuzzConfig(job="PLC2NMUX", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "ccu2.ncl"
+
+    def per_slice(slicen):
+        def get_substs(sig="A0", conn="A0"):
+            subs = {"slice": slicen}
+            if conn == "0":
+                # subs["muxcfg"] = "::{}=0".format(sig)
+                subs["muxcfg"] = "::A0=0,A1=0,B0=0,B1=0,C0=0,C1=0,D0=0,D1=0"
+            else:
+                subs["muxcfg"] = ""
+            return subs
+        # for sig in ["A0", "A1", "B0", "B1", "C0", "C1", "D0", "D1"]:
+        for sig in ["A0"]:
+            nonrouting.fuzz_enum_setting(cfg, "SLICE{}.{}MUX".format(slicen, sig), [sig, "0"],
+                                         lambda x: get_substs(sig=sig, conn=x),
+                                         empty_bitfile, False)
+    # fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+    fuzzloops.parallel_foreach(["A"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/013-plc2_mkmux/empty.ncl b/fuzzers/machxo2/013-plc2_mkmux/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/013-plc2_mkmux/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/013-plc2_mkmux/fuzzer.py b/fuzzers/machxo2/013-plc2_mkmux/fuzzer.py
new file mode 100644
index 0000000..bcb0f0d
--- /dev/null
+++ b/fuzzers/machxo2/013-plc2_mkmux/fuzzer.py
@@ -0,0 +1,39 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+# No evidence this affects any bits.
+
+cfg = FuzzConfig(job="PLC2MKMUX", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "mkmux.ncl"
+
+    def per_slice(slicen):
+        def get_substs(m0mux="M0", m1mux="M1", f_mode="F"):
+            if m0mux == "OFF":
+                s_m0mux = "#OFF"
+            else:
+                s_m0mux = m0mux
+            return dict(slice=slicen, m0mux=s_m0mux, m1mux=m1mux)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.M0MUX".format(slicen), ["M0", "OFF", "0"],
+                                     lambda x: get_substs(m0mux=x),
+                                     empty_bitfile, False)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.M1MUX".format(slicen), ["M1", "OFF", "0"],
+                                     lambda x: get_substs(m1mux=x),
+                                     empty_bitfile, False)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.F0".format(slicen), ["F", "OFF", "0"],
+                                     lambda x: get_substs(f_mode=x),
+                                     empty_bitfile, False)
+    fuzzloops.parallel_foreach(["A", "B", "C", "D"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/013-plc2_mkmux/mkmux.ncl b/fuzzers/machxo2/013-plc2_mkmux/mkmux.ncl
new file mode 100644
index 0000000..c553eb0
--- /dev/null
+++ b/fuzzers/machxo2/013-plc2_mkmux/mkmux.ncl
@@ -0,0 +1,32 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "REG0:::REGSET=RESET:SD=0 "
+                 "Q0:Q "
+                 "GSR:DISABLED "
+                 "CLKMUX:CLK "
+                 "CEMUX:1 "
+                 "LSRMUX:LSR "
+                 "SRMODE:LSR_OVER_CE "
+                 "M0MUX:${m0mux} "
+                 "M1MUX:${m1mux} ";
+         primitive REG0 q_6;
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/014-plc2_wremux/empty.ncl b/fuzzers/machxo2/014-plc2_wremux/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/014-plc2_wremux/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/014-plc2_wremux/fuzzer.py b/fuzzers/machxo2/014-plc2_wremux/fuzzer.py
new file mode 100644
index 0000000..cff8b36
--- /dev/null
+++ b/fuzzers/machxo2/014-plc2_wremux/fuzzer.py
@@ -0,0 +1,33 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import fuzzloops
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="PLC2WRE", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl", tiles=["R10C11:PLC"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "wremux.ncl"
+
+    def per_slice(slicen):
+        def get_substs(wremux):
+            if wremux == "INV":
+                wremux = "WRE:::WRE=#INV"
+            if wremux == "1":
+                wremux = "0:::0=1"
+            return dict(slice=slicen, wremux=wremux)
+        nonrouting.fuzz_enum_setting(cfg, "SLICE{}.WREMUX".format(slicen), ["0", "1", "WRE", "INV"],
+                                     lambda x: get_substs(wremux=x),
+                                     empty_bitfile, False)
+
+    # B also has a WREMUX signal, but the same bit as A controls it.
+    fuzzloops.parallel_foreach(["A"], per_slice)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/014-plc2_wremux/wremux.ncl b/fuzzers/machxo2/014-plc2_wremux/wremux.ncl
new file mode 100644
index 0000000..3eedbb6
--- /dev/null
+++ b/fuzzers/machxo2/014-plc2_wremux/wremux.ncl
@@ -0,0 +1,25 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:DPRAM "
+                 "DPRAM::DO0=0x0000,DO1=0x0000 "
+                 "WREMUX:${wremux} ";
+         primitive DPRAM q_6;
+      }
+      site R10C11${slice};
+   }
+
+}
diff --git a/fuzzers/machxo2/017-patch_carry/fuzzer.py b/fuzzers/machxo2/017-patch_carry/fuzzer.py
new file mode 100644
index 0000000..95344d1
--- /dev/null
+++ b/fuzzers/machxo2/017-patch_carry/fuzzer.py
@@ -0,0 +1,30 @@
+import pytrellis
+
+# Taken from ECP5. Using R5C10 as an example, R5C11_HFIE0000, or "E1_HFIE0000"
+# will not be considered part of the current tile by isptcl.get_wires_at_position
+# when invoked via fuzz_interconnect (unless possibly func_cib=True- untested).
+#
+# However, R5C10_HFIE0000, or "HFIE0000" is considered part of the current tile.
+# When netname_filter_union=True*, as is the default of fuzz_interconnect,
+# the arc R5C9_FCO --> R5C10_HFIE0000 gets filtered out by netname_predicate
+# because "R5C9_FCO" is not part of the current tile.
+#
+# We can special-case "R5C9_FCO" in the netname_predicate, but it feels more
+# natural to have carry out (FCO) be the _source_ connection to the next tile,
+# rather than the sink connection from the previous tile
+# (e.g. R5C10_FCO --> R5C11_HFIE0000 is preferable to
+# R5C9_FCO --> R5C10_HFIE0000 --> R5C10_FCI).
+#
+# * Which should really be named netname_filter_intersect.
+
+def main():
+    pytrellis.load_database("../../../database")
+    db = pytrellis.get_tile_bitdata(pytrellis.TileLocator("MachXO2", "LCMXO2-1200HC", "PLC"))
+    fc = pytrellis.FixedConnection()
+    fc.source = "FCO"
+    fc.sink = "E1_HFIE0000"
+    db.add_fixed_conn(fc)
+    db.save()
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/020-center-mux/center-mux.ncl b/fuzzers/machxo2/020-center-mux/center-mux.ncl
new file mode 100644
index 0000000..3f9d75c
--- /dev/null
+++ b/fuzzers/machxo2/020-center-mux/center-mux.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, CLK);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/020-center-mux/fuzzer.py b/fuzzers/machxo2/020-center-mux/fuzzer.py
new file mode 100644
index 0000000..38af516
--- /dev/null
+++ b/fuzzers/machxo2/020-center-mux/fuzzer.py
@@ -0,0 +1,117 @@
+from collections import defaultdict
+
+from fuzzconfig import FuzzConfig
+import interconnect
+import nets
+import pytrellis
+import re
+import argparse
+from mk_nets import *
+
+def fc_filter_all(a, n):
+    return True
+
+# TCL claims these are fixed connections; we should treat them as
+# inputs/outputs to/from DCC/DCM BELs.
+def fc_filter_dcc(a, n):
+    return bool(not (nets.machxo2.dcc_sig_re.match(a[0]) and nets.machxo2.dcc_sig_re.match(a[1])) and
+                not (nets.machxo2.dcm_sig_re.match(a[0]) and nets.machxo2.dcm_sig_re.match(a[1])))
+
+# The relevant tiles were inferred from the center_mux experiment.
+# Asterisk means "the fuzzer generated connects with non-local prefixes".
+jobs = [
+        # *
+        {
+            "netnames" : eclk_out + eclk_cib + eclk_div,
+            "cfg" : FuzzConfig(job="GLOBAL_ECLK", family="MachXO2", device="LCMXO2-1200HC", ncl="center-mux.ncl",
+                      tiles=["CENTER6:CENTER_EBR_CIB", "CENTER_EBR14:CENTER_EBR",
+                             "CENTER9:CENTER8", "CENTER8:CENTER7", "CENTER7:CENTER6",
+                             "CENTER5:CENTER5", "CENTER4:CENTER4"]),
+            "prefix" : "1200_",
+            "overrides" : defaultdict(lambda : str("sink")),
+            "fc_filter" : fc_filter_all
+        },
+
+        # *
+        {
+            "netnames" : eclkbridge,
+            "cfg" : FuzzConfig(job="GLOBAL_ECLKBRIDGE", family="MachXO2", device="LCMXO2-1200HC", ncl="center-mux.ncl",
+                      tiles=["CENTER6:CENTER_EBR_CIB", "CENTER_EBR14:CENTER_EBR",
+                             "CENTER9:CENTER8", "CENTER8:CENTER7", "CENTER7:CENTER6",
+                             "CENTER5:CENTER5", "CENTER4:CENTER4"]),
+            "prefix" : "1200_",
+            "overrides" : { "R6C13_JECSOUT0_ECLKBRIDGECS" : "driver",
+                            "R6C13_JECSOUT1_ECLKBRIDGECS" : "driver",
+                            "R6C13_JSEL0_ECLKBRIDGECS" : "sink",
+                            "R6C13_JSEL1_ECLKBRIDGECS" : "sink" },
+            "fc_filter" : fc_filter_all
+        },
+
+        {
+            "netnames" : dcm + dcc,
+            "cfg" : FuzzConfig(job="GLOBAL_DCM_DCC", family="MachXO2", device="LCMXO2-1200HC", ncl="center-mux.ncl",
+                      tiles=["CENTER6:CENTER_EBR_CIB", "CENTER_EBR14:CENTER_EBR",
+                             "CENTER9:CENTER8", "CENTER8:CENTER7", "CENTER7:CENTER6",
+                             "CENTER5:CENTER5", "CENTER4:CENTER4"]),
+            "prefix" : "1200_",
+            "overrides" : defaultdict(lambda : str("sink")),
+            "fc_filter" : fc_filter_dcc
+        },
+
+        {
+            "netnames" : hfsn_cib + hfsn_out + global_cib + global_out + pll + clock_pin,
+            "cfg" : FuzzConfig(job="GLOBAL_HFSN", family="MachXO2", device="LCMXO2-1200HC", ncl="center-mux.ncl",
+                      tiles=["CENTER6:CENTER_EBR_CIB", "CENTER_EBR14:CENTER_EBR",
+                             "CENTER9:CENTER8", "CENTER8:CENTER7", "CENTER7:CENTER6",
+                             "CENTER5:CENTER5", "CENTER4:CENTER4"]),
+            "prefix" : "1200_",
+            "overrides" : defaultdict(lambda : str("sink")),
+            "fc_filter" : fc_filter_all
+        },
+
+        # Thanks to fc_filter_dcc, make sure to manually connect DCC outputs to
+        # entrance to global network!
+        {
+            "netnames" : ["R6C13_VPRX0000", "R6C13_VPRX0100", "R6C13_VPRX0200", "R6C13_VPRX0300",
+                          "R6C13_VPRX0400", "R6C13_VPRX0500", "R6C13_VPRX0600", "R6C13_VPRX0700"],
+            "cfg" : FuzzConfig(job="GLOBAL_DCC_OUT", family="MachXO2", device="LCMXO2-1200HC", ncl="center-mux.ncl",
+                      tiles=["CENTER6:CENTER_EBR_CIB", "CENTER_EBR14:CENTER_EBR",
+                             "CENTER9:CENTER8", "CENTER8:CENTER7", "CENTER7:CENTER6",
+                             "CENTER5:CENTER5", "CENTER4:CENTER4"]),
+            "prefix" : "1200_",
+            "overrides" : defaultdict(lambda : str("sink")),
+            "fc_filter" : fc_filter_all
+        },
+]
+
+
+def main(args):
+    pytrellis.load_database("../../../database")
+
+    for job in [jobs[i] for i in args.ids]:
+        cfg = job["cfg"]
+        cfg.setup()
+        interconnect.fuzz_interconnect_with_netnames(config=cfg, netnames=job["netnames"],
+                                                     netname_filter_union=False,
+                                                     netdir_override=job["overrides"],
+                                                     nonlocal_prefix=job["prefix"],
+                                                     fc_predicate=job["fc_filter"])
+
+    # TODO: R6C13_JA0 --> R6C13_JCE0_DCC. But TCL also claims
+    # R6C13_CLKI0_DCC --> R6C13_CLKO0_DCC (pseudo = 1). Contradiction?
+    # From talking to Dave: No it's not a contradiction. A
+    # config bit controls whether JCE0 has any effect.
+    # Might be fuzzed with something like:
+    # interconnect.fuzz_interconnect_with_netnames(config=cfg, netnames=["R6C13_CLKI0_DCC", "R6C13_CLKO0_DCC", "R6C13_JCE0_DCC"],
+    #                                              netname_filter_union=False,
+    #                                              netdir_override = {
+    #                                                 "R6C13_JCE0_DCC" : "sink",
+    #                                              })
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Center Mux Routing Fuzzer.")
+    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
+                    default=range(0, len(jobs)), help="Job (indices) to run.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/020-center-mux/mk_nets.py b/fuzzers/machxo2/020-center-mux/mk_nets.py
new file mode 100644
index 0000000..1590976
--- /dev/null
+++ b/fuzzers/machxo2/020-center-mux/mk_nets.py
@@ -0,0 +1,114 @@
+from nets import net_product
+
+hfsn_cib = []
+hfsn_cib.extend(net_product(["R6C13_JSNETCIBL{0}",
+    "R6C13_JSNETCIBR{0}",
+    "R6C13_JSNETCIBT{0}",
+    "R6C13_JSNETCIBB{0}"],
+    range(2)))
+hfsn_cib.extend(net_product(["R6C13_JSNETCIBMID{0}"], range(8)))
+
+hfsn_out = []
+hfsn_out.extend(net_product(["R6C13_VSRX0{0}00"], range(8)))
+
+global_cib = []
+global_cib.extend(net_product(["R6C13_JPCLKCIBLLQ{0}",
+    "R6C13_JPCLKCIBLRQ{0}",
+    "R6C13_JPCLKCIBVIQB{0}",
+    "R6C13_JPCLKCIBVIQT{0}"],
+    range(2)))
+global_cib.extend(net_product(["R6C13_JPCLKCIBMID{0}"], range(2, 4)))
+global_cib.extend(net_product(["R6C13_PCLKCIBLLQ{0}",
+    "R6C13_PCLKCIBLRQ{0}",
+    "R6C13_PCLKCIBVIQB{0}",
+    "R6C13_PCLKCIBVIQT{0}"],
+    range(2)))
+global_cib.extend(net_product(["R6C13_PCLKCIBMID{0}"], range(2, 4)))
+
+global_out = []
+global_out.extend(net_product(["R6C13_VPRXCLKI{0}"], range(6)))
+global_out.extend(net_product(["R6C13_VPRXCLKI6{0}",
+    "R6C13_VPRXCLKI7{0}"],
+    range(2)))
+
+eclk_cib = []
+eclk_cib.extend(net_product(["R6C13_JECLKCIBB{0}",
+    "R6C13_JECLKCIBT{0}"],
+    range(2)))
+eclk_cib.extend(net_product(["R6C13_ECLKCIBB{0}",
+    "R6C13_ECLKCIBT{0}"],
+    range(2)))
+
+eclk_div = []
+eclk_div.extend(net_product(["R6C13_JBCDIVX{0}",
+    "R6C13_JBCDIV1{0}",
+    "R6C13_JTCDIVX{0}",
+    "R6C13_JTCDIV1{0}"],
+    range(2)))
+
+eclk_out = []
+eclk_out.extend(net_product(["R6C13_EBRG0CLK{0}",
+    "R6C13_EBRG1CLK{0}"],
+    range(2)))
+
+pll = []
+pll.extend(net_product(["R6C13_JLPLLCLK{0}"], range(4)))
+
+clock_pin = []
+clock_pin.extend(net_product(["R6C13_JPCLKT2{0}",
+    "R6C13_JPCLKT0{0}"],
+    range(2)))
+clock_pin.extend(net_product(["R6C13_JPCLKT3{0}"], range(3)))
+clock_pin.extend(net_product(["R6C13_JPCLKT1{0}"], range(1)))
+
+dcc = []
+dcc.extend(net_product(["R6C13_CLKI{0}_DCC",
+    "R6C13_CLKO{0}_DCC",
+    "R6C13_JCE{0}_DCC"],
+    range(8)))
+
+# Unfortunately, same deal w/ concatenated nets also applies to the
+# output.
+# "second" is kinda a misnomer here- clock nets go through tristates,
+# although TCL claims a fixed connection. There are multiple layers of
+# fixed connections where the net names change on the outputs, so we
+# just take care of all suspected fixed connections in one fell swoop.
+#
+# All the initial output connections are attached to muxes controlled
+# by config bits, and thus are _not_ fixed.
+dcm = []
+# Global nets 6 and 7 are "MUX"ed twice- once to choose between two
+# connections, another for clock enable (CEN). First net.
+dcm.extend(net_product(["R6C13_CLK{0}_6_DCM",
+    "R6C13_CLK{0}_7_DCM"],
+    range(2)))
+dcm.extend(net_product(["R6C13_JSEL{0}_DCM",
+    "R6C13_DCMOUT{0}_DCM"],
+    range(6, 8)))
+
+# TODO: CLK{0,1}_{0,1}_ECLKBRIDGECS do not in fact drive
+# R6C13_JSEL{0}_ECLKBRIDGECS. R6C13_JSEL{0}_ECLKBRIDGECS is driven from
+# elsewhere.
+eclkbridge = []
+eclkbridge.extend(net_product(["R6C13_CLK{0}_0_ECLKBRIDGECS",
+    "R6C13_CLK{0}_1_ECLKBRIDGECS",
+    "R6C13_JECSOUT{0}_ECLKBRIDGECS",
+    "R6C13_JSEL{0}_ECLKBRIDGECS"],
+    range(2)))
+
+all = [hfsn_cib, hfsn_out, global_cib, global_out, eclk_cib, eclk_div,
+    eclk_out, pll, clock_pin, dcc, dcm, eclkbridge]
+
+all_flat = []
+
+def main():
+    import json
+
+    for l in all:
+        all_flat.extend(l)
+
+    print(json.dumps(all_flat))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/021-glb-entry/empty.ncl b/fuzzers/machxo2/021-glb-entry/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/021-glb-entry/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/021-glb-entry/fuzzer.py b/fuzzers/machxo2/021-glb-entry/fuzzer.py
new file mode 100644
index 0000000..5ce8394
--- /dev/null
+++ b/fuzzers/machxo2/021-glb-entry/fuzzer.py
@@ -0,0 +1,43 @@
+from fuzzconfig import FuzzConfig
+import interconnect
+import pytrellis
+
+jobs = [
+    {
+        "cfg": FuzzConfig(job="GLB_ENTRY", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                          tiles=["CENTER6:CENTER_EBR_CIB"]),
+        "left_net": "R6C{}_HPSX{:02d}00",
+        "right_net": "R6C{}_HPSX{:02d}00"
+    },
+]
+
+
+def main():
+    # left_end and right_end are 1200HC-specific. However, the results
+    # also readily apply to 2000HC devices because they also have a
+    # CENTER_EBR_CIB tile (without qualifiers).
+    def left_end(x):
+        return 8 if x % 2 == 0 else 7
+
+    def right_end(x):
+        if x == 0 or x == 4:
+            return 18
+        elif x == 1 or x == 5:
+            return 19
+        else:
+            return 17
+
+    pytrellis.load_database("../../../database")
+    for job in jobs:
+        cfg = job["cfg"]
+        cfg.setup()
+        netnames = []
+        netnames += [job["left_net"].format(left_end(x), x) for x in range(8)]
+        netnames += [job["right_net"].format(right_end(x), x) for x in range(8)]
+
+        interconnect.fuzz_interconnect_with_netnames(config=cfg, netnames=netnames,
+                                                     netname_filter_union=False)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/021-glb-entry/tap.ncl b/fuzzers/machxo2/021-glb-entry/tap.ncl
new file mode 100644
index 0000000..aee9b98
--- /dev/null
+++ b/fuzzers/machxo2/021-glb-entry/tap.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal clk_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, CLK);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/022-glb-cib_ebr/fuzzer.py b/fuzzers/machxo2/022-glb-cib_ebr/fuzzer.py
new file mode 100644
index 0000000..68b70ce
--- /dev/null
+++ b/fuzzers/machxo2/022-glb-cib_ebr/fuzzer.py
@@ -0,0 +1,58 @@
+from fuzzconfig import FuzzConfig
+import interconnect
+import pytrellis
+import argparse
+from nets import net_product
+
+def mk_nets(tilepos, glb_ids):
+    ud_nets = []
+    rc_prefix = "R{}C{}_".format(tilepos[0], tilepos[1])
+
+    # Up/Down conns
+    ud_nets.extend(net_product(
+        net_product(["R4C{}_VPTX0{{}}00", "R9C{}_VPTX0{{}}00"], [tilepos[1]]),
+        glb_ids))
+
+    # Phantom DCCs- First fill in "T"/"B", and then global id
+    ud_nets.extend(net_product(
+        net_product([rc_prefix + "CLKI{{}}{}_DCC",
+                     # rc_prefix + "JCE{{}}{}_DCC", # TODO: These nets may not exist?
+                     rc_prefix + "CLKO{{}}{}_DCC"], ("T", "B")),
+        glb_ids))
+
+    return ud_nets
+
+def flatten_nets(tilepos):
+    return [nets for netpair in [(0, 4), (1, 5), (2, 6), (3, 7)] for nets in mk_nets(tilepos, netpair)]
+
+jobs = [
+    (FuzzConfig(job="GLB_UPDOWN26", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C4:CIB_EBR0"]), mk_nets((6, 4), (2, 6))),
+    (FuzzConfig(job="GLB_UPDOWN15", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C7:CIB_EBR0"]), mk_nets((6, 7), (1, 5))),
+    (FuzzConfig(job="GLB_UPDOWN04", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C10:CIB_EBR0"]), mk_nets((6, 10), (0, 4))),
+    (FuzzConfig(job="GLB_UPDOWN37", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C17:CIB_EBR0"]), mk_nets((6, 17), (3, 7))),
+    (FuzzConfig(job="CIB0_EBR0_END0_UPDOWN", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C1:CIB_EBR0_END0"]), flatten_nets((6,1))),
+    (FuzzConfig(job="CIB0_EBR2_END0_UPDOWN", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C22:CIB_EBR2_END0"]), flatten_nets((6,22)))
+]
+
+def main(args):
+    pytrellis.load_database("../../../database")
+
+    for job in [jobs[i] for i in args.ids]:
+        cfg, netnames = job
+        cfg.setup()
+        interconnect.fuzz_interconnect_with_netnames(config=cfg, netnames=netnames,
+                                                     netname_filter_union=False)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Center Mux Routing Fuzzer.")
+    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
+                    default=range(0, len(jobs)), help="Job (indices) to run.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/022-glb-cib_ebr/tap.ncl b/fuzzers/machxo2/022-glb-cib_ebr/tap.ncl
new file mode 100644
index 0000000..aee9b98
--- /dev/null
+++ b/fuzzers/machxo2/022-glb-cib_ebr/tap.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal clk_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, CLK);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/023-glb-dcc/dcc.ncl b/fuzzers/machxo2/023-glb-dcc/dcc.ncl
new file mode 100644
index 0000000..854d8d7
--- /dev/null
+++ b/fuzzers/machxo2/023-glb-dcc/dcc.ncl
@@ -0,0 +1,22 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+  ${comment} comp I1
+  ${comment} {
+  ${comment}     logical
+  ${comment}     {
+  ${comment}        cellmodel-name DCC;
+  ${comment}        program "MODE:DCCA "
+  ${comment}                "DCCA:#ON ";
+  ${comment}     }
+  ${comment}     site ${site};
+  ${comment}  }
+}
diff --git a/fuzzers/machxo2/023-glb-dcc/empty.ncl b/fuzzers/machxo2/023-glb-dcc/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/023-glb-dcc/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/023-glb-dcc/fuzzer.py b/fuzzers/machxo2/023-glb-dcc/fuzzer.py
new file mode 100644
index 0000000..d1d2f58
--- /dev/null
+++ b/fuzzers/machxo2/023-glb-dcc/fuzzer.py
@@ -0,0 +1,35 @@
+from fuzzconfig import FuzzConfig
+import interconnect
+import pytrellis
+import nonrouting
+
+jobs = [
+    (FuzzConfig(job="GLB_DCC", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl",
+                          tiles=["CENTER6:CENTER_EBR_CIB", "CENTER_EBR14:CENTER_EBR",
+                                 "CENTER9:CENTER8", "CENTER8:CENTER7", "CENTER7:CENTER6",
+                                 "CENTER5:CENTER5", "CENTER4:CENTER4"]), 8)
+]
+
+
+def main():
+    pytrellis.load_database("../../../database")
+
+    for job in jobs:
+        cfg, N = job
+        cfg.setup()
+        empty_bitfile = cfg.build_design(cfg.ncl, {})
+        cfg.ncl = "dcc.ncl"
+        for i in range(N):
+            loc = "DCC{}".format(i)
+            def get_substs(mode="DCCA"):
+                if mode == "NONE":
+                    comment = "//"
+                else:
+                    comment = ""
+                return dict(site=loc, comment=comment)
+
+            nonrouting.fuzz_enum_setting(cfg, "{}.MODE".format(loc), ["NONE", "DCCA"],
+                                         lambda x: get_substs(mode=x), empty_bitfile, False)
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/024-glb-branch/fuzzer.py b/fuzzers/machxo2/024-glb-branch/fuzzer.py
new file mode 100644
index 0000000..e4dc626
--- /dev/null
+++ b/fuzzers/machxo2/024-glb-branch/fuzzer.py
@@ -0,0 +1,102 @@
+from fuzzconfig import FuzzConfig
+import interconnect
+import pytrellis
+import argparse
+from nets import net_product
+
+# def mk_nets(tilepos, glb_ids):
+#     branch_nets = []
+#     rc_prefix = "R{}C{}_".format(tilepos[0], tilepos[1])
+#
+#     # Branch Conns... will be
+#     branch_nets.extend(net_product([rc_prefix + "HPBX0{}00"], glb_ids))
+#     return branch_nets
+
+jobs = [
+    # PLC
+    (FuzzConfig(job="GLB_BRANCH37", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["R5C17:PLC"]), ["R5C18_HPBX0300", "R5C18_HPBX0700"]),
+    (FuzzConfig(job="GLB_BRANCH26", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["R5C16:PLC"]), ["R5C17_HPBX0200", "R5C17_HPBX0600"]),
+    (FuzzConfig(job="GLB_BRANCH15", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["R5C15:PLC"]), ["R5C16_HPBX0100", "R5C16_HPBX0500"]),
+    (FuzzConfig(job="GLB_BRANCH04", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["R5C14:PLC"]), ["R5C15_HPBX0000", "R5C15_HPBX0400"]),
+
+    # CIB_EBR
+    (FuzzConfig(job="CIB_EBR_BRANCH26", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C4:CIB_EBR0"]), ["R6C5_HPBX0200", "R6C5_HPBX0600"]),
+    (FuzzConfig(job="CIB_EBR_BRANCH15", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C7:CIB_EBR0"]), ["R6C8_HPBX0100", "R6C8_HPBX0500"]),
+    (FuzzConfig(job="CIB_EBR_BRANCH04", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C10:CIB_EBR0"]), ["R6C11_HPBX0000", "R6C11_HPBX0400"]),
+    (FuzzConfig(job="CIB_EBR_BRANCH37", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C17:CIB_EBR0"]), ["R6C18_HPBX0300", "R6C18_HPBX0700"]),
+
+    # CIB_PIC_T0.
+    (FuzzConfig(job="CIB_PIC_T0_BRANCH37", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R1C9:CIB_PIC_T0"]), ["R1C10_HPBX0300", "R1C10_HPBX0700"]),
+    (FuzzConfig(job="CIB_PIC_T0_BRANCH04", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R1C10:CIB_PIC_T0"]), ["R1C11_HPBX0000", "R1C11_HPBX0400"]),
+    (FuzzConfig(job="CIB_PIC_T0_BRANCH15", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R1C11:CIB_PIC_T0"]), ["R1C12_HPBX0100", "R1C12_HPBX0500"]),
+    (FuzzConfig(job="CIB_PIC_T0_BRANCH26", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R1C12:CIB_PIC_T0"]), ["R1C13_HPBX0200", "R1C13_HPBX0600"]),
+
+    # CIB_EBR0_END0
+    (FuzzConfig(job="CIB_EBR0_END0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C1:CIB_EBR0_END0"]), ["R6C1_HPBX0100", "R6C2_HPBX0200", "R6C2_HPBX0300",
+                                                          "R6C1_HPBX0500", "R6C2_HPBX0600", "R6C2_HPBX0700"]),
+
+    # PIC_L0
+    (FuzzConfig(job="PIC_L0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["PL5:PIC_L0"]), ["R5C1_HPBX0100", "R5C2_HPBX0200", "R5C2_HPBX0300",
+                                              "R5C1_HPBX0500", "R5C2_HPBX0600", "R5C2_HPBX0700"]),
+
+    # CIB_EBR2_END0- This appears to be a noop after other fuzzers run.
+    (FuzzConfig(job="CIB_EBR2_END0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["CIB_R6C22:CIB_EBR2_END0"]), ["R6C22_HPBX0000", "R6C22_HPBX0100",
+                                                          "R6C22_HPBX0400", "R6C22_HPBX0500"]),
+
+    # PIC_R0- This also appears to be a noop after other fuzzers run.
+    (FuzzConfig(job="PIC_R0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["PR5:PIC_R0"]), ["R5C22_HPBX0000", "R5C22_HPBX0100",
+                                              "R5C22_HPBX0400", "R5C22_HPBX0500"]),
+
+    # URC0- This also appears to be a noop after other fuzzers run.
+    (FuzzConfig(job="URC0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["PR1:URC0"]), ["R1C22_HPBX0000", "R1C22_HPBX0100",
+                                              "R1C22_HPBX0400", "R1C22_HPBX0500"]),
+
+    # LRC0- This also appears to be a noop after other fuzzers run.
+    (FuzzConfig(job="LRC0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["PR11:LRC0"]), ["R11C22_HPBX0000", "R11C22_HPBX0100",
+                                              "R11C22_HPBX0400", "R11C22_HPBX0500"]),
+
+    # ULC0
+    (FuzzConfig(job="ULC0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["PL1:ULC0"]), ["R1C1_HPBX0100", "R1C2_HPBX0200", "R1C2_HPBX0300",
+                                              "R1C1_HPBX0500", "R1C2_HPBX0600", "R1C2_HPBX0700"]),
+
+    # LLC0
+    (FuzzConfig(job="LLC0_BRANCH", family="MachXO2", device="LCMXO2-1200HC", ncl="tap.ncl",
+                      tiles=["PL11:LLC0"]), ["R11C1_HPBX0100", "R11C2_HPBX0200", "R11C2_HPBX0300",
+                                              "R11C1_HPBX0500", "R11C2_HPBX0600", "R11C2_HPBX0700"]),
+]
+
+def main(args):
+    pytrellis.load_database("../../../database")
+
+    for job in [jobs[i] for i in args.ids]:
+        cfg, netnames = job
+        cfg.setup()
+        interconnect.fuzz_interconnect_with_netnames(config=cfg, netnames=netnames,
+                                                     netname_filter_union=False)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Center Mux Routing Fuzzer.")
+    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
+                    default=range(0, len(jobs)), help="Job (indices) to run.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/024-glb-branch/tap.ncl b/fuzzers/machxo2/024-glb-branch/tap.ncl
new file mode 100644
index 0000000..aee9b98
--- /dev/null
+++ b/fuzzers/machxo2/024-glb-branch/tap.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal clk_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, CLK);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/030-cib_pict0/cibroute.ncl b/fuzzers/machxo2/030-cib_pict0/cibroute.ncl
new file mode 100644
index 0000000..7e7370b
--- /dev/null
+++ b/fuzzers/machxo2/030-cib_pict0/cibroute.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, A0);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/030-cib_pict0/fuzzer.py b/fuzzers/machxo2/030-cib_pict0/fuzzer.py
new file mode 100644
index 0000000..300acc7
--- /dev/null
+++ b/fuzzers/machxo2/030-cib_pict0/fuzzer.py
@@ -0,0 +1,37 @@
+from collections import defaultdict
+
+from fuzzconfig import FuzzConfig
+import interconnect
+import nets
+import pytrellis
+import re
+
+cfg = FuzzConfig(job="CIBPICT0ROUTE", family="MachXO2", device="LCMXO2-1200HC", ncl="cibroute.ncl", tiles=["CIB_R1C10:CIB_PIC_T0"])
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+
+    span1_re = re.compile(r'R\d+C\d+_[VH]01[NESWTLBR]\d{4}')
+
+    def nn_filter(net, netnames):
+        """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
+        left out by Tcl"""
+        return ((net in netnames or span1_re.match(net)) and nets.is_cib(net)) or nets.machxo2.is_global(net)
+
+    def fc_filter(arc, netnames):
+        """ Ignore connections between two general routing nets. These are edge buffers which vary based on location
+        and must be excluded from the CIB database.
+        """
+        return not (nets.general_routing_re.match(arc[0]) and nets.general_routing_re.match(arc[1]))
+    interconnect.fuzz_interconnect(config=cfg, location=(1, 10),
+                                   netname_predicate=nn_filter,
+                                   fc_predicate=fc_filter,
+                                   netname_filter_union=True,
+                                   enable_span1_fix=True,
+                                   netdir_override=defaultdict(lambda : str("ignore")))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/032-copy-cib_pict0/fuzzer.py b/fuzzers/machxo2/032-copy-cib_pict0/fuzzer.py
new file mode 100644
index 0000000..f592f8d
--- /dev/null
+++ b/fuzzers/machxo2/032-copy-cib_pict0/fuzzer.py
@@ -0,0 +1,15 @@
+import dbcopy
+import pytrellis
+
+# Then copy the BRANCH info from CIB_PIC_T0 back to the other tiles.
+shared_tiles = ["CIB_PIC_T_DUMMY", "CIB_CFG0", "CIB_CFG1", "CIB_CFG2", "CIB_CFG3"]
+
+def main():
+    pytrellis.load_database("../../../database")
+
+    for dest in shared_tiles:
+        dbcopy.dbcopy("MachXO2", "LCMXO2-1200HC", "CIB_PIC_T0", dest)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/034-cib_ebrn/cibroute.ncl b/fuzzers/machxo2/034-cib_ebrn/cibroute.ncl
new file mode 100644
index 0000000..7e7370b
--- /dev/null
+++ b/fuzzers/machxo2/034-cib_ebrn/cibroute.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, A0);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/034-cib_ebrn/fuzzer.py b/fuzzers/machxo2/034-cib_ebrn/fuzzer.py
new file mode 100644
index 0000000..eea53d7
--- /dev/null
+++ b/fuzzers/machxo2/034-cib_ebrn/fuzzer.py
@@ -0,0 +1,67 @@
+from collections import defaultdict
+
+from fuzzconfig import FuzzConfig
+import interconnect
+import nets
+import pytrellis
+import re
+import argparse
+
+jobs = [
+        {
+            "cfg": FuzzConfig(job="CIBEBR0ROUTE", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="cibroute.ncl", tiles=["CIB_R6C10:CIB_EBR0"]),
+            "location": (6, 10),
+            "nn_filter_extra": []
+        },
+        {
+            "cfg": FuzzConfig(job="CIBEBR0END0ROUTE", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="cibroute.ncl", tiles=["CIB_R6C1:CIB_EBR0_END0"]),
+            "location": (6, 1),
+            "nn_filter_extra": ["G_HPBX0100", "G_HPBX0500"]
+        },
+        {
+            "cfg": FuzzConfig(job="CIBEBR2END0ROUTE", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="cibroute.ncl", tiles=["CIB_R6C22:CIB_EBR2_END0"]),
+            "location": (6, 22),
+            "nn_filter_extra": []
+        },
+]
+
+
+def main(args):
+    pytrellis.load_database("../../../database")
+    for job in [jobs[i] for i in args.ids]:
+        cfg = job["cfg"]
+        cfg.setup()
+
+        span1_re = re.compile(r'R\d+C\d+_[VH]01[NESWTLBR]\d{4}')
+
+        def nn_filter(net, netnames):
+            """I want to handle global nets that are associated with this
+            tile manually; any matching nets are filtered out."""
+            if net in job["nn_filter_extra"]:
+                return False
+
+            """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
+            left out by Tcl"""
+            return ((net in netnames or span1_re.match(net)) and nets.is_cib(net)) or nets.machxo2.is_global(net)
+
+        def fc_filter(arc, netnames):
+            """ Ignore connections between two general routing nets. These are edge buffers which vary based on location
+            and must be excluded from the CIB database.
+            """
+            return not (nets.general_routing_re.match(arc[0]) and nets.general_routing_re.match(arc[1]))
+        interconnect.fuzz_interconnect(config=cfg, location=job["location"],
+                                       netname_predicate=nn_filter,
+                                       fc_predicate=fc_filter,
+                                       netname_filter_union=True,
+                                       enable_span1_fix=True,
+                                       netdir_override=defaultdict(lambda : str("ignore")))
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="CIB_EBRn Fuzzer.")
+    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
+                    default=range(0, len(jobs)), help="Job (indices) to run.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/035-copy-cib_ebr0/fuzzer.py b/fuzzers/machxo2/035-copy-cib_ebr0/fuzzer.py
new file mode 100644
index 0000000..fb71f52
--- /dev/null
+++ b/fuzzers/machxo2/035-copy-cib_ebr0/fuzzer.py
@@ -0,0 +1,39 @@
+import dbcopy
+import pytrellis
+import nets
+
+# Based on prior fuzzing conjecture that CIB_EBR
+# 0,1,2, and DUMMY, and CIB_PIC_B0 and CIB_PIC_B_DUMMY have the same layout.
+
+# Pending dbcopy and prjtrellis changes (TODO), CIB_PIC_B0 and CIB_PIC_B_DUMMY
+# need to be manually tweaked to convert U_/D_ prefixes to G_ prefixes. There's
+# too much shared routing to justify rerunning the fuzzers.
+
+shared_tiles = ["CIB_EBR1", "CIB_EBR2", "CIB_EBR_DUMMY"]
+shared_tiles_no_lrudconns = ["CIB_PIC_B0", "CIB_PIC_B_DUMMY"]
+
+# Only globals are the BRANCH connections which are fed by U_/D_ conns with
+# G_ prefixes (muxed).
+def exclude_lrud_conns(conn):
+    src = conn.source
+    sink = conn.sink
+
+    return not (src.startswith("G_CLKO") or sink.startswith("G_CLKI"))
+
+def exclude_lrud_muxes(conn):
+    (src, sink) = conn
+
+    return not (src.startswith("G_CLKI") and sink.startswith("G_CLKO"))
+
+def main():
+    pytrellis.load_database("../../../database")
+
+    for dest in shared_tiles:
+        dbcopy.dbcopy("MachXO2", "LCMXO2-1200HC", "CIB_EBR0", dest)
+
+    for dest in shared_tiles_no_lrudconns:
+        dbcopy.copy_muxes_with_predicate("MachXO2", "LCMXO2-1200HC", "CIB_EBR0", dest, exclude_lrud_muxes)
+        dbcopy.copy_conns_with_predicate("MachXO2", "LCMXO2-1200HC", "CIB_EBR0", dest, exclude_lrud_conns)
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/036-corners/cibroute.ncl b/fuzzers/machxo2/036-corners/cibroute.ncl
new file mode 100644
index 0000000..7e7370b
--- /dev/null
+++ b/fuzzers/machxo2/036-corners/cibroute.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, A0);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/036-corners/fuzzer.py b/fuzzers/machxo2/036-corners/fuzzer.py
new file mode 100644
index 0000000..7591983
--- /dev/null
+++ b/fuzzers/machxo2/036-corners/fuzzer.py
@@ -0,0 +1,80 @@
+from collections import defaultdict
+
+from fuzzconfig import FuzzConfig
+import interconnect
+import nets
+import pytrellis
+import re
+import argparse
+
+jobs = [
+        # URC0 has same routing as `CIB_PIC_T`, except for globals.
+        {
+            "cfg": FuzzConfig(job="URC0ROUTE", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="cibroute.ncl", tiles=["PR1:URC0"]),
+            "location": (1, 22),
+            "nn_filter_extra": []
+        },
+
+        # ULC0 appears to have unique routing and globals.
+        {
+            "cfg": FuzzConfig(job="ULC0ROUTE", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="cibroute.ncl", tiles=["PL1:ULC0"]),
+            "location": (1, 1),
+            "nn_filter_extra": []
+        },
+
+        # LLC0 has same routing and globals as PIC_L0.
+        {
+            "cfg": FuzzConfig(job="LLC0ROUTE", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="cibroute.ncl", tiles=["PL11:LLC0"]),
+            "location": (11, 1),
+            "nn_filter_extra": []
+        },
+
+        # LRC0 has same routing and globals as PIC_R0.
+        {
+            "cfg": FuzzConfig(job="LRC0ROUTE", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="cibroute.ncl", tiles=["PR11:LRC0"]),
+            "location": (11, 22),
+            "nn_filter_extra": []
+        },
+]
+
+
+def main(args):
+    pytrellis.load_database("../../../database")
+    for job in [jobs[i] for i in args.ids]:
+        cfg = job["cfg"]
+        cfg.setup()
+
+        span1_re = re.compile(r'R\d+C\d+_[VH]01[NESWTLBR]\d{4}')
+
+        def nn_filter(net, netnames):
+            """I want to handle global nets that are associated with this
+            tile manually; any matching nets are filtered out."""
+            if net in job["nn_filter_extra"]:
+                return False
+
+            """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
+            left out by Tcl"""
+            return ((net in netnames or span1_re.match(net)) and nets.is_cib(net)) or nets.machxo2.is_global(net)
+
+        def fc_filter(arc, netnames):
+            """ Ignore connections between two general routing nets. These are edge buffers which vary based on location
+            and must be excluded from the CIB database.
+            """
+            return not (nets.general_routing_re.match(arc[0]) and nets.general_routing_re.match(arc[1]))
+        interconnect.fuzz_interconnect(config=cfg, location=job["location"],
+                                       netname_predicate=nn_filter,
+                                       fc_predicate=fc_filter,
+                                       netname_filter_union=True,
+                                       enable_span1_fix=True,
+                                       netdir_override=defaultdict(lambda : str("ignore")))
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Corner Fuzzer.")
+    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
+                    default=range(0, len(jobs)), help="Job (indices) to run.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/050-pio_routing/fuzzer.py b/fuzzers/machxo2/050-pio_routing/fuzzer.py
new file mode 100644
index 0000000..42d8c32
--- /dev/null
+++ b/fuzzers/machxo2/050-pio_routing/fuzzer.py
@@ -0,0 +1,167 @@
+from collections import defaultdict
+from itertools import product
+
+from fuzzconfig import FuzzConfig
+import interconnect
+import nets
+import pytrellis
+import re
+import argparse
+
+import isptcl
+import mk_nets
+
+span1_re = re.compile(r'R\d+C\d+_[VH]01[NESWTLBR]\d{4}')
+jofx_re = re.compile(r'R\d+C\d+_JOFX\d')
+def nn_filter(net, netnames):
+    """ Match nets that are: in the tile according to Tcl, global nets, or span-1 nets that are accidentally
+    left out by Tcl"""
+    return net in netnames or nets.machxo2.is_global(net) or span1_re.match(net)
+
+# JOFX source connections are conjectured to not go to anything.
+# Also ignore edge connections.
+# TODO: We should probably ignore KEEP connections too, but right now am unsure.
+def fc_filter(arc, netnames):
+    return not jofx_re.match(arc[0]) and not (nets.general_routing_re.match(arc[0]) and nets.general_routing_re.match(arc[1]))
+
+# Bank of None means that the I/O connections are in another tile.
+jobs = [
+        {
+           "pos" : (12, 11),
+           "cfg" : FuzzConfig(job="PIOROUTEB", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["PB11:PIC_B0"]),
+           "missing_nets" : None,
+           "nn_filter": nn_filter,
+           "bank" : "B",
+        },
+        {
+           "pos" : (11, 11),
+           "cfg" : FuzzConfig(job="PIOROUTEB_CIB", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["CIB_R11C11:CIB_PIC_B0"]),
+            # A bug in the span1 fix prevents span1 nets from being included.
+            # Just fuzz manually for now.
+           "missing_nets" : ["R10C11_V01N0001", "R10C11_V01N0101"],
+           "nn_filter": nn_filter,
+           "bank" : None,
+        },
+        {
+           "pos" : (10, 1),
+           "cfg" : FuzzConfig(job="PIOROUTEL", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["PL10:PIC_L0"]),
+           "missing_nets" : None,
+           "nn_filter": nn_filter,
+           "bank" : "L"
+        },
+
+        # Probably the same thing as PIC_L0 plus some additional fixed connections?
+        {
+           "pos" : (11, 1),
+           "cfg" : FuzzConfig(job="PIOROUTELLC0", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["PL11:LLC0"]),
+           "missing_nets" : None,
+           "nn_filter": nn_filter,
+           "bank" : "L"
+        },
+
+        # 4
+        {
+           "pos" : (10, 22),
+           "cfg" : FuzzConfig(job="PIOROUTER", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                   tiles=["PR10:PIC_R0"]),
+           "missing_nets" : None,
+           "nn_filter": nn_filter,
+           "bank" : "R"
+        },
+        {
+           "pos" : (0, 12),
+           "cfg" : FuzzConfig(job="PIOROUTET", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["PT12:PIC_T0"]),
+           "missing_nets" : None,
+           "nn_filter" : lambda x, nets: x.startswith("R0C12"),
+           "bank" : "T",
+        },
+        {
+           "pos" : (1, 12),
+           "cfg" : FuzzConfig(job="PIOROUTET_CIB", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["CIB_R1C12:CIB_PIC_T0"]),
+           "missing_nets" : None,
+           "nn_filter": nn_filter,
+           "bank" : None,
+        },
+        {
+           "pos" : (9, 1),
+           "cfg" : FuzzConfig(job="PIOROUTELS0", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["PL9:PIC_LS0"]),
+           "missing_nets" : None,
+           "nn_filter": nn_filter,
+           "bank" : "LS",
+        },
+
+        # 8
+        {
+           "pos" : (3, 22),
+           "cfg" : FuzzConfig(job="PIOROUTERS0", family="MachXO2", device="LCMXO2-1200HC", ncl="pioroute.ncl",
+                                  tiles=["PR3:PIC_RS0"]),
+           "missing_nets" : None,
+           "nn_filter": nn_filter,
+           "bank" : "RS",
+        },
+]
+
+def main(args):
+    pytrellis.load_database("../../../database")
+    for job in [jobs[i] for i in args.ids]:
+        cfg = job["cfg"]
+        cfg.setup()
+
+        if args.i:
+            # Fuzz basic routing, ignore fixed connections to/from I/O pads.
+            interconnect.fuzz_interconnect(config=cfg, location=job["pos"],
+                                           netname_predicate=job["nn_filter"],
+                                           netdir_override=defaultdict(lambda : str("ignore")),
+                                           fc_predicate=fc_filter,
+                                           netname_filter_union=False,
+                                           enable_span1_fix=True)
+
+        if args.m and job["missing_nets"]:
+            interconnect.fuzz_interconnect_with_netnames(config=cfg,
+                                                         netnames=job["missing_nets"],
+                                                         fc_predicate=fc_filter,
+                                                         netname_filter_union=False,
+                                                         bidir=True,
+                                                         netdir_override=defaultdict(lambda : str("ignore")))
+
+
+        if args.p and job["bank"]:
+            # I/O connections in the left/right tiles exist as-if a column "0"
+            # or one past maximum is physically present.
+            if job["bank"].startswith("R"):
+                ab_only = job["bank"].endswith("S")
+                io_nets = mk_nets.io_conns((job["pos"][0], job["pos"][1] + 1), job["bank"], ab_only)
+            elif job["bank"].startswith("L"):
+                ab_only = job["bank"].endswith("S")
+                io_nets = mk_nets.io_conns((job["pos"][0], job["pos"][1] - 1), job["bank"], ab_only)
+            else:
+                io_nets = mk_nets.io_conns((job["pos"][0], job["pos"][1]), job["bank"])
+
+            io_list = [io[0] for io in io_nets]
+            override_dict = {io[0]: io[1] for io in io_nets}
+            print(override_dict)
+
+            interconnect.fuzz_interconnect_with_netnames(config=cfg,
+                                                         netnames=io_list,
+                                                         fc_predicate=fc_filter,
+                                                         netname_filter_union=False,
+                                                         bidir=True,
+                                                         netdir_override=override_dict)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="PIO Routing Fuzzer.")
+    parser.add_argument("-i", action="store_true", help="Fuzz interconnect.")
+    parser.add_argument("-m", action="store_true", help="Fuzz missing nets.")
+    parser.add_argument("-p", action="store_true", help="Fuzz I/O pad connections.")
+    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
+                    default=range(0, len(jobs)), help="Job (indices) to run.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/050-pio_routing/mk_nets.py b/fuzzers/machxo2/050-pio_routing/mk_nets.py
new file mode 100644
index 0000000..8648e26
--- /dev/null
+++ b/fuzzers/machxo2/050-pio_routing/mk_nets.py
@@ -0,0 +1,145 @@
+from nets import net_product, char_range
+from itertools import product, starmap
+from collections import defaultdict
+import re
+
+def io_conns(tile, bank, ab_only=False):
+    # All I/O connections on the left bank are contained in the other banks.
+    all_template = [
+        ("JPADDO{}", "sink"),
+        ("JPADDT{}", "sink"),
+        ("PADDO{}_PIO", "sink"),
+        ("PADDT{}_PIO", "sink"),
+        ("IOLDO{}_PIO","sink"),
+        ("IOLDO{}_{}IOLOGIC","sink"),
+        ("IOLTO{}_PIO","sink"),
+        ("IOLTO{}_{}IOLOGIC","sink"),
+        ("JPADDI{}_PIO", "driver"),
+        ("PADDI{}_{}IOLOGIC", "driver"),
+        ("JDI{}", "driver"),
+        ("INDD{}_{}IOLOGIC", "driver"),
+        ("DI{}_{}IOLOGIC", "sink"),
+        ("JONEG{}_{}IOLOGIC", "sink"),
+        ("JOPOS{}_{}IOLOGIC", "sink"),
+        ("JTS{}_{}IOLOGIC", "sink"),
+        ("JCE{}_{}IOLOGIC", "sink"),
+        ("JLSR{}_{}IOLOGIC", "sink"),
+        ("JCLK{}_{}IOLOGIC", "sink"),
+        ("JIN{}_{}IOLOGIC", "driver"),
+        ("JIP{}_{}IOLOGIC", "driver"),
+        ("INRD{}_PIO", "sink"),
+        ("PG{}_PIO", "sink")
+    ]
+
+    right = [
+        ("DQSW90{}_RIOLOGIC", "sink"),
+        ("DQSR90{}_RIOLOGIC", "sink"),
+        ("DDRCLKPOL{}_RIOLOGIC", "sink")
+    ]
+
+    bottom = [
+        ("JRXDA{2}_{1}IOLOGIC", "driver"),
+        ("JRXD{2}{0}_{1}IOLOGIC", "driver"),
+        ("JDEL{2}{0}_{1}IOLOGIC", "sink"),
+        ("JLIP{}_{}IOLOGIC", "sink"),
+        ("ECLK{}_{}IOLOGIC", "sink"),
+    ]
+
+    top = [
+        ("JTXD{2}{0}_{1}IOLOGIC", "sink"),
+        ("ECLK{}_{}IOLOGIC", "sink"),
+        ("LVDS{}_PIO", "sink"),
+    ]
+
+    if bank == "B":
+        bank_template = bottom
+    elif bank == "T":
+        bank_template = top
+    elif bank.startswith("R"):
+        bank_template = right
+    else:
+        bank_template = []
+
+    # Nets which come in 0-3/0-7 and 0-4, respectively.
+    rxda_re = re.compile("JRXDA\{2\}*")
+    rxd_re = re.compile("JRXD\{2\}\{0\}*")
+    txda_re = re.compile("JTXD\{2\}A*")
+    txd_re = re.compile("JTXD\{2\}\{0\}*")
+    del_re = re.compile("JDEL*")
+    eclk_re = re.compile("ECLKC*")
+
+    netlist = []
+    if ab_only:
+        pads = ("A", "B")
+    else:
+        pads = ("A", "B", "C", "D")
+
+    for pad in pads:
+        # B/BS/T/TSIOLOGIC
+        if bank in ("B", "T"):
+            if pad == "A":
+                io_prefix = bank
+            elif pad == "C":
+                io_prefix = "{}S".format(bank)
+            else:
+                io_prefix = ""
+        # RIOLOGIC
+        elif bank.startswith("R"):
+            io_prefix = "R"
+        # Just "LOGIC"
+        else:
+            io_prefix = ""
+
+        for f, d in all_template:
+            suffix = f.format(pad, io_prefix)
+            netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+
+        if bank.startswith("R"):
+            for f, d in bank_template:
+                suffix = f.format(pad, io_prefix)
+                netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+        elif bank == "B":
+            for f, d in bank_template:
+                if del_re.match(f) and pad in ("A", "C"):
+                    for n in range(5):
+                        suffix = f.format(pad, io_prefix, n)
+                        netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+                elif rxda_re.match(f) and pad == "A":
+                    for n in range(8):
+                        suffix = f.format(pad, io_prefix, n)
+                        netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+                elif rxd_re.match(f) and pad in ("A", "C"):
+                    for n in range(4):
+                        suffix = f.format(pad, io_prefix, n)
+                        netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+                elif pad in ("A", "C") and not rxda_re.match(f):
+                    suffix = f.format(pad, io_prefix)
+                    netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+        elif bank == "T":
+            for f, d in bank_template:
+                if txd_re.match(f) and pad in ("A", "C"):
+                    netrange = range(8) if pad == "A" else range(4)
+                    for n in netrange:
+                        suffix = f.format(pad, io_prefix, n)
+                        netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+                elif eclk_re.match(f) and pad in ("A", "C"):
+                    suffix = f.format(pad, io_prefix)
+                    netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+                elif not txd_re.match(f) and not eclk_re.match(f):
+                    suffix = f.format(pad, io_prefix)
+                    netlist.append(("R{}C{}_{}".format(tile[0], tile[1], suffix), d))
+
+    return netlist
+
+def main():
+    for t, b, ab in zip(
+        ((10, 0), (12, 11), (10, 23), (1, 11), (9, 0), (3, 23)),
+        ("L", "B", "R", "T", "LS", "RS"),
+        (False, False, False, False, True, True)):
+            print("Bank {} (AB only={}):".format(b, ab))
+            for i, n in enumerate(io_conns(t, b, ab)):
+                print(i, n)
+            print("")
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/050-pio_routing/pioroute.ncl b/fuzzers/machxo2/050-pio_routing/pioroute.ncl
new file mode 100644
index 0000000..a7e908a
--- /dev/null
+++ b/fuzzers/machxo2/050-pio_routing/pioroute.ncl
@@ -0,0 +1,39 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp PIO
+   {
+      logical
+      {
+         cellmodel-name PIO;
+         program "TRIMUX:PADDT:::PADDT=0 "
+                 "IOBUF:::PULLMODE=NONE,DRIVE=8, \"
+                    "SLEWRATE=SLOW,HYSTERESIS=NA "
+                 "DATAMUX:PADDO "
+                 "VREF:OFF "
+                 "ODMUX:TRIMUX "
+                 "LVDSMUX:DATAMUX ";
+         primitive IOBUF PIO_pad;
+         primitive PAD PIO;
+      }
+      site "11";
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (PIO, PADDI),
+         // loads
+         (PIO, PADDO);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/051-pio_attrs/empty.ncl b/fuzzers/machxo2/051-pio_attrs/empty.ncl
new file mode 100644
index 0000000..11b72ab
--- /dev/null
+++ b/fuzzers/machxo2/051-pio_attrs/empty.ncl
@@ -0,0 +1,12 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+}
diff --git a/fuzzers/machxo2/051-pio_attrs/fuzzer.py b/fuzzers/machxo2/051-pio_attrs/fuzzer.py
new file mode 100644
index 0000000..9185f61
--- /dev/null
+++ b/fuzzers/machxo2/051-pio_attrs/fuzzer.py
@@ -0,0 +1,245 @@
+from collections import defaultdict
+
+from fuzzconfig import FuzzConfig
+import interconnect
+import nets
+import pytrellis
+import re
+import argparse
+import fuzzloops
+import nonrouting
+import os
+
+jobs = [
+        {
+            "cfg": FuzzConfig(job="PICB0_AB", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="empty.ncl", tiles=["PB11:PIC_B0"]),
+            "side": "B",
+            "pins": [("13", "A"), ("14", "B")]
+        },
+
+        # Split into multiple jobs, because Diamond chokes if the I/Os don't
+        # actually physically exist (QFN32 is default).
+        {
+            "cfg": FuzzConfig(job="PICB0_CD", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="empty.ncl", tiles=["PB6:PIC_B0"]),
+            "side": "B",
+            "pins": [("9", "C"), ("10", "D")]
+        },
+
+        {
+            "cfg": FuzzConfig(job="PICL0_IO", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="empty.ncl", tiles=["PL5:PIC_L0"]),
+            "side": "L",
+            "pins": [("12", "A"), ("13", "B"), ("14", "C"), ("15", "D")],
+            "package": "TQFP100"
+        },
+
+        {
+            "cfg": FuzzConfig(job="PICR0_IO", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="empty.ncl", tiles=["PR5:PIC_R0"]),
+            "side": "R",
+            "pins": [("65", "A"), ("64", "B"), ("63", "C"), ("62", "D")],
+            "package": "TQFP100"
+        },
+
+        {
+            "cfg": FuzzConfig(job="PICT0_IO", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="empty.ncl", tiles=["PT10:PIC_T0"]),
+            "side": "T",
+            "pins": [("97", "A"), ("96", "B")],
+            "package": "TQFP100"
+        },
+
+        # FIXME: WARNING - map: In "LOCATE COMP "pad" SITE "PT10{C,D} pin" ;":
+        # Current SYS_CONFIG setting prohibits pin be used as user IO. This
+        # preference has been disabled. Why?
+        {
+            "cfg": FuzzConfig(job="PICT0_IO", family="MachXO2", device="LCMXO2-1200HC",
+                        ncl="empty.ncl", tiles=["PT12:PIC_T0"]),
+            "side": "T",
+            "pins": [("28", "C"), ("27", "D")]
+        },
+]
+
+# Function constructed from reading the MachXO2 sysIO Usage Guide.
+# Diamond is very sensitive to invalid I/O combinations, and will happily
+# change the I/O type out from under you if you give it a bad combination.
+# This can lead to further errors when the Diamond-assigned I/O type is
+# invalid for the pin (for the complementary pair, especially).
+def get_io_types(dir, pio, side):
+    # Singled-ended I/O types.
+    types = [
+        "LVTTL33",
+        "LVCMOS33",
+        "LVCMOS25",
+        "LVCMOS18",
+        "LVCMOS15",
+        "LVCMOS12",
+        "SSTL25_I",
+        "SSTL18_I",
+        "HSTL18_I"
+    ]
+
+    if dir == "INPUT":
+        types += [
+            "SSTL25_II",
+            "SSTL18_II",
+            "HSTL18_II",
+            "LVCMOS25R33",
+            "LVCMOS18R33",
+            "LVCMOS18R25",
+            "LVCMOS15R33",
+            "LVCMOS15R25"
+        ]
+
+    if dir in ("INPUT", "BIDIR"):
+        types += [
+            "LVCMOS12R33",
+            "LVCMOS12R25",
+            "LVCMOS10R33",
+            "LVCMOS10R25"
+        ]
+
+    if side == "B":
+        # Only bottom bank supports PCI33.
+        types += [
+            "PCI33"
+        ]
+
+    # Differential I/O types.
+    if pio in ("A", "C"):
+        types += [
+            "SSTL25D_I",
+            "SSTL18D_I",
+            "HSTL18D_I",
+            "MIPI",
+            "LVCMOS33D",
+            "LVCMOS25D",
+            "LVCMOS18D",
+            "LVCMOS15D",
+            "LVCMOS12D",
+        ]
+
+        if dir == "INPUT":
+            # True differential inputs.
+            # FIXME: Also supported in bidir?
+            # map_impl.mrp suggests no (warning: violates legal combination
+            # and is ignored.)
+            types += [
+                "SSTL25D_II",
+                "SSTL18D_II",
+                "HSTL18D_II",
+                "LVDS25",
+                "LVPECL33",
+                "MLVDS25",
+                "BLVDS25",
+                "RSDS25"
+            ]
+
+        if dir == "OUTPUT":
+            # Emulated differential output.
+            # FIXME: Also supported in bidir?
+            types += [
+                "LVDS25E",
+                "LVPECL33E",
+                "MLVDS25E",
+                "BLVDS25E",
+                "RSDS25E"
+            ]
+
+            # True differential output. Only supported on Top and primary pair.
+            if pio == "A" and side == "T":
+                types += [
+                    "LVDS25"
+                ]
+    return types
+
+
+def get_cfg_vccio(iotype):
+    m = re.match(r".*(\d)(\d)$", iotype)
+    if not m:
+        return "3.3"
+    return "{}.{}".format(m.group(1), m.group(2))
+
+
+def main(args):
+    pytrellis.load_database("../../../database")
+    for job in [jobs[i] for i in args.ids]:
+        cfg = job["cfg"]
+        side = job["side"]
+        pins = job["pins"]
+
+        os.environ['DEV_PACKAGE'] = job.get("package", "QFN32")
+
+        cfg.setup()
+        empty_bitfile = cfg.build_design(cfg.ncl, {})
+        cfg.ncl = "pio.v"
+
+        def per_pin(pin):
+            loc, pio = pin
+
+            def get_substs(iomode, extracfg=None):
+                if iomode == "NONE":
+                    iodir, type = "NONE", ""
+                else:
+                    iodir, type = iomode.split("_", 1)
+                substs = {
+                    "dir": iodir,
+                    "io_type": type,
+                    "loc": loc,
+                    "extra_attrs": "",
+                    "cfg_vio": "3.3"
+                }
+                if extracfg is not None:
+                    substs["extra_attrs"] = '(* {}="{}" *)'.format(extracfg[0], extracfg[1])
+                if side == "B":
+                    substs["cfg_vio"] = get_cfg_vccio(type)
+                return substs
+
+            modes = ["NONE"]
+            for iodir in ("INPUT", "OUTPUT", "BIDIR"):
+                modes += [iodir + "_" + _ for _ in get_io_types(iodir, pio, side)]
+
+            nonrouting.fuzz_enum_setting(cfg, "PIO{}.BASE_TYPE".format(pio), modes,
+                                         lambda x: get_substs(iomode=x),
+                                         empty_bitfile, False)
+
+            nonrouting.fuzz_enum_setting(cfg, "PIO{}.PULLMODE".format(pio), ["UP", "DOWN", "NONE", "KEEPER", "FAILSAFE"],
+                                         lambda x: get_substs(iomode="INPUT_LVCMOS33", extracfg=("PULLMODE", x)),
+                                         empty_bitfile)
+            nonrouting.fuzz_enum_setting(cfg, "PIO{}.SLEWRATE".format(pio), ["FAST", "SLOW"],
+                                         lambda x: get_substs(iomode="OUTPUT_LVCMOS33", extracfg=("SLEWRATE", x)),
+                                         empty_bitfile)
+            # # FIXME: Do LVCMOS12, which is 2/6mA.
+            nonrouting.fuzz_enum_setting(cfg, "PIO{}.DRIVE".format(pio), ["4", "8", "12", "16", "24"],
+                                         lambda x: get_substs(iomode="OUTPUT_LVCMOS33", extracfg=("DRIVE", x)),
+                                         empty_bitfile)
+            nonrouting.fuzz_enum_setting(cfg, "PIO{}.HYSTERESIS".format(pio), ["SMALL", "LARGE"],
+                                         lambda x: get_substs(iomode="INPUT_LVCMOS33", extracfg=("HYSTERESIS", x)),
+                                         empty_bitfile)
+            nonrouting.fuzz_enum_setting(cfg, "PIO{}.OPENDRAIN".format(pio), ["ON", "OFF"],
+                                         lambda x: get_substs(iomode="OUTPUT_LVCMOS33", extracfg=("OPENDRAIN", x)),
+                                         empty_bitfile)
+            if side in "B":
+                nonrouting.fuzz_enum_setting(cfg, "PIO{}.CLAMP".format(pio), ["PCI", "OFF"],
+                                             lambda x: get_substs(iomode="INPUT_PCI33", extracfg=("CLAMP", x)),
+                                             empty_bitfile)
+            else:
+                nonrouting.fuzz_enum_setting(cfg, "PIO{}.CLAMP".format(pio), ["ON", "OFF"],
+                                             lambda x: get_substs(iomode="INPUT_LVCMOS33", extracfg=("CLAMP", x)),
+                                             empty_bitfile)
+            if side in "T" and pio in "A":
+                nonrouting.fuzz_enum_setting(cfg, "PIO{}.DIFFDRIVE".format(pio), ["1.25"],
+                                             lambda x: get_substs(iomode="OUTPUT_LVDS25", extracfg=("DIFFDRIVE", x)),
+                                             empty_bitfile)
+
+        fuzzloops.parallel_foreach(pins, per_pin)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="PIO Attributes Fuzzer.")
+    parser.add_argument(dest="ids", metavar="N", type=int, nargs="*",
+                    default=range(0, len(jobs)), help="Job (indices) to run.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/051-pio_attrs/pio.lpf b/fuzzers/machxo2/051-pio_attrs/pio.lpf
new file mode 100644
index 0000000..0c934b5
--- /dev/null
+++ b/fuzzers/machxo2/051-pio_attrs/pio.lpf
@@ -0,0 +1 @@
+SYSCONFIG CONFIG_IOVOLTAGE = ${cfg_vio};
diff --git a/fuzzers/machxo2/051-pio_attrs/pio.v b/fuzzers/machxo2/051-pio_attrs/pio.v
new file mode 100644
index 0000000..0fa131a
--- /dev/null
+++ b/fuzzers/machxo2/051-pio_attrs/pio.v
@@ -0,0 +1,68 @@
+`define ${dir}
+
+`ifdef NONE
+
+module top(input x);
+
+// Minimum legal empty module
+wire dummy;
+
+// Dummy load
+GSR gsr_i(.GSR(dummy));
+
+// Dummy source
+OSCH osc_i(.OSC(dummy));
+
+
+endmodule
+
+`else
+
+module top(inout pad);
+
+`ifdef BIDIR
+
+wire dummyo, dummyi;
+
+(* keep *)
+(* LOC="${loc}" *)
+(* IO_TYPE="${io_type}" *)
+${extra_attrs}
+BB b_b(.B(pad), .O(dummyo), .I(1'b1), .T(dummyi));
+
+// Dummy load
+GSR gsr_i(.GSR(dummyo));
+
+// Dummy source
+OSCH osc_i(.OSC(dummyi));
+
+`endif
+
+`ifdef INPUT
+
+wire dummyo;
+
+(* keep *)
+(* LOC="${loc}" *)
+(* IO_TYPE="${io_type}" *)
+${extra_attrs}
+IB i_b(.I(pad), .O(dummyo));
+
+// Dummy load
+GSR gsr_i(.GSR(dummyo));
+
+`endif
+
+`ifdef OUTPUT
+
+(* keep *)
+(* LOC="${loc}" *)
+(* IO_TYPE="${io_type}" *)
+${extra_attrs}
+OB o_b(.O(pad), .I(1'b1));
+
+`endif
+
+endmodule
+
+`endif
diff --git a/fuzzers/machxo2/052-pio_fixup/fuzzer.py b/fuzzers/machxo2/052-pio_fixup/fuzzer.py
new file mode 100644
index 0000000..0bdf954
--- /dev/null
+++ b/fuzzers/machxo2/052-pio_fixup/fuzzer.py
@@ -0,0 +1,10 @@
+import dbfixup
+import pytrellis
+
+def main():
+    pytrellis.load_database("../../../database")
+    dbfixup.remove_enum_bits("MachXO2", "LCMXO2-1200HC", "PIC_L0", (29, 11))
+    dbfixup.remove_enum_bits("MachXO2", "LCMXO2-1200HC", "PIC_R0", (29, 59), (0, 48))
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/053-copy-pio_routing/fuzzer.py b/fuzzers/machxo2/053-copy-pio_routing/fuzzer.py
new file mode 100644
index 0000000..389de28
--- /dev/null
+++ b/fuzzers/machxo2/053-copy-pio_routing/fuzzer.py
@@ -0,0 +1,86 @@
+import dbcopy
+import pytrellis
+import nets
+
+# In the below filters: accept HF/HL/and HBSX fixed connections to be copied
+# for now.
+def exclude_abcd_conns(conn):
+    if isinstance(conn, pytrellis.FixedConnection):
+        src = conn.source
+        sink = conn.sink
+    else:
+        (src, sink) = conn
+    if (src.endswith("A") or "A_" in src
+        or src.endswith("B") or "B_" in src
+        or src.endswith("C") or "C_" in src
+        or src.endswith("D") or "D_" in src):
+        return False
+    elif (sink.endswith("A") or "A_" in sink
+          or sink.endswith("B") or "B_" in sink
+          or sink.endswith("C") or "C_" in sink
+          or sink.endswith("D") or "D_" in sink):
+        return False
+    else:
+        return True
+
+def exclude_cd_conns(conn):
+    if isinstance(conn, pytrellis.FixedConnection):
+        src = conn.source
+        sink = conn.sink
+    else:
+        (src, sink) = conn
+    if src.endswith("C") or "C_" in src or src.endswith("D") or "D_" in src:
+        return False
+    elif sink.endswith("C") or "C_" in sink or sink.endswith("D") or "D_" in sink:
+        return False
+    else:
+        return True
+
+# URC0 has same routing as `CIB_PIC_T`, but the globals are unique.
+def exclude_globals(conn):
+    if isinstance(conn, pytrellis.FixedConnection):
+        src = conn.source
+        sink = conn.sink
+    else:
+        (src, sink) = conn
+    return not (src.startswith("G_") and (sink.startswith("BRANCH_") or sink.startswith("G_")))
+
+def include_globals_only(conn):
+    return not exclude_globals(conn)
+
+# Key: (Mux Predicate, Conn Predicate)
+pio_tiles_l = {
+    "PIC_LS0" : (exclude_cd_conns, exclude_cd_conns),
+    "PIC_L0_VREF3" : (lambda a: True, lambda c: True),
+    "PIC_L0_DUMMY" : (exclude_abcd_conns, exclude_abcd_conns),
+    "LLC0" : (exclude_abcd_conns, exclude_abcd_conns),
+}
+
+pio_tiles_r = {
+    "PIC_RS0" : (exclude_cd_conns, exclude_cd_conns),
+    "PIC_R0_DUMMY" : (exclude_abcd_conns, exclude_abcd_conns),
+    "LRC0" : (exclude_abcd_conns, exclude_abcd_conns),
+}
+
+pio_tiles_cib = {
+    "URC0" : (exclude_globals, exclude_globals)
+}
+
+def main():
+    pytrellis.load_database("../../../database")
+
+    for dest, pred in pio_tiles_l.items():
+        dbcopy.copy_muxes_with_predicate("MachXO2", "LCMXO2-1200HC", "PIC_L0", dest, pred[0])
+        dbcopy.copy_conns_with_predicate("MachXO2", "LCMXO2-1200HC", "PIC_L0", dest, pred[1])
+
+    for dest, pred in pio_tiles_r.items():
+        dbcopy.copy_muxes_with_predicate("MachXO2", "LCMXO2-1200HC", "PIC_R0", dest, pred[0])
+        dbcopy.copy_conns_with_predicate("MachXO2", "LCMXO2-1200HC", "PIC_R0", dest, pred[1])
+
+    for dest, pred in pio_tiles_cib.items():
+        dbcopy.copy_muxes_with_predicate("MachXO2", "LCMXO2-1200HC", "CIB_PIC_T0", dest, pred[0])
+        dbcopy.copy_conns_with_predicate("MachXO2", "LCMXO2-1200HC", "CIB_PIC_T0", dest, pred[1])
+
+
+if __name__ == "__main__":
+    main()
diff --git a/fuzzers/machxo2/102-oscg/empty.ncl b/fuzzers/machxo2/102-oscg/empty.ncl
new file mode 100644
index 0000000..7779581
--- /dev/null
+++ b/fuzzers/machxo2/102-oscg/empty.ncl
@@ -0,0 +1,11 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+}
diff --git a/fuzzers/machxo2/102-oscg/fuzzer.py b/fuzzers/machxo2/102-oscg/fuzzer.py
new file mode 100644
index 0000000..eaac68b
--- /dev/null
+++ b/fuzzers/machxo2/102-oscg/fuzzer.py
@@ -0,0 +1,84 @@
+from fuzzconfig import FuzzConfig
+import nonrouting
+import pytrellis
+import fuzzloops
+import interconnect
+import argparse
+
+cfg = FuzzConfig(job="OSCH", family="MachXO2", device="LCMXO2-1200HC", ncl="empty.ncl",
+                                          tiles=["PT8:PIC_T_DUMMY_OSC",
+                                                "PT4:CFG0", "PT5:CFG1",
+                                                "PT6:CFG2", "PT7:CFG3",
+                                                "CIB_R1C4:CIB_CFG0",
+                                                "CIB_R1C5:CIB_CFG1"])
+
+cfg_r = FuzzConfig(job="OSCH_ROUTE", family="MachXO2", device="LCMXO2-1200HC", ncl="osc_routing.ncl",
+                                          tiles=["CIB_R1C4:CIB_CFG0"])
+
+
+def get_substs(mode="OSCH", nom_freq="2.08", stdby="0"):
+    if mode == "NONE":
+        comment = "//"
+    else:
+        comment = ""
+
+    if stdby == "1":
+        stdby = ""
+        stdby_0 = "//"
+    else:
+        stdby = "//"
+        stdby_0 = ""
+
+    if nom_freq == "2.08":
+        using_non_default_freq = ""
+        using_default_freq = "//"
+    else:
+        using_non_default_freq = "//"
+        using_default_freq = ""
+
+    return dict(comment=comment,
+                nom_freq=nom_freq,
+                stdby=stdby,
+                stdby_0=stdby_0,
+                using_non_default_freq=using_non_default_freq,
+                using_default_freq=using_default_freq)
+
+def main(args):
+    pytrellis.load_database("../../../database")
+    cfg.setup()
+    empty_bitfile = cfg.build_design(cfg.ncl, {})
+    cfg.ncl = "osc.ncl"
+
+    if args.e:
+        nonrouting.fuzz_enum_setting(cfg, "OSCH.STDBY", ["0", "1"],
+                                     lambda x: get_substs(stdby=x), empty_bitfile)
+        nonrouting.fuzz_enum_setting(cfg, "OSCH.MODE", ["NONE", "OSCH"],
+                                     lambda x: get_substs(mode=x), empty_bitfile)
+
+    if args.f:
+        nonrouting.fuzz_enum_setting(cfg, "OSCH.NOM_FREQ",
+            ["{}".format(i) for i in [
+                2.08, 2.15, 2.22, 2.29, 2.38, 2.46, 2.56, 2.66, 2.77, 2.89,
+                3.02, 3.17, 3.33, 3.50, 3.69, 3.91, 4.16, 4.29, 4.43, 4.59,
+                4.75, 4.93, 5.12, 5.32, 5.54, 5.78, 6.05, 6.33, 6.65, 7.00,
+                7.39, 7.82, 8.31, 8.58, 8.87, 9.17, 9.50, 9.85, 10.23, 10.64,
+                11.08, 11.57, 12.09, 12.67, 13.30, 14.00, 14.78, 15.65, 15.65, 16.63,
+                17.73, 19.00, 20.46, 22.17, 24.18, 26.60, 29.56, 33.25, 38.00, 44.33,
+                53.20, 66.50, 88.67, 133.00
+            ]], lambda x: get_substs(nom_freq=x), empty_bitfile)
+
+    if args.r:
+        cfg_r.setup()
+        interconnect.fuzz_interconnect_with_netnames(
+            cfg_r,
+            ["R1C4_JOSC_OSC"],
+            bidir=True,
+            netdir_override={"R1C4_JOSC_OSC" : "driver"})
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="OSCH Fuzzer.")
+    parser.add_argument("-e", action="store_true", help="Fuzz other enums.")
+    parser.add_argument("-f", action="store_true", help="Fuzz frequency enum.")
+    parser.add_argument("-r", action="store_true", help="Fuzz routing.")
+    args = parser.parse_args()
+    main(args)
diff --git a/fuzzers/machxo2/102-oscg/osc.ncl b/fuzzers/machxo2/102-oscg/osc.ncl
new file mode 100644
index 0000000..1396f62
--- /dev/null
+++ b/fuzzers/machxo2/102-oscg/osc.ncl
@@ -0,0 +1,23 @@
+::FROM-WRITER;
+design top
+{
+   device
+   {
+       architecture xo2c00;
+       device LCMXO2-1200HC;
+       package QFN32;
+       performance "6";
+   }
+
+   ${comment} comp OSC
+   ${comment} {
+   ${comment}    logical {
+   ${comment}       cellmodel-name OSC;
+   ${comment}       program "${program}"
+   ${using_non_default_freq} ${stdby} ${comment}               "OSCH:#ON ";
+   ${using_non_default_freq} ${stdby_0} ${comment}               "OSCH::::STDBY=0 ";
+   ${using_default_freq} ${comment}               "OSCH:::NOM_FREQ=${nom_freq}:STDBY=0 ";
+   ${comment}    }
+   ${comment}    site OSC;
+   ${comment} }
+}
diff --git a/fuzzers/machxo2/102-oscg/osc_routing.ncl b/fuzzers/machxo2/102-oscg/osc_routing.ncl
new file mode 100644
index 0000000..7e7370b
--- /dev/null
+++ b/fuzzers/machxo2/102-oscg/osc_routing.ncl
@@ -0,0 +1,35 @@
+::FROM-WRITER;
+design top
+{
+    device
+    {
+        architecture xo2c00;
+        device LCMXO2-1200HC;
+        package QFN32;
+        performance "6";
+    }
+
+   comp SLICE_0
+      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
+   {
+      logical
+      {
+         cellmodel-name SLICE;
+         program "MODE:LOGIC "
+                 "K0::H0=0 "
+                 "F0:F ";
+         primitive K0 i3_4_lut;
+      }
+      site R2C2A;
+   }
+
+    signal q_c
+   {
+      signal-pins
+         // drivers
+         (SLICE_0, F0),
+         // loads
+         (SLICE_0, A0);
+      ${route}
+   }
+}
diff --git a/fuzzers/machxo2/900-db_fixup/fuzzer.py b/fuzzers/machxo2/900-db_fixup/fuzzer.py
new file mode 100644
index 0000000..100a28c
--- /dev/null
+++ b/fuzzers/machxo2/900-db_fixup/fuzzer.py
@@ -0,0 +1,19 @@
+import dbfixup
+import pytrellis
+
+device = "LCMXO2-1200HC"
+
+
+def main():
+    pytrellis.load_database("../../../database")
+    chip = pytrellis.Chip("LCMXO2-1200HC")
+    tiletypes = set()
+    for tile in chip.get_all_tiles():
+        tiletypes.add(tile.info.type)
+
+    for tiletype in sorted(tiletypes):
+        dbfixup.dbfixup("MachXO2", device, tiletype)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/libtrellis/include/Bels.hpp b/libtrellis/include/Bels.hpp
index cb4b5ea..ad3faa8 100644
--- a/libtrellis/include/Bels.hpp
+++ b/libtrellis/include/Bels.hpp
@@ -5,7 +5,7 @@
 #include "RoutingGraph.hpp"
 
 namespace Trellis {
-namespace Bels {
+namespace Ecp5Bels {
 
 void add_lc(RoutingGraph &graph, int x, int y, int z);
 void add_pio(RoutingGraph &graph, int x, int y, int z);
@@ -21,6 +21,18 @@
 void add_misc(RoutingGraph &graph, const std::string &name, int x, int y);
 void add_ioclk_bel(RoutingGraph &graph, const std::string &name, int x, int y, int i, int bank = -1);
 }
+
+namespace MachXO2Bels {
+
+void add_lc(RoutingGraph &graph, int x, int y, int z);
+void add_pio(RoutingGraph &graph, int x, int y, int z);
+void add_dcc(RoutingGraph &graph, int x, int y, /* const std::string &name, */ int z);
+void add_dcm(RoutingGraph &graph, int x, int y, int n, int z);
+void add_osch(RoutingGraph &graph, int x, int y, int z);
+
+}
+
+
 }
 
 #endif
diff --git a/libtrellis/include/Chip.hpp b/libtrellis/include/Chip.hpp
index 1c12c3b..c5295ce 100644
--- a/libtrellis/include/Chip.hpp
+++ b/libtrellis/include/Chip.hpp
@@ -75,7 +75,7 @@
     int spine_row, spine_col;
 };
 
-struct GlobalsInfo
+struct Ecp5GlobalsInfo
 {
     vector<GlobalRegion> quadrants;
     vector<TapSegment> tapsegs;
@@ -88,6 +88,42 @@
     pair<int, int> get_spine_driver(std::string quadrant, int col);
 };
 
+struct LeftRightConn
+{
+    string name;
+    int row;
+    std::pair<int, int> row_span;
+};
+
+inline bool operator==(const LeftRightConn &a, const LeftRightConn &b)
+{
+    return (a.name == b.name) && (a.row == b.row) && (a.row_span == b.row_span);
+}
+
+
+// Some columns contain global routing which EPIC shows as missing DCCA
+// primitives between L/R and U/D connections. None of the DCCs between these
+// connections appear to physically exist on-chip. However, the bitstream
+// does appear to treat them specially, so keep this info around.
+struct MissingDccs
+{
+    int row;
+    std::vector<int> missing;
+};
+
+inline bool operator==(const MissingDccs &a, const MissingDccs &b)
+{
+    return (a.row == b.row) && (a.missing == b.missing);
+}
+
+struct MachXO2GlobalsInfo
+{
+    std::vector<LeftRightConn> lr_conns;
+    std::vector<std::vector<int>> ud_conns;
+    std::vector<std::vector<pair<int, int>>> branch_spans;
+    std::vector<MissingDccs> missing_dccs;
+};
+
 class Tile;
 
 // A difference between two Chips
@@ -148,8 +184,15 @@
     // Block RAM initialisation (WIP)
     map<uint16_t, vector<uint16_t>> bram_data;
 
-    // Globals data
-    GlobalsInfo global_data;
+    // Globals data- Should be a variant, but I couldn't get boost::python
+    // to behave with boost::variant.
+    Ecp5GlobalsInfo global_data_ecp5;
+    MachXO2GlobalsInfo global_data_machxo2;
+
+private:
+    // Factory functions
+    shared_ptr<RoutingGraph> get_routing_graph_ecp5();
+    shared_ptr<RoutingGraph> get_routing_graph_machxo2();
 };
 
 ChipDelta operator-(const Chip &a, const Chip &b);
diff --git a/libtrellis/include/Database.hpp b/libtrellis/include/Database.hpp
index 1571277..d495cca 100644
--- a/libtrellis/include/Database.hpp
+++ b/libtrellis/include/Database.hpp
@@ -43,10 +43,12 @@
 // Obtain basic information about a device
 ChipInfo get_chip_info(const DeviceLocator &part);
 
-struct GlobalsInfo;
+struct Ecp5GlobalsInfo;
+struct MachXO2GlobalsInfo;
 
 // Obtain global network information for a chip
-GlobalsInfo get_global_info(const DeviceLocator &part);
+Ecp5GlobalsInfo get_global_info_ecp5(const DeviceLocator &part);
+MachXO2GlobalsInfo get_global_info_machxo2(const DeviceLocator &part);
 
 // Obtain the tilegrid for a part
 struct TileInfo;
diff --git a/libtrellis/include/DedupChipdb.hpp b/libtrellis/include/DedupChipdb.hpp
index 88688c9..fc6468f 100644
--- a/libtrellis/include/DedupChipdb.hpp
+++ b/libtrellis/include/DedupChipdb.hpp
@@ -357,6 +357,32 @@
 
 shared_ptr<DedupChipdb> make_dedup_chipdb(Chip &chip);
 
+/*
+An optimized chip database is a database with the following properties, intended to be used in place-and-route flows.
+ - All wire, bel and arc IDs are sequential starting from zero at a location
+ - Sequential IDs means it is possible to store wires, bels, and arcs in vectors instead of maps
+ - The database is fully linked: wires contain arcs and bel pins up and downhill, arcs store their up and down wires,
+   etc
+
+This database is _not_ deduplicated, meaning global data is still stored directly
+in this database and relative coordinates are NOT used! Types are reused when
+possible.
+*/
+
+typedef RelId OptId;
+typedef DdArcData OptArcData;
+
+struct OptimizedChipdb : public IdStore
+{
+    OptimizedChipdb();
+
+    OptimizedChipdb(const IdStore &base);
+
+    map<Location, LocationData> tiles;
+};
+
+shared_ptr<OptimizedChipdb> make_optimized_chipdb(Chip &chip);
+
 }
 }
 
diff --git a/libtrellis/include/RoutingGraph.hpp b/libtrellis/include/RoutingGraph.hpp
index 9561b6b..bacc6a1 100644
--- a/libtrellis/include/RoutingGraph.hpp
+++ b/libtrellis/include/RoutingGraph.hpp
@@ -6,8 +6,12 @@
 #include <vector>
 #include <string>
 #include <set>
+#include <regex>
 #include <boost/functional/hash.hpp>
 
+#include "Chip.hpp"
+#include "Database.hpp"
+
 using namespace std;
 
 namespace Trellis {
@@ -132,6 +136,7 @@
 
     // Must be set up beforehand
     std::string chip_name;
+    std::string chip_family;
     std::string chip_prefix;
     int max_row, max_col;
 
@@ -154,6 +159,32 @@
     // Add a Bel input or output pin
     void add_bel_input(RoutingBel &bel, ident_t pin, int wire_x, int wire_y, ident_t wire_name);
     void add_bel_output(RoutingBel &bel, ident_t pin, int wire_x, int wire_y, ident_t wire_name);
+
+private:
+    // Factory functions
+    RoutingId globalise_net_ecp5(int row, int col, const std::string &db_name);
+    RoutingId globalise_net_machxo2(int row, int col, const std::string &db_name);
+
+    // Algorithm to give global nets a unique position in MachXO2 devices.
+    // ECP5 defers global routing to nextpnr.
+    // Given the tile from where a net was queried, find the actual physical
+    // wire on chip that corresponds to the queried net rather than the
+    // imprecise location stored with the database.
+    RoutingId find_machxo2_global_position(int row, int col, const std::string &db_name);
+    // We need access to globals.json to correctly assign global positions.
+    // Internal use only- do NOT expose below methods, members, and types to
+    // pytrellis for now.
+    MachXO2GlobalsInfo global_data_machxo2;
+    enum GlobalType {
+        CENTER,
+        LEFT_RIGHT,
+        UP_DOWN,
+        BRANCH,
+        OTHER,
+        NONE
+    };
+    // Helper function to put all regexes in one place.
+    GlobalType get_global_type_from_name(const std::string &db_name, smatch &match);
 };
 }
 
diff --git a/libtrellis/include/Tile.hpp b/libtrellis/include/Tile.hpp
index 4c5059f..3c612ab 100644
--- a/libtrellis/include/Tile.hpp
+++ b/libtrellis/include/Tile.hpp
@@ -10,6 +10,8 @@
 #include "CRAM.hpp"
 
 namespace Trellis {
+
+extern map<pair<int, int>, pair<int, int>> center_map;
 pair<int, int> get_row_col_pair_from_chipsize(string name, pair<int, int> chip_size, int bias);
 
 // Basic information about a site
diff --git a/libtrellis/src/Bels.cpp b/libtrellis/src/Bels.cpp
index d8493a9..64a9025 100644
--- a/libtrellis/src/Bels.cpp
+++ b/libtrellis/src/Bels.cpp
@@ -3,7 +3,7 @@
 #include "Database.hpp"
 #include "BitDatabase.hpp"
 namespace Trellis {
-namespace Bels {
+namespace Ecp5Bels {
 
 void add_lc(RoutingGraph &graph, int x, int y, int z) {
     char l = "ABCD"[z];
@@ -635,4 +635,162 @@
 
 
 }
+
+namespace MachXO2Bels {
+    void add_lc(RoutingGraph &graph, int x, int y, int z) {
+        char l = "ABCD"[z];
+        string name = string("SLICE") + l;
+        int lc0 = z * 2;
+        int lc1 = z * 2 + 1;
+        RoutingBel bel;
+        bel.name = graph.ident(name);
+        bel.type = graph.ident("SLICE");
+        bel.loc.x = x;
+        bel.loc.y = y;
+        bel.z = z;
+
+        // Bel in/outs ordered by going from the bottom-up of each SLICE, clockwise.
+        if(z == 0) {
+            graph.add_bel_input(bel, graph.ident("FCI"), x, y, graph.ident(fmt("FCI_SLICE")));
+        } else {
+            graph.add_bel_input(bel, graph.ident("FCI"), x, y, graph.ident(fmt("FCI" << l << "_SLICE")));
+        }
+
+        graph.add_bel_input(bel, graph.ident("CLK"), x, y, graph.ident(fmt("CLK" << z << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("LSR"), x, y, graph.ident(fmt("LSR" << z << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("CE"), x, y, graph.ident(fmt("CE" << z << "_SLICE")));
+
+        if(z == 0 || z == 1) {
+            graph.add_bel_input(bel, graph.ident("WCK"), x, y, graph.ident(fmt("WCK" << z << "_SLICE")));
+            graph.add_bel_input(bel, graph.ident("WRE"), x, y, graph.ident(fmt("WRE" << z << "_SLICE")));
+
+            graph.add_bel_input(bel, graph.ident("WD1"), x, y, graph.ident(fmt("WD1" << l << "_SLICE")));
+            graph.add_bel_input(bel, graph.ident("WD0"), x, y, graph.ident(fmt("WD0" << l << "_SLICE")));
+
+            graph.add_bel_input(bel, graph.ident("WAD3"), x, y, graph.ident(fmt("WAD3" << l << "_SLICE")));
+            graph.add_bel_input(bel, graph.ident("WAD2"), x, y, graph.ident(fmt("WAD2" << l << "_SLICE")));
+            graph.add_bel_input(bel, graph.ident("WAD1"), x, y, graph.ident(fmt("WAD1" << l << "_SLICE")));
+            graph.add_bel_input(bel, graph.ident("WAD0"), x, y, graph.ident(fmt("WAD0" << l << "_SLICE")));
+        }
+
+        graph.add_bel_input(bel, graph.ident("FXA"), x, y, graph.ident(fmt("FXA" << l << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("FXB"), x, y, graph.ident(fmt("FXB" << l << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("M0"), x, y, graph.ident(fmt("M" << lc0 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("M1"), x, y, graph.ident(fmt("M" << lc1 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("DI0"), x, y, graph.ident(fmt("DI" << lc0 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("DI1"), x, y, graph.ident(fmt("DI" << lc1 << "_SLICE")));
+
+        graph.add_bel_input(bel, graph.ident("A0"), x, y, graph.ident(fmt("A" << lc0 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("B0"), x, y, graph.ident(fmt("B" << lc0 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("C0"), x, y, graph.ident(fmt("C" << lc0 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("D0"), x, y, graph.ident(fmt("D" << lc0 << "_SLICE")));
+
+        graph.add_bel_input(bel, graph.ident("A1"), x, y, graph.ident(fmt("A" << lc1 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("B1"), x, y, graph.ident(fmt("B" << lc1 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("C1"), x, y, graph.ident(fmt("C" << lc1 << "_SLICE")));
+        graph.add_bel_input(bel, graph.ident("D1"), x, y, graph.ident(fmt("D" << lc1 << "_SLICE")));
+
+
+        if(z == 3) {
+            graph.add_bel_output(bel, graph.ident("FCO"), x, y, graph.ident(fmt("FCO_SLICE")));
+        } else {
+            graph.add_bel_output(bel, graph.ident("FCO"), x, y, graph.ident(fmt("FCO" << l << "_SLICE")));
+        }
+
+        if(z == 2) {
+            graph.add_bel_output(bel, graph.ident("WDO3"), x, y, graph.ident(fmt("WDO3" << l << "_SLICE")));
+            graph.add_bel_output(bel, graph.ident("WDO2"), x, y, graph.ident(fmt("WDO2" << l << "_SLICE")));
+            graph.add_bel_output(bel, graph.ident("WDO1"), x, y, graph.ident(fmt("WDO1" << l << "_SLICE")));
+            graph.add_bel_output(bel, graph.ident("WDO0"), x, y, graph.ident(fmt("WDO0" << l << "_SLICE")));
+
+            graph.add_bel_output(bel, graph.ident("WADO3"), x, y, graph.ident(fmt("WADO3" << l << "_SLICE")));
+            graph.add_bel_output(bel, graph.ident("WADO2"), x, y, graph.ident(fmt("WADO2" << l << "_SLICE")));
+            graph.add_bel_output(bel, graph.ident("WADO1"), x, y, graph.ident(fmt("WADO1" << l << "_SLICE")));
+            graph.add_bel_output(bel, graph.ident("WADO0"), x, y, graph.ident(fmt("WADO0" << l << "_SLICE")));
+        }
+
+        graph.add_bel_output(bel, graph.ident("OFX1"), x, y, graph.ident(fmt("FX" << l << "_SLICE")));
+        graph.add_bel_output(bel, graph.ident("Q1"), x, y, graph.ident(fmt("Q" << lc1 << "_SLICE")));
+        graph.add_bel_output(bel, graph.ident("F1"), x, y, graph.ident(fmt("F" << lc1 << "_SLICE")));
+        graph.add_bel_output(bel, graph.ident("Q0"), x, y, graph.ident(fmt("Q" << lc0 << "_SLICE")));
+        graph.add_bel_output(bel, graph.ident("F0"), x, y, graph.ident(fmt("F" << lc0 << "_SLICE")));
+        graph.add_bel_output(bel, graph.ident("OFX0"), x, y, graph.ident(fmt("F5" << l << "_SLICE")));
+
+        graph.add_bel(bel);
+    }
+
+    void add_pio(RoutingGraph &graph, int x, int y, int z) {
+        char l = "ABCD"[z];
+        string name = string("PIO") + l;
+        RoutingBel bel;
+        bel.name = graph.ident(name);
+        bel.type = graph.ident("PIO");
+        bel.loc.x = x;
+        bel.loc.y = y;
+        bel.z = z;
+
+        graph.add_bel_input(bel, graph.ident("I"), x, y, graph.ident(fmt("PADDO" << l << "_PIO")));
+        graph.add_bel_input(bel, graph.ident("T"), x, y, graph.ident(fmt("PADDT" << l << "_PIO")));
+        graph.add_bel_output(bel, graph.ident("O"), x, y, graph.ident(fmt("JPADDI" << l << "_PIO")));
+
+        graph.add_bel_input(bel, graph.ident("IOLDO"), x, y, graph.ident(fmt("IOLDO" << l << "_PIO")));
+        graph.add_bel_input(bel, graph.ident("IOLTO"), x, y, graph.ident(fmt("IOLTO" << l << "_PIO")));
+
+        graph.add_bel(bel);
+    }
+
+    void add_dcc(RoutingGraph &graph, int x, int y, /* const std::string &name, */ int z) {
+        // TODO: All DCCs appear to be in center. Phantom DCCs line the columns
+        // for global routing with names of the form DCC_RxCy_{0,1}{T,B}. Hence
+        // commented-out name parameter.
+        // Diamond acknowledges these BELs, but attempting to use them crashes.
+        // See if they indeed do exist.
+        string name = string("DCC") + std::to_string(z);
+        RoutingBel bel;
+        bel.name = graph.ident(name);
+        bel.type = graph.ident("DCCA");
+        bel.loc.x = x;
+        bel.loc.y = y;
+        bel.z = z;
+
+        graph.add_bel_input(bel, graph.ident("CLKI"), x, y, graph.ident(fmt("G_CLKI" << name << "_DCC")));
+        graph.add_bel_input(bel, graph.ident("CE"), x, y, graph.ident(fmt("G_JCE" << name << "_DCC")));
+        graph.add_bel_output(bel, graph.ident("CLKO"), x, y, graph.ident(fmt("G_CLKO" << name << "_DCC")));
+
+        graph.add_bel(bel);
+    }
+
+    void add_dcm(RoutingGraph &graph, int x, int y, int n, int z) {
+        string name = string("DCM") + std::to_string(n);
+        RoutingBel bel;
+        bel.name = graph.ident(name);
+        bel.type = graph.ident("DCMA");
+        bel.loc.x = x;
+        bel.loc.y = y;
+        bel.z = z;
+
+        graph.add_bel_input(bel, graph.ident("CLK0"), x, y, graph.ident(fmt("G_CLK0_" << n << "_DCM")));
+        graph.add_bel_input(bel, graph.ident("CLK1"), x, y, graph.ident(fmt("G_CLK1_" << n << "_DCM")));
+        graph.add_bel_input(bel, graph.ident("SEL"), x, y, graph.ident(fmt("G_JSEL" << n << "_DCM")));
+        graph.add_bel_output(bel, graph.ident("DCMOUT"), x, y, graph.ident(fmt("G_DCMOUT" << n << "_DCM")));
+
+        graph.add_bel(bel);
+    }
+
+    void add_osch(RoutingGraph &graph, int x, int y, int z) {
+        string name = string("OSCH");
+        RoutingBel bel;
+        bel.name = graph.ident(name);
+        bel.type = graph.ident("OSCH");
+        bel.loc.x = x;
+        bel.loc.y = y;
+        bel.z = z;
+
+        graph.add_bel_input(bel, graph.ident("STDBY"), x, y, graph.ident(fmt("JSTDBY_OSC")));
+        graph.add_bel_output(bel, graph.ident("OSC"), x, y, graph.ident(fmt("G_JOSC_OSC")));
+        graph.add_bel_output(bel, graph.ident("SEDSTDBY"), x, y, graph.ident(fmt("SEDSTDBY_OSC")));
+
+        graph.add_bel(bel);
+    }
+}
 }
diff --git a/libtrellis/src/Bitstream.cpp b/libtrellis/src/Bitstream.cpp
index fb0aa38..1a35e94 100644
--- a/libtrellis/src/Bitstream.cpp
+++ b/libtrellis/src/Bitstream.cpp
@@ -39,8 +39,6 @@
     BitstreamOptions(const Chip &chip) {
       if (chip.info.family == "MachXO2") {
           // Write frames out in order 0 => max or reverse (max => 0).
-          // This apparently does NOT apply to compressed bitstreams, which
-          // uses reversed_frames = true unconditionally.
           reversed_frames = false;
           dummy_bytes_after_preamble = 2;
           crc_meta = 0xE0; // CRC check (0x80), once at end (0x40), dummy bits
@@ -600,9 +598,7 @@
                     bytes_per_frame += (7 - ((bytes_per_frame - 1) % 8));
                 unique_ptr<uint8_t[]> frame_bytes = make_unique<uint8_t[]>(bytes_per_frame);
                 for (size_t i = 0; i < frame_count; i++) {
-                    // Apparently when a bitstream is compressed, even on
-                    // MachXO2, frames are written in reverse order!
-                    const size_t idx = (chip->info.num_frames - 1) - i;
+                    size_t idx = ops.reversed_frames ? (chip->info.num_frames - 1) - i : i;
                     if (cmd == BitstreamCommand::LSC_PROG_INCR_CMP)
                         rd.get_compressed_bytes(frame_bytes.get(), bytes_per_frame, compression_dict.get());
                     else
@@ -798,6 +794,7 @@
         size_t bytes_per_frame = (chip.info.bits_per_frame + chip.info.pad_bits_after_frame +
                                   chip.info.pad_bits_before_frame) / 8U;
         for (size_t i = 0; i < frames; i++) {
+            const size_t idx = ops.reversed_frames? (chip.info.num_frames - 1) - i : i;
             frames_data.emplace_back();
             auto &frame_bytes = frames_data.back();
             frame_bytes.resize(bytes_per_frame);
@@ -805,7 +802,7 @@
                 size_t ofs = j + chip.info.pad_bits_after_frame;
                 assert(((bytes_per_frame - 1) - (ofs / 8)) < bytes_per_frame);
                 frame_bytes[(bytes_per_frame - 1) - (ofs / 8)] |=
-                        (chip.cram.bit((chip.info.num_frames - 1) - i, j) & 0x01) << (ofs % 8);
+                        (chip.cram.bit(idx, j) & 0x01) << (ofs % 8);
             }
         }
         // Then compress and write
diff --git a/libtrellis/src/Chip.cpp b/libtrellis/src/Chip.cpp
index e983fa4..339da74 100644
--- a/libtrellis/src/Chip.cpp
+++ b/libtrellis/src/Chip.cpp
@@ -32,7 +32,13 @@
         }
         tiles_at_location.at(row).at(col).push_back(make_pair(tile.name, tile.type));
     }
-    global_data = get_global_info(DeviceLocator{info.family, info.name});
+
+    if(info.family == "ECP5")
+        global_data_ecp5 = get_global_info_ecp5(DeviceLocator{info.family, info.name});
+    else if(info.family == "MachXO2")
+        global_data_machxo2 = get_global_info_machxo2(DeviceLocator{info.family, info.name});
+    else
+        throw runtime_error("Unknown chip family " + info.family);
 }
 
 shared_ptr<Tile> Chip::get_tile_by_name(string name)
@@ -109,6 +115,16 @@
 
 shared_ptr<RoutingGraph> Chip::get_routing_graph()
 {
+    if(info.family == "ECP5") {
+        return get_routing_graph_ecp5();
+    } else if(info.family == "MachXO2") {
+        return get_routing_graph_machxo2();
+    } else
+      throw runtime_error("Unknown chip family: " + info.family);
+}
+
+shared_ptr<RoutingGraph> Chip::get_routing_graph_ecp5()
+{
     shared_ptr<RoutingGraph> rg(new RoutingGraph(*this));
     //cout << "Building routing graph" << endl;
     for (auto tile_entry : tiles) {
@@ -121,142 +137,184 @@
         // SLICE Bels
         if (tile->info.type == "PLC2") {
             for (int z = 0; z < 4; z++)
-                Bels::add_lc(*rg, x, y, z);
+                Ecp5Bels::add_lc(*rg, x, y, z);
         }
         // PIO Bels
         if (tile->info.type.find("PICL0") != string::npos || tile->info.type.find("PICR0") != string::npos)
             for (int z = 0; z < 4; z++) {
-                Bels::add_pio(*rg, x, y, z);
-                Bels::add_iologic(*rg, x, y, z, false);
+                Ecp5Bels::add_pio(*rg, x, y, z);
+                Ecp5Bels::add_iologic(*rg, x, y, z, false);
             }
         if (tile->info.type.find("PIOT0") != string::npos || (tile->info.type.find("PICB0") != string::npos && tile->info.type != "SPICB0"))
             for (int z = 0; z < 2; z++) {
-                Bels::add_pio(*rg, x, y, z);
-                Bels::add_iologic(*rg, x, y, z, true);
+                Ecp5Bels::add_pio(*rg, x, y, z);
+                Ecp5Bels::add_iologic(*rg, x, y, z, true);
             }
         if (tile->info.type == "SPICB0") {
-            Bels::add_pio(*rg, x, y, 0);
-            Bels::add_iologic(*rg, x, y, 0, true);
+            Ecp5Bels::add_pio(*rg, x, y, 0);
+            Ecp5Bels::add_iologic(*rg, x, y, 0, true);
         }
         // DCC Bels
         if (tile->info.type == "LMID_0")
             for (int z = 0; z < 14; z++)
-                Bels::add_dcc(*rg, x, y, "L", std::to_string(z));
+                Ecp5Bels::add_dcc(*rg, x, y, "L", std::to_string(z));
         if (tile->info.type == "RMID_0")
             for (int z = 0; z < 14; z++)
-                Bels::add_dcc(*rg, x, y, "R", std::to_string(z));
+                Ecp5Bels::add_dcc(*rg, x, y, "R", std::to_string(z));
         if (tile->info.type == "TMID_0")
             for (int z = 0; z < 12; z++)
-                Bels::add_dcc(*rg, x, y, "T", std::to_string(z));
+                Ecp5Bels::add_dcc(*rg, x, y, "T", std::to_string(z));
         if (tile->info.type == "BMID_0V" || tile->info.type == "BMID_0H")
             for (int z = 0; z < 16; z++)
-                Bels::add_dcc(*rg, x, y, "B", std::to_string(z));
+                Ecp5Bels::add_dcc(*rg, x, y, "B", std::to_string(z));
         // RAM Bels
         if (tile->info.type == "MIB_EBR0" || tile->info.type == "EBR_CMUX_UR" || tile->info.type == "EBR_CMUX_LR"
             || tile->info.type == "EBR_CMUX_LR_25K")
-            Bels::add_bram(*rg, x, y, 0);
+            Ecp5Bels::add_bram(*rg, x, y, 0);
         if (tile->info.type == "MIB_EBR2")
-            Bels::add_bram(*rg, x, y, 1);
+            Ecp5Bels::add_bram(*rg, x, y, 1);
         if (tile->info.type == "MIB_EBR4")
-            Bels::add_bram(*rg, x, y, 2);
+            Ecp5Bels::add_bram(*rg, x, y, 2);
         if (tile->info.type == "MIB_EBR6")
-            Bels::add_bram(*rg, x, y, 3);
+            Ecp5Bels::add_bram(*rg, x, y, 3);
         // DSP Bels
         if (tile->info.type == "MIB_DSP0")
-            Bels::add_mult18(*rg, x, y, 0);
+            Ecp5Bels::add_mult18(*rg, x, y, 0);
         if (tile->info.type == "MIB_DSP1")
-            Bels::add_mult18(*rg, x, y, 1);
+            Ecp5Bels::add_mult18(*rg, x, y, 1);
         if (tile->info.type == "MIB_DSP4")
-            Bels::add_mult18(*rg, x, y, 4);
+            Ecp5Bels::add_mult18(*rg, x, y, 4);
         if (tile->info.type == "MIB_DSP5")
-            Bels::add_mult18(*rg, x, y, 5);
+            Ecp5Bels::add_mult18(*rg, x, y, 5);
         if (tile->info.type == "MIB_DSP3")
-            Bels::add_alu54b(*rg, x, y, 3);
+            Ecp5Bels::add_alu54b(*rg, x, y, 3);
         if (tile->info.type == "MIB_DSP7")
-            Bels::add_alu54b(*rg, x, y, 7);
+            Ecp5Bels::add_alu54b(*rg, x, y, 7);
         // PLL Bels
         if (tile->info.type == "PLL0_UL")
-            Bels::add_pll(*rg, "UL", x+1, y);
+            Ecp5Bels::add_pll(*rg, "UL", x+1, y);
         if (tile->info.type == "PLL0_LL")
-            Bels::add_pll(*rg, "LL", x, y-1);
+            Ecp5Bels::add_pll(*rg, "LL", x, y-1);
         if (tile->info.type == "PLL0_LR")
-            Bels::add_pll(*rg, "LR", x, y-1);
+            Ecp5Bels::add_pll(*rg, "LR", x, y-1);
         if (tile->info.type == "PLL0_UR")
-            Bels::add_pll(*rg, "UR", x-1, y);
+            Ecp5Bels::add_pll(*rg, "UR", x-1, y);
         // DCU and ancillary Bels
         if (tile->info.type == "DCU0") {
-            Bels::add_dcu(*rg, x, y);
-            Bels::add_extref(*rg, x, y);
+            Ecp5Bels::add_dcu(*rg, x, y);
+            Ecp5Bels::add_extref(*rg, x, y);
         }
         if (tile->info.type == "BMID_0H")
             for (int z = 0; z < 2; z++)
-                Bels::add_pcsclkdiv(*rg, x, y-1, z);
+                Ecp5Bels::add_pcsclkdiv(*rg, x, y-1, z);
         // Config/system Bels
         if (tile->info.type == "EFB0_PICB0") {
-            Bels::add_misc(*rg, "GSR", x, y-1);
-            Bels::add_misc(*rg, "JTAGG", x, y-1);
-            Bels::add_misc(*rg, "OSCG", x, y-1);
-            Bels::add_misc(*rg, "SEDGA", x, y-1);
+            Ecp5Bels::add_misc(*rg, "GSR", x, y-1);
+            Ecp5Bels::add_misc(*rg, "JTAGG", x, y-1);
+            Ecp5Bels::add_misc(*rg, "OSCG", x, y-1);
+            Ecp5Bels::add_misc(*rg, "SEDGA", x, y-1);
         }
         if (tile->info.type == "DTR")
-            Bels::add_misc(*rg, "DTR", x, y-1);
+            Ecp5Bels::add_misc(*rg, "DTR", x, y-1);
         if (tile->info.type == "EFB1_PICB1")
-            Bels::add_misc(*rg, "USRMCLK", x-5, y);
+            Ecp5Bels::add_misc(*rg, "USRMCLK", x-5, y);
         if (tile->info.type == "ECLK_L") {
-            Bels::add_ioclk_bel(*rg, "CLKDIVF", x-2, y, 0, 7);
-            Bels::add_ioclk_bel(*rg, "CLKDIVF", x-2, y, 1, 6);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y, 0, 7);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y, 1, 7);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y+1, 0, 6);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y+1, 1, 6);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y, 0, 7);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y, 1, 7);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y+1, 0, 6);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y+1, 1, 6);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y-1, 0);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y, 0);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y+1, 0);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y+2, 0);
-            Bels::add_ioclk_bel(*rg, "ECLKBRIDGECS", x-2, y, 1);
-            Bels::add_ioclk_bel(*rg, "BRGECLKSYNC", x-2, y, 1);
+            Ecp5Bels::add_ioclk_bel(*rg, "CLKDIVF", x-2, y, 0, 7);
+            Ecp5Bels::add_ioclk_bel(*rg, "CLKDIVF", x-2, y, 1, 6);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y, 0, 7);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y, 1, 7);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y+1, 0, 6);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x-2, y+1, 1, 6);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y, 0, 7);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y, 1, 7);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y+1, 0, 6);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x-2, y+1, 1, 6);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y-1, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y+1, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x-2, y+2, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKBRIDGECS", x-2, y, 1);
+            Ecp5Bels::add_ioclk_bel(*rg, "BRGECLKSYNC", x-2, y, 1);
         }
         if (tile->info.type == "ECLK_R") {
-            Bels::add_ioclk_bel(*rg, "CLKDIVF", x+2, y, 0);
-            Bels::add_ioclk_bel(*rg, "CLKDIVF", x+2, y, 1);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y, 0, 2);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y, 1, 2);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y+1, 0, 3);
-            Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y+1, 1, 3);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y, 0, 2);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y, 1, 2);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y+1, 0, 3);
-            Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y+1, 1, 3);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y-1, 0);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y, 0);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y+1, 0);
-            Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y+2, 0);
-            Bels::add_ioclk_bel(*rg, "ECLKBRIDGECS", x+2, y, 0);
-            Bels::add_ioclk_bel(*rg, "BRGECLKSYNC", x+2, y, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "CLKDIVF", x+2, y, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "CLKDIVF", x+2, y, 1);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y, 0, 2);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y, 1, 2);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y+1, 0, 3);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKSYNCB", x+2, y+1, 1, 3);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y, 0, 2);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y, 1, 2);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y+1, 0, 3);
+            Ecp5Bels::add_ioclk_bel(*rg, "TRELLIS_ECLKBUF", x+2, y+1, 1, 3);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y-1, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y+1, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DLLDELD", x+2, y+2, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "ECLKBRIDGECS", x+2, y, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "BRGECLKSYNC", x+2, y, 0);
         }
         if (tile->info.type == "DDRDLL_UL")
-            Bels::add_ioclk_bel(*rg, "DDRDLL", x-2, y-10, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DDRDLL", x-2, y-10, 0);
         if (tile->info.type == "DDRDLL_ULA")
-            Bels::add_ioclk_bel(*rg, "DDRDLL", x-2, y-13, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DDRDLL", x-2, y-13, 0);
         if (tile->info.type == "DDRDLL_UR")
-            Bels::add_ioclk_bel(*rg, "DDRDLL", x+2, y-10, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DDRDLL", x+2, y-10, 0);
         if (tile->info.type == "DDRDLL_URA")
-            Bels::add_ioclk_bel(*rg, "DDRDLL", x+2, y-13, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DDRDLL", x+2, y-13, 0);
         if (tile->info.type == "DDRDLL_LL")
-            Bels::add_ioclk_bel(*rg, "DDRDLL", x-2, y+13, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DDRDLL", x-2, y+13, 0);
         if (tile->info.type == "DDRDLL_LR")
-            Bels::add_ioclk_bel(*rg, "DDRDLL", x+2, y+13, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DDRDLL", x+2, y+13, 0);
         if (tile->info.type == "PICL0_DQS2" || tile->info.type == "PICR0_DQS2")
-            Bels::add_ioclk_bel(*rg, "DQSBUFM", x, y, 0);
+            Ecp5Bels::add_ioclk_bel(*rg, "DQSBUFM", x, y, 0);
 
     }
     return rg;
 }
 
+shared_ptr<RoutingGraph> Chip::get_routing_graph_machxo2()
+{
+    shared_ptr<RoutingGraph> rg(new RoutingGraph(*this));
+
+    for (auto tile_entry : tiles) {
+        shared_ptr<Tile> tile = tile_entry.second;
+        //cout << "    Tile " << tile->info.name << endl;
+        shared_ptr<TileBitDatabase> bitdb = get_tile_bitdata(TileLocator{info.family, info.name, tile->info.type});
+        bitdb->add_routing(tile->info, *rg);
+        int x, y;
+        tie(y, x) = tile->info.get_row_col();
+
+        // SLICE Bels
+        if (tile->info.type == "PLC")
+            for (int z = 0; z < 4; z++)
+                MachXO2Bels::add_lc(*rg, x, y, z);
+
+        // PIO Bels
+        if (tile->info.type.find("PIC_L0") != string::npos || tile->info.type.find("PIC_LS0") != string::npos ||
+            tile->info.type.find("PIC_T") != string::npos ||
+            tile->info.type.find("PIC_R0") != string::npos || tile->info.type.find("PIC_RS0") != string::npos ||
+            tile->info.type.find("PIC_B") != string::npos)
+            for (int z = 0; z < 4; z++)
+                MachXO2Bels::add_pio(*rg, x, y, z);
+
+        // DCC/DCM Bels
+        if (tile->info.type.find("CENTER_EBR_CIB") != string::npos) {
+          for (int z = 0; z < 8; z++)
+              MachXO2Bels::add_dcc(*rg, x, y, z);
+          for (int z = 6; z < 8; z++)
+              // Start at z = 8, but names start at 6.
+              MachXO2Bels::add_dcm(*rg, x, y, z, z + 2);
+        }
+
+        if (tile->info.type.find("CIB_CFG0") != string::npos) {
+            MachXO2Bels::add_osch(*rg, x, y, 0);
+        }
+    }
+
+    return rg;
+}
+
 // Global network funcs
 
 bool GlobalRegion::matches(int row, int col) const {
@@ -273,7 +331,7 @@
     return (col >= rx0 && col <= rx1);
 }
 
-string GlobalsInfo::get_quadrant(int row, int col) const {
+string Ecp5GlobalsInfo::get_quadrant(int row, int col) const {
     for (const auto &quad : quadrants) {
         if (quad.matches(row, col))
             return quad.name;
@@ -281,7 +339,7 @@
     throw runtime_error(fmt("R" << row << "C" << col << " matches no globals quadrant"));
 }
 
-TapDriver GlobalsInfo::get_tap_driver(int row, int col) const {
+TapDriver Ecp5GlobalsInfo::get_tap_driver(int row, int col) const {
     for (const auto &seg : tapsegs) {
         if (seg.matches_left(row, col)) {
             TapDriver td;
@@ -299,7 +357,7 @@
     throw runtime_error(fmt("R" << row << "C" << col << " matches no global TAP_DRIVE segment"));
 }
 
-pair<int, int> GlobalsInfo::get_spine_driver(std::string quadrant, int col) {
+pair<int, int> Ecp5GlobalsInfo::get_spine_driver(std::string quadrant, int col) {
     for (const auto &seg : spinesegs) {
         if (seg.quadrant == quadrant && seg.tap_col == col) {
             return make_pair(seg.spine_row, seg.spine_col);
diff --git a/libtrellis/src/Database.cpp b/libtrellis/src/Database.cpp
index 3344f44..2b65019 100644
--- a/libtrellis/src/Database.cpp
+++ b/libtrellis/src/Database.cpp
@@ -85,11 +85,11 @@
     return ci;
 }
 
-GlobalsInfo get_global_info(const DeviceLocator &part) {
+Ecp5GlobalsInfo get_global_info_ecp5(const DeviceLocator &part) {
     string glbdata_path = db_root + "/" + part.family + "/" + part.device + "/globals.json";
     pt::ptree glb_parsed;
     pt::read_json(glbdata_path, glb_parsed);
-    GlobalsInfo glbs;
+    Ecp5GlobalsInfo glbs;
     for (const pt::ptree::value_type &quad : glb_parsed.get_child("quadrants")) {
         GlobalRegion rg;
         rg.name = quad.first;
@@ -120,6 +120,83 @@
     return glbs;
 }
 
+MachXO2GlobalsInfo get_global_info_machxo2(const DeviceLocator &part) {
+    string glbdata_path = db_root + "/" + part.family + "/" + part.device + "/globals.json";
+    pt::ptree glb_parsed;
+    pt::read_json(glbdata_path, glb_parsed);
+    MachXO2GlobalsInfo glbs;
+
+    for (const pt::ptree::value_type &lr : glb_parsed.get_child("lr-conns")) {
+        LeftRightConn lrc;
+        lrc.name = lr.first;
+        lrc.row = lr.second.get<int>("row");
+
+        auto rs = lr.second.get_child("row-span").begin();
+        lrc.row_span.first = rs->second.get_value<int>();
+        rs++;
+        lrc.row_span.second = rs->second.get_value<int>();
+
+        glbs.lr_conns.push_back(lrc);
+    }
+
+    // The column is a string the in JSON file so the JSON is easier to read
+    // (because JSON doesn't have comments). Columns need to be parsed in
+    // order.
+    int col_no = 0;
+    for (const pt::ptree::value_type &ud : glb_parsed.get_child("ud-conns")) {
+        std::vector<int> udc;
+
+        assert(col_no == std::stoi(ud.first));
+
+        for(const pt::ptree::value_type &glb_no : ud.second.get_child(""))
+        {
+            udc.push_back(glb_no.second.get<int>(""));
+        }
+
+        glbs.ud_conns.push_back(udc);
+        col_no++;
+    }
+
+    col_no = 0;
+    for (const pt::ptree::value_type &spans : glb_parsed.get_child("branch-spans")) {
+        std::vector<pair<int, int> > sp;
+
+        assert(col_no == std::stoi(spans.first));
+
+        for(auto global_no : glbs.ud_conns[col_no])
+        {
+          std::pair<int, int> sr;
+
+          string global_key = std::to_string(global_no);
+          auto curr_sr = spans.second.get_child(global_key).begin();
+
+          sr.first = curr_sr->second.get_value<int>();
+          curr_sr++;
+          sr.second = curr_sr->second.get_value<int>();
+
+          sp.push_back(sr);
+        }
+
+        glbs.branch_spans.push_back(sp);
+        col_no++;
+    }
+
+    for (const pt::ptree::value_type &dccs : glb_parsed.get_child("missing-dccs")) {
+        MissingDccs missing;
+
+        missing.row = std::stoi(dccs.first);
+
+        for(const pt::ptree::value_type &dcc_no : dccs.second.get_child(""))
+        {
+            missing.missing.push_back(dcc_no.second.get<int>(""));
+        }
+
+        glbs.missing_dccs.push_back(missing);
+    }
+
+    return glbs;
+}
+
 vector<TileInfo> get_device_tilegrid(const DeviceLocator &part) {
     vector <TileInfo> tilesInfo;
     assert(db_root != "");
diff --git a/libtrellis/src/OptChipdb.cpp b/libtrellis/src/OptChipdb.cpp
new file mode 100644
index 0000000..d2904d3
--- /dev/null
+++ b/libtrellis/src/OptChipdb.cpp
@@ -0,0 +1,105 @@
+#include "DedupChipdb.hpp"
+#include "Chip.hpp"
+
+namespace Trellis {
+namespace DDChipDb {
+
+OptimizedChipdb::OptimizedChipdb()
+{
+
+}
+
+OptimizedChipdb::OptimizedChipdb(const IdStore &base) : IdStore(base)
+{}
+
+shared_ptr<OptimizedChipdb> make_optimized_chipdb(Chip &chip)
+{
+    shared_ptr<RoutingGraph> graph = chip.get_routing_graph();
+    for (auto &loc : graph->tiles) {
+        const auto &td = loc.second;
+        // Index bels, wires and arcs
+        int bel_id = 0, wire_id = 0, arc_id = 0;
+        for (auto &bel : td.bels) {
+            RoutingId rid;
+            rid.loc = loc.first;
+            rid.id = bel.first;
+            bel.second.cdb_id = bel_id++;
+        }
+        for (auto &wire : td.wires) {
+            RoutingId rid;
+            rid.loc = loc.first;
+            rid.id = wire.first;
+            wire.second.cdb_id  = wire_id++;
+        }
+        for (auto &arc : td.arcs) {
+            RoutingId rid;
+            rid.loc = loc.first;
+            rid.id = arc.first;
+            arc.second.cdb_id = arc_id++;
+        }
+    }
+    shared_ptr<OptimizedChipdb> cdb = make_shared<OptimizedChipdb>(IdStore(*graph));
+    for (const auto &loc : graph->tiles) {
+        int x = loc.first.x, y = loc.first.y;
+        LocationData ld;
+        const auto &td = loc.second;
+        for (const auto &bel : td.bels) {
+            const RoutingBel &rb = bel.second;
+            BelData bd;
+            bd.name = rb.name;
+            bd.type = rb.type;
+            bd.z = rb.z;
+            for (const auto &wire : rb.pins) {
+                BelWire bw;
+                bw.pin = wire.first;
+                bw.wire = OptId{wire.second.first.loc, graph->tiles.at(wire.second.first.loc).wires.at(wire.second.first.id).cdb_id};
+                bw.dir = wire.second.second;
+                bd.wires.push_back(bw);
+            }
+            ld.bels.push_back(bd);
+        }
+
+        for (const auto &arc : td.arcs) {
+            const RoutingArc &ra = arc.second;
+            OptArcData ad;
+            ad.tiletype = ra.tiletype;
+            ad.cls = ra.configurable ? ARC_STANDARD : ARC_FIXED;
+            ad.delay = 1;
+            ad.sinkWire = OptId{ra.sink.loc, graph->tiles.at(ra.sink.loc).wires.at(ra.sink.id).cdb_id};
+            ad.srcWire = OptId{ra.source.loc, graph->tiles.at(ra.source.loc).wires.at(ra.source.id).cdb_id};
+            ld.arcs.push_back(ad);
+        }
+
+        for (const auto &wire : td.wires) {
+            const RoutingWire &rw = wire.second;
+            WireData wd;
+            wd.name = rw.id;
+            for (const auto &dh : rw.downhill)
+                wd.arcsDownhill.insert(OptId{dh.loc, graph->tiles.at(dh.loc).arcs.at(dh.id).cdb_id});
+            for (const auto &uh : rw.uphill)
+                wd.arcsUphill.insert(OptId{uh.loc, graph->tiles.at(uh.loc).arcs.at(uh.id).cdb_id});
+            for (const auto &bdh : rw.belsDownhill) {
+                BelPort bp;
+                bp.pin = bdh.second;
+                bp.bel = OptId{bdh.first.loc, graph->tiles.at(bdh.first.loc).bels.at(bdh.first.id).cdb_id};
+                wd.belPins.push_back(bp);
+            }
+            assert(rw.belsUphill.size() <= 1);
+            if (rw.belsUphill.size() == 1) {
+                const auto &buh = rw.belsUphill[0];
+                BelPort uh;
+                uh.bel = OptId{buh.first.loc, graph->tiles.at(buh.first.loc).bels.at(buh.first.id).cdb_id};
+                uh.pin = buh.second;
+                wd.belPins.push_back(uh);
+            }
+            ld.wires.push_back(wd);
+        }
+
+        cdb->tiles[loc.first] = ld;
+    }
+
+    return cdb;
+}
+
+}
+}
diff --git a/libtrellis/src/PyTrellis.cpp b/libtrellis/src/PyTrellis.cpp
index ac07c35..3ea2f7d 100644
--- a/libtrellis/src/PyTrellis.cpp
+++ b/libtrellis/src/PyTrellis.cpp
@@ -126,12 +126,45 @@
     class_<vector<TapSegment>>("TapSegmentVector")
             .def(vector_indexing_suite<vector<TapSegment>>());
 
-    class_<GlobalsInfo>("GlobalsInfo")
-            .def_readwrite("quadrants", &GlobalsInfo::quadrants)
-            .def_readwrite("tapsegs", &GlobalsInfo::tapsegs)
-            .def("get_quadrant", &GlobalsInfo::get_quadrant)
-            .def("get_tap_driver", &GlobalsInfo::get_tap_driver)
-            .def("get_spine_driver", &GlobalsInfo::get_spine_driver);
+    class_<Ecp5GlobalsInfo>("Ecp5GlobalsInfo")
+            .def_readwrite("quadrants", &Ecp5GlobalsInfo::quadrants)
+            .def_readwrite("tapsegs", &Ecp5GlobalsInfo::tapsegs)
+            .def("get_quadrant", &Ecp5GlobalsInfo::get_quadrant)
+            .def("get_tap_driver", &Ecp5GlobalsInfo::get_tap_driver)
+            .def("get_spine_driver", &Ecp5GlobalsInfo::get_spine_driver);
+
+    class_<LeftRightConn>("LeftRightConn")
+            .def_readwrite("name", &LeftRightConn::name)
+            .def_readwrite("row", &LeftRightConn::row)
+            .def_readwrite("row_span", &LeftRightConn::row_span);
+
+    class_<MissingDccs>("MissingDccs")
+            .def_readwrite("row", &MissingDccs::row)
+            .def_readwrite("missing", &MissingDccs::missing);
+
+    class_<vector<LeftRightConn>>("LeftRightConnVector")
+            .def(vector_indexing_suite<vector<LeftRightConn>>());
+
+    class_<vector<vector<int>>>("UpDownConnVector")
+            .def(vector_indexing_suite<vector<vector<int>>>());
+
+    class_<vector<vector<std::pair<int, int>>>>("BranchSpanVector")
+            .def(vector_indexing_suite<vector<vector<std::pair<int, int>>>>());
+
+    class_<vector<MissingDccs>>("MissingDccsVector")
+            .def(vector_indexing_suite<vector<MissingDccs>>());
+
+    class_<vector<int>>("IntVector")
+            .def(vector_indexing_suite<vector<int>>());
+
+    class_<vector<std::pair<int, int>>>("IntPairVector")
+            .def(vector_indexing_suite<vector<std::pair<int, int>>>());
+
+    class_<MachXO2GlobalsInfo>("MachXO2GlobalsInfo")
+            .def_readwrite("lr_conns", &MachXO2GlobalsInfo::lr_conns)
+            .def_readwrite("ud_conns", &MachXO2GlobalsInfo::ud_conns)
+            .def_readwrite("branch_spans", &MachXO2GlobalsInfo::branch_spans)
+            .def_readwrite("missing_dccs", &MachXO2GlobalsInfo::missing_dccs);
 
     class_<Chip>("Chip", init<string>())
             .def(init<uint32_t>())
@@ -148,7 +181,10 @@
             .def_readwrite("tiles", &Chip::tiles)
             .def_readwrite("usercode", &Chip::usercode)
             .def_readwrite("metadata", &Chip::metadata)
-            .def_readwrite("global_data", &Chip::global_data)
+            // Alias for backwards compatibility.
+            .def_readwrite("global_data", &Chip::global_data_ecp5)
+            .def_readwrite("global_data_ecp5", &Chip::global_data_ecp5)
+            .def_readwrite("global_data_machxo2", &Chip::global_data_machxo2)
             .def(self - self);
 
     class_<ChipDelta>("ChipDelta")
@@ -443,6 +479,7 @@
 
     class_<RoutingGraph, shared_ptr<RoutingGraph>>("RoutingGraph", no_init)
             .def_readonly("chip_name", &RoutingGraph::chip_name)
+            .def_readonly("chip_family", &RoutingGraph::chip_family)
             .def_readonly("max_row", &RoutingGraph::max_row)
             .def_readonly("max_col", &RoutingGraph::max_col)
             .def("ident", &RoutingGraph::ident)
@@ -519,6 +556,9 @@
     class_<map<checksum_t, LocationData>>("LocationTypesMap")
             .def(map_indexing_suite<map<checksum_t, LocationData>>());
 
+    class_<map<Location, LocationData>>("LocationMapDirect")
+            .def(map_indexing_suite<map<Location, LocationData>>());
+
     class_<checksum_t>("checksum_t")
             .def_readwrite("first", &checksum_t::first)
             .def_readwrite("second", &checksum_t::second)
@@ -532,6 +572,14 @@
             .def("to_str", &DedupChipdb::to_str);
 
     def("make_dedup_chipdb", make_dedup_chipdb);
+
+    class_<OptimizedChipdb, shared_ptr<OptimizedChipdb>>("OptimizedChipdb")
+            .def_readwrite("tiles", &OptimizedChipdb::tiles)
+            .def("ident", &OptimizedChipdb::ident)
+            .def("to_str", &OptimizedChipdb::to_str);
+
+    def("make_optimized_chipdb", make_optimized_chipdb);
+
 }
 
 #endif
diff --git a/libtrellis/src/RoutingGraph.cpp b/libtrellis/src/RoutingGraph.cpp
index 05a7222..84eb625 100644
--- a/libtrellis/src/RoutingGraph.cpp
+++ b/libtrellis/src/RoutingGraph.cpp
@@ -3,12 +3,15 @@
 #include "Tile.hpp"
 #include <regex>
 #include <iostream>
+#include <algorithm>
 
 namespace Trellis {
 
+// This is ignored for the MachXO2 family- globals are handled during routing
+// graph creation.
 const Location GlobalLoc(-2, -2);
 
-RoutingGraph::RoutingGraph(const Chip &c) : chip_name(c.info.name), max_row(c.get_max_row()), max_col(c.get_max_col())
+RoutingGraph::RoutingGraph(const Chip &c) : chip_name(c.info.name), chip_family(c.info.family), max_row(c.get_max_row()), max_col(c.get_max_col())
 {
     tiles[GlobalLoc].loc = GlobalLoc;
     for (int y = 0; y <= max_row; y++) {
@@ -23,8 +26,14 @@
         chip_prefix = "45K_";
     else if (chip_name.find("85F") != string::npos)
         chip_prefix = "85K_";
+    else if (chip_name.find("1200HC") != string::npos)
+        // FIXME: Prefix to distinguish XO, XO2, and XO3?
+        chip_prefix = "1200_";
     else
         assert(false);
+
+    if(c.info.family == "MachXO2")
+        global_data_machxo2 = get_global_info_machxo2(DeviceLocator{c.info.family, c.info.name});
 }
 
 ident_t IdStore::ident(const std::string &str) const
@@ -53,6 +62,16 @@
 
 RoutingId RoutingGraph::globalise_net(int row, int col, const std::string &db_name)
 {
+    if(chip_family == "ECP5") {
+        return globalise_net_ecp5(row, col, db_name);
+    } else if(chip_family == "MachXO2") {
+        return globalise_net_machxo2(row, col, db_name);
+    } else
+        throw runtime_error("Unknown chip family: " + chip_family);
+}
+
+RoutingId RoutingGraph::globalise_net_ecp5(int row, int col, const std::string &db_name)
+{
     static const std::regex e(R"(^([NS]\d+)?([EW]\d+)?_(.*))", std::regex::optimize);
     std::string stripped_name = db_name;
     if (db_name.find("25K_") == 0 || db_name.find("45K_") == 0 || db_name.find("85K_") == 0) {
@@ -111,6 +130,95 @@
     }
 }
 
+RoutingId RoutingGraph::globalise_net_machxo2(int row, int col, const std::string &db_name)
+{
+  static const std::regex e(R"(^([NS]\d+)?([EW]\d+)?_(.*))", std::regex::optimize);
+  std::string stripped_name = db_name;
+
+  if (db_name.find("256_") == 0 || db_name.find("640_") == 0) {
+      if (db_name.substr(0, 4) == chip_prefix) {
+          stripped_name = db_name.substr(4);
+      } else {
+          return RoutingId();
+      }
+  }
+
+  if (db_name.find("1200_") == 0 || db_name.find("2000_") == 0 ||
+      db_name.find("4000_") == 0 || db_name.find("7000_") == 0) {
+      if (db_name.substr(0, 5) == chip_prefix) {
+          stripped_name = db_name.substr(5);
+      } else {
+          return RoutingId();
+      }
+  }
+
+  if (stripped_name.find("G_") == 0 || stripped_name.find("L_") == 0 || stripped_name.find("R_") == 0 ||
+      stripped_name.find("U_") == 0 || stripped_name.find("D_") == 0 || stripped_name.find("BRANCH_") == 0) {
+      // Global prefix detected, use the prefix and row/col to map "logical"
+      // globals on a tile basis to physical globals which are shared between
+      // tiles.
+      return find_machxo2_global_position(row, col, stripped_name);
+  } else {
+      RoutingId id;
+      id.loc.x = int16_t(col);
+      id.loc.y = int16_t(row);
+      // Local net, process prefix
+      smatch m;
+      if (regex_match(stripped_name, m, e)) {
+          for (int i = 1; i < int(m.size()) - 1; i++) {
+              string g = m.str(i);
+              if (g.empty()) continue;
+              if (g[0] == 'N') id.loc.y -= std::stoi(g.substr(1));
+              else if (g[0] == 'S') id.loc.y += std::stoi(g.substr(1));
+              else if (g[0] == 'W') {
+                  id.loc.x -= std::stoi(g.substr(1));
+
+                  if(id.loc.x < 0) {
+                      // Special case: PIO wires on left side have a relative
+                      // position placing them outside the chip thanks to MachXO2's
+                      // wonderful 1-based column numbering, and lack of dedicated
+                      // PIO tiles on the left and right.
+                      // Top and bottom unaffected due to dedicated PIO tiles.
+                      bool pio_wire = (db_name.find("PADD") != string::npos ||
+                          db_name.find("IOLDO") != string::npos ||
+                          db_name.find("IOLTO") != string::npos ||
+                          db_name.find("JINCK") != string::npos);
+
+                      if((id.loc.x == -1) && pio_wire)
+                          id.loc.x = 0;
+                  }
+              }
+              else if (g[0] == 'E') {
+                  id.loc.x += std::stoi(g.substr(1));
+
+                  if(id.loc.x > max_col) {
+                      bool pio_wire = (db_name.find("PADD") != string::npos ||
+                        db_name.find("IOLDO") != string::npos ||
+                        db_name.find("IOLTO") != string::npos||
+                        db_name.find("JINCK") != string::npos);
+
+                      // Same deal as left side, except the position exceeds
+                      // the maximum row.
+                      // TODO: Should this become part of general edge-handling
+                      // code, rather than a special case in the generic relative-
+                      // position logic?
+                      if((id.loc.x == max_col + 1) && pio_wire)
+                          id.loc.x = max_col;
+                  }
+              }
+              else
+                  assert(false);
+          }
+          id.id = ident(m.str(m.size() - 1));
+      } else {
+          id.id = ident(stripped_name);
+      }
+      if (id.loc.x < 0 || id.loc.x > max_col || id.loc.y < 0 || id.loc.y > max_row)
+          return RoutingId(); // TODO: handle edge nets properly
+      return id;
+  }
+}
+
 void RoutingGraph::add_arc(Location loc, const RoutingArc &arc)
 {
     RoutingId arcId;
@@ -162,4 +270,238 @@
     tiles[wireId.loc].wires[wireId.id].belsUphill.push_back(make_pair(belId, pin));
 }
 
+RoutingId RoutingGraph::find_machxo2_global_position(int row, int col, const std::string &db_name) {
+    // Globals are given their nominal position, even if they span multiple
+    // tiles, by the following rules (determined by a combination of regexes
+    // on db_name and row/col):
+    smatch m;
+    pair<int, int> center = center_map[make_pair(max_row, max_col)];
+    RoutingId curr_global;
+
+    GlobalType strategy = get_global_type_from_name(db_name, m);
+
+    // All globals in the center tile get a nominal position of the center
+    // tile. We have to use regexes because a number of these connections
+    // in the center mux have config bits spread across multiple tiles
+    // (although few nets actually have routing bits which cross tiles).
+    //
+    // This handles L/R_HPSX as well. DCCs are handled a bit differently
+    // until we can determine they only truly exist in center tile (the row,
+    // col, and db_name will still be enough to distinguish them).
+    if(strategy == GlobalType::CENTER) {
+        // Some arcs, like those which connect to VPRXCLKI0 in the 1200HC part
+        // may appear more than once. We assume that open tools like nextpnr
+        // are able to tolerate the same physical arc appearing twice in the
+        // routing graph without any problems. This should also make bitstream
+        // generation easier if the open tools make sure to set an arc as used
+        // in each tile where it exists.
+        curr_global.id = ident(db_name);
+        curr_global.loc.x = center.second;
+        curr_global.loc.y = center.first;
+        return curr_global;
+
+    // If we found a global emanating from the CENTER MUX, return a L_/R_
+    // global net in the center tile based upon the current tile position
+    // (specifically column).
+    } else if(strategy == GlobalType::LEFT_RIGHT) {
+        assert(row == center.first);
+        // Prefixes only required in the center tile.
+        assert(db_name[0] == 'G');
+
+        std::string db_copy = db_name;
+
+        // Center column tiles connect to the left side of the center mux.
+        if(col <= center.second)
+            db_copy[0] = 'L';
+        else
+            db_copy[0] = 'R';
+
+        curr_global.id = ident(db_copy);
+        curr_global.loc.x = center.second;
+        curr_global.loc.y = center.first;
+        return curr_global;
+
+    // U/D wires get the nominal position of center row, current column.
+    // Both U_/D_ and G_ prefixes are handled here.
+    } else if(strategy == GlobalType::UP_DOWN) {
+        std::string db_copy = db_name;
+        std::vector<int> & ud_conns_in_col = global_data_machxo2.ud_conns[col];
+        auto conn_begin = ud_conns_in_col.begin();
+        auto conn_end = ud_conns_in_col.end();
+        int conn_no = std::stoi(m.str(1));
+
+        // First check whether the requested global is in the current column.
+        // If not, no point in continuing.
+        if(std::find(conn_begin, conn_end, conn_no) == conn_end)
+            return RoutingId();
+
+        // Special case the center row, which will have both U/D wires.
+        if(row == center.first) {
+            assert((db_name[0] == 'U') || (db_name[0] == 'D'));
+        } else {
+            // Otherwise choose an U_/D_ wire at nominal position based on
+            // the current tile's row.
+            // Prefixes only required in the center row.
+            assert(db_name[0] == 'G');
+
+            // Center column tiles are considered above the center mux,
+            // despite sharing the same tile.
+            if(row <= center.first)
+                db_copy[0] = 'U';
+            else
+                db_copy[0] = 'D';
+        }
+
+        curr_global.id = ident(db_copy);
+        curr_global.loc.x = col;
+        curr_global.loc.y = center.first;
+        return curr_global;
+
+    // BRANCH wires get nominal position of the row/col where they connect
+    // to U_/D_ routing. We need the global_data_machxo2 struct to figure
+    // out this information.
+    } else if(strategy == GlobalType::BRANCH) {
+        std::vector<int> candidate_cols;
+
+        if(col > 1)
+            candidate_cols.push_back(col - 2);
+        if(col > 0)
+            candidate_cols.push_back(col - 1);
+        candidate_cols.push_back(col);
+        if(col < max_col)
+            candidate_cols.push_back(col + 1);
+
+        for(auto curr_col : candidate_cols) {
+            std::vector<int> & ud_conns_in_col = global_data_machxo2.ud_conns[curr_col];
+            auto conn_begin = ud_conns_in_col.begin();
+            auto conn_end = ud_conns_in_col.end();
+            int conn_no = std::stoi(m.str(1));
+
+            // First check whether the requested global is in the current column.
+            // If not, no point in continuing.
+            if(std::find(conn_begin, conn_end, conn_no) != conn_end) {
+                curr_global.id = ident(db_name);
+                curr_global.loc.x = curr_col;
+                curr_global.loc.y = row;
+                break;
+            }
+        }
+
+        // One of the candidate columns should have had the correct U/D
+        // connection to route to.
+        assert(curr_global != RoutingId());
+        return curr_global;
+
+    // For OSCH, and DCCs assign nominal position of current requested tile.
+    // DCM only exist in center tile but have their routing spread out
+    // across tiles.
+    } else if(strategy == GlobalType::OTHER) {
+        curr_global.id = ident(db_name);
+        curr_global.loc.x = col;
+        curr_global.loc.y = row;
+        return curr_global;
+    } else {
+        // TODO: Not fuzzed and/or handled yet!
+        return RoutingId();
+    }
+}
+
+RoutingGraph::GlobalType RoutingGraph::get_global_type_from_name(const std::string &db_name, smatch &match) {
+    // A RoutingId uniquely describes a net on the chip- using a string
+    // identifier (id- converted to int), and a nominal position (loc).
+    // Two RoutingIds with the same id and loc represent the same net, so
+    // we can use heuristics to connect globals to the rest of the routing
+    // graph properly, given the current tile position and the global's
+    // identifier.
+
+    // First copy regexes from utils.nets.machxo2, adjusting as necessary for
+    // prefixes and regex syntax. Commented-out ones are not ready yet:
+    // Globals
+    static const std::regex global_entry(R"(G_VPRX(\d){2}00)", std::regex::optimize);
+    static const std::regex global_left_right(R"([LR]_HPSX(\d){2}00)", std::regex::optimize);
+    static const std::regex global_left_right_g(R"(G_HPSX(\d){2}00)", std::regex::optimize);
+    static const std::regex global_up_down(R"([UD]_VPTX(\d){2}00)", std::regex::optimize);
+    static const std::regex global_up_down_g(R"(G_VPTX(\d){2}00)", std::regex::optimize);
+    static const std::regex global_branch(R"(BRANCH_HPBX(\d){2}00)", std::regex::optimize);
+
+    // High Fanout Secondary Nets
+    // static const std::regex hfsn_entry(R"(G_VSRX(\d){2}00)", std::regex::optimize);
+    // static const std::regex hfsn_left_right(R"(G_HSSX(\d){2}00)", std::regex::optimize);
+    // L2Rs control bidirectional portion of HFSNs!!
+    // static const std::regex hfsn_l2r(R"(G_HSSX(\d){2}00_[RL]2[LR])", std::regex::optimize);
+    // static const std::regex hfsn_up_down(R"(G_VSTX(\d){2}00)", std::regex::optimize);
+    // HSBX(\d){2}00 are fixed connections to HSBX(\d){2}01s.
+    // static const std::regex hfsn_branch(R"(G_HSBX(\d){2}0[01])", std::regex::optimize);
+
+    // Center Mux
+    // Outputs- entry to DCCs connected to globals (VPRXI -> DCC -> VPRX) *
+    static const std::regex center_mux_glb_out(R"(G_VPRXCLKI\d+)", std::regex::optimize);
+    // Outputs- connected to ECLKBRIDGEs *
+    // static const std::regex center_mux_ebrg_out(R"(G_EBRG(\d){1}CLK(\d){1})", std::regex::optimize);
+
+    // Inputs- CIB routing to HFSNs
+    // static const std::regex cib_out_to_hfsn(R"(G_JSNETCIB([TBRL]|MID)(\d){1})", std::regex::optimize);
+    // Inputs- CIB routing to globals
+    static const std::regex cib_out_to_glb(R"(G_J?PCLKCIB(L[TBRL]Q|MID|VIQ[TBRL])(\d){1})", std::regex::optimize);
+    // Inputs- CIB routing to ECLKBRIDGEs
+    // static const std::regex cib_out_to_eclk(R"(G_J?ECLKCIB[TBRL](\d){1})", std::regex::optimize);
+
+    // Inputs- Edge clocks dividers
+    // static const std::regex eclk_out(R"(G_J?[TBRL]CDIV(X(\d){1}|(\d){2}))", std::regex::optimize);
+    // Inputs- PLL
+    // static const std::regex pll_out(R"(G_J?[LR]PLLCLK\d+)", std::regex::optimize);
+    // Inputs- Clock pads
+    // static const std::regex clock_pin(R"(G_J?PCLK[TBLR]\d+)", std::regex::optimize);
+
+    // Part of center-mux but can also be found elsewhere
+    // DCCs connected to globals *
+    static const std::regex dcc_sig(R"(G_J?(CLK[IO]|CE)(\d){1}[TB]?_DCC)", std::regex::optimize);
+
+    // DCMs- connected to DCCs on globals 6 and 7 *
+    static const std::regex dcm_sig(R"(G_J?(CLK(\d){1}_|SEL|DCMOUT)(\d){1}_DCM)", std::regex::optimize);
+
+    // ECLKBRIDGEs- TODO
+    // static const std::regex eclkbridge_sig(R"(G_J?(CLK(\d){1}_|SEL|ECSOUT)(\d){1}_ECLKBRIDGECS)", std::regex::optimize);
+
+    // Oscillator output
+    static const std::regex osc_clk(R"(G_J?OSC_.*)", std::regex::optimize);
+
+    // Soft Error Detection Clock *
+    // static const std::regex sed_clk(R"(G_J?SEDCLKOUT)", std::regex::optimize);
+
+    // PLL/DLL Clocks
+    // static const std::regex pll_clk(R"(G_[TB]ECLK\d)", std::regex::optimize);
+
+    // PG/INRD/LVDS
+    // static const std::regex pg(R"(G_PG)", std::regex::optimize);
+    // static const std::regex inrd(R"(G_INRD)", std::regex::optimize);
+    // static const std::regex vds(R"(G_LVDS)", std::regex::optimize);
+
+    // DDR
+    // static const std::regex ddrclkpol(R"(G_DDRCLKPOL)", std::regex::optimize);
+    // static const std::regex dqsr90(R"(G_DQSR90)", std::regex::optimize);
+    // static const std::regex qsw90(R"(G_DQSW90)", std::regex::optimize);
+
+    if(regex_match(db_name, match, global_entry) ||
+        regex_match(db_name, match, global_left_right) ||
+        regex_match(db_name, match, center_mux_glb_out) ||
+        regex_match(db_name, match, cib_out_to_glb) ||
+        regex_match(db_name, match, dcm_sig)) {
+        return GlobalType::CENTER;
+    } else if(regex_match(db_name, match, global_left_right_g)) {
+        return GlobalType::LEFT_RIGHT;
+    } else if(regex_match(db_name, match, global_up_down) ||
+        regex_match(db_name, match, global_up_down_g)) {
+        return GlobalType::UP_DOWN;
+    } else if(regex_match(db_name, match, global_branch)) {
+        return GlobalType::BRANCH;
+    } else if(regex_match(db_name, match, dcc_sig) ||
+        regex_match(db_name, match, osc_clk)) {
+        return GlobalType::OTHER;
+    } else {
+        // TODO: Not fuzzed and/or handled yet!
+        return GlobalType::NONE;
+    }
+}
+
 }
diff --git a/libtrellis/src/Tile.cpp b/libtrellis/src/Tile.cpp
index 05b2cc0..d4f723c 100644
--- a/libtrellis/src/Tile.cpp
+++ b/libtrellis/src/Tile.cpp
@@ -21,18 +21,33 @@
 static const regex tile_r_re(R"([A-Za-z0-9_]*R(\d+))");
 
 // Given the zero-indexed max chip_size, return the zero-indexed
-// center. Mainly for MachXO2.
+// center. Mainly for MachXO2, it is based on the location of the entry
+// to global routing.
 // TODO: Make const.
 map<pair<int, int>, pair<int, int>> center_map = {
+    // 256HC
+    {make_pair(7, 9), make_pair(3, 4)},
+    // 640HC
+    {make_pair(8, 17), make_pair(3, 7)},
     // 1200HC
-    {make_pair(12, 21), make_pair(6, 12)}
+    {make_pair(12, 21), make_pair(6, 12)},
+    // 2000HC
+    {make_pair(15, 25), make_pair(8, 13)},
+    // 4000HC
+    {make_pair(22, 31), make_pair(11, 15)},
+    // 7000HC
+    {make_pair(26, 40), make_pair(13, 18)},
 };
 
 // Universal function to get a zero-indexed row/column pair.
 pair<int, int> get_row_col_pair_from_chipsize(string name, pair<int, int> chip_size, int bias) {
     smatch m;
 
-    if(regex_search(name, m, tile_rxcx_re)) {
+    // Special-cases... CENTER30 will match wrong regex. Only on 7000HC,
+    // this position is a best-guess.
+    if(name.find("CENTER30") != std::string::npos) {
+        return make_pair(20, 29);
+    } else if(regex_search(name, m, tile_rxcx_re)) {
         return make_pair(stoi(m.str(1)), stoi(m.str(2)) - bias);
     } else if(regex_search(name, m, tile_centert_re)) {
         return make_pair(0, center_map[chip_size].second);
diff --git a/minitests/lut/const_0.v b/minitests/lut/const_0.v
new file mode 100644
index 0000000..0d5ec83
--- /dev/null
+++ b/minitests/lut/const_0.v
@@ -0,0 +1,3 @@
+module top(output f);
+    assign f = 0;
+endmodule
diff --git a/minitests/lut/const_1.v b/minitests/lut/const_1.v
new file mode 100644
index 0000000..6f3ad42
--- /dev/null
+++ b/minitests/lut/const_1.v
@@ -0,0 +1,3 @@
+module top(output f);
+    assign f = 1;
+endmodule
diff --git a/minitests/lut/lut4_reg.v b/minitests/lut/lut4_reg.v
new file mode 100644
index 0000000..8994d7c
--- /dev/null
+++ b/minitests/lut/lut4_reg.v
@@ -0,0 +1,11 @@
+module top(input a, input b, input c, input d, input clk, output reg q);
+
+wire q_in;
+
+LUT4 #(.init (32'hF444)) I1 ( .A (a), .B (b), .C (c), .D (d), .Z (q_in) );
+
+always @(posedge clk) begin
+  q <= q_in;
+end
+
+endmodule
diff --git a/minitests/lut/lut5_reg.v b/minitests/lut/lut5_reg.v
new file mode 100644
index 0000000..2c7b45c
--- /dev/null
+++ b/minitests/lut/lut5_reg.v
@@ -0,0 +1,11 @@
+module top(input a, input b, input c, input d, input e, input clk, output reg q);
+
+wire q_in;
+
+LUT5 #(.init (32'hF4444)) I1 ( .A (a), .B (b), .C (c), .D (d), .E (e), .Z (q_in) );
+
+always @(posedge clk) begin
+  q <= q_in;
+end
+
+endmodule
diff --git a/minitests/lut/lut7.v b/minitests/lut/lut7.v
new file mode 100644
index 0000000..1f08a39
--- /dev/null
+++ b/minitests/lut/lut7.v
@@ -0,0 +1,6 @@
+module top(input a, input b, input c, input d, input e, input f, input g, input clk, output q);
+
+// https://www.guidgenerator.com/online-guid-generator.aspx
+LUT7 #(.init (128'hd13686b35db74bd88bbb244fc3fd36af)) I1 ( .A (a), .B (b), .C (c), .D (d), .E (e), .F (f), .G (g),  .Z (q) );
+
+endmodule
diff --git a/minitests/machxo2/cb2/cb2.v b/minitests/machxo2/cb2/cb2.v
new file mode 100644
index 0000000..0b44887
--- /dev/null
+++ b/minitests/machxo2/cb2/cb2.v
@@ -0,0 +1,3 @@
+module top(input pc0, input pc1, input con, output nc0, output nc1);
+    CB2 C1 ( .CI (1'b0), .PC0 (pc0), .PC1 (pc1), .CON (con), .NC0 (nc0), .NC1 (nc1) );
+endmodule
diff --git a/minitests/machxo2/dcc/dcc1.v b/minitests/machxo2/dcc/dcc1.v
new file mode 100644
index 0000000..af00ac4
--- /dev/null
+++ b/minitests/machxo2/dcc/dcc1.v
@@ -0,0 +1,5 @@
+module dcc_test1(input clki, input ce, output clko);
+
+DCCA I1 (.CLKI (clki), .CE (ce), .CLKO (clko));
+
+endmodule
diff --git a/minitests/machxo2/dcc/dcc2.v b/minitests/machxo2/dcc/dcc2.v
new file mode 100644
index 0000000..b1efa1c
--- /dev/null
+++ b/minitests/machxo2/dcc/dcc2.v
@@ -0,0 +1,12 @@
+module dcc_test2(input clki, input ce0, input ce1, output clko);
+
+wire clk_int;
+
+// Despite EPIC claiming more DCCs exist, and the synthesizer accepting
+// the LOC constraint, trying to use the other DCCs causes an assertion failure
+// during placement!
+
+DCCA I1 (.CLKI (clki), .CE (ce0), .CLKO (clk_int));
+DCCA I2 (.CLKI (clk_int), .CE (ce1), .CLKO (clko))  /* synthesis LOC=DCC_R6C14_0B */;
+
+endmodule
diff --git a/minitests/machxo2/efb2/EFB.v b/minitests/machxo2/efb2/EFB.v
new file mode 100644
index 0000000..a7b7d55
--- /dev/null
+++ b/minitests/machxo2/efb2/EFB.v
@@ -0,0 +1,139 @@
+/* Verilog netlist generated by SCUBA Diamond (64-bit) 3.10.0.111.2 */
+/* Module Version: 1.2 */
+/* C:\lscc\diamond\3.10_x64\ispfpga\bin\nt64\scuba.exe -w -n EFB -lang verilog -synth lse -bus_exp 7 -bb -type efb -arch xo2c00 -freq 50 -spi -spi_mode Both -spi_lsb -spi_freq 1 -spi_cs 3 -pll1 -wb -dev 1200  */
+/* Tue Nov 13 20:01:18 2018 */
+
+
+`timescale 1 ns / 1 ps
+module EFB_top (wb_clk_i, wb_rst_i, wb_cyc_i, wb_stb_i, wb_we_i, wb_adr_i,
+    wb_dat_i, wb_dat_o, wb_ack_o, spi_clk, spi_miso, spi_mosi, spi_scsn,
+    spi_csn, pll0_bus_i, pll0_bus_o)/* synthesis NGD_DRC_MASK=1 */;
+    input wire wb_clk_i;
+    input wire wb_rst_i;
+    input wire wb_cyc_i;
+    input wire wb_stb_i;
+    input wire wb_we_i;
+    input wire [7:0] wb_adr_i;
+    input wire [7:0] wb_dat_i;
+    input wire spi_scsn;
+    input wire [8:0] pll0_bus_i;
+    output wire [7:0] wb_dat_o;
+    output wire wb_ack_o;
+    output wire [2:0] spi_csn;
+    output wire [16:0] pll0_bus_o;
+    inout wire spi_clk;
+    inout wire spi_miso;
+    inout wire spi_mosi;
+
+    wire scuba_vhi;
+    wire spi_mosi_oe;
+    wire spi_mosi_o;
+    wire spi_miso_oe;
+    wire spi_miso_o;
+    wire spi_clk_oe;
+    wire spi_clk_o;
+    wire spi_mosi_i;
+    wire spi_miso_i;
+    wire spi_clk_i;
+    wire scuba_vlo;
+
+    VHI scuba_vhi_inst (.Z(scuba_vhi));
+
+    BB BBspi_mosi (.I(spi_mosi_o), .T(spi_mosi_oe), .O(spi_mosi_i), .B(spi_mosi));
+
+    BB BBspi_miso (.I(spi_miso_o), .T(spi_miso_oe), .O(spi_miso_i), .B(spi_miso));
+
+    BB BBspi_clk (.I(spi_clk_o), .T(spi_clk_oe), .O(spi_clk_i), .B(spi_clk));
+
+    VLO scuba_vlo_inst (.Z(scuba_vlo));
+
+    defparam EFBInst_0.UFM_INIT_FILE_FORMAT = "HEX" ;
+    defparam EFBInst_0.UFM_INIT_FILE_NAME = "NONE" ;
+    defparam EFBInst_0.UFM_INIT_ALL_ZEROS = "ENABLED" ;
+    defparam EFBInst_0.UFM_INIT_START_PAGE = 0 ;
+    defparam EFBInst_0.UFM_INIT_PAGES = 0 ;
+    defparam EFBInst_0.DEV_DENSITY = "1200L" ;
+    defparam EFBInst_0.EFB_UFM = "DISABLED" ;
+    defparam EFBInst_0.TC_ICAPTURE = "DISABLED" ;
+    defparam EFBInst_0.TC_OVERFLOW = "DISABLED" ;
+    defparam EFBInst_0.TC_ICR_INT = "OFF" ;
+    defparam EFBInst_0.TC_OCR_INT = "OFF" ;
+    defparam EFBInst_0.TC_OV_INT = "OFF" ;
+    defparam EFBInst_0.TC_TOP_SEL = "OFF" ;
+    defparam EFBInst_0.TC_RESETN = "ENABLED" ;
+    defparam EFBInst_0.TC_OC_MODE = "TOGGLE" ;
+    defparam EFBInst_0.TC_OCR_SET = 32767 ;
+    defparam EFBInst_0.TC_TOP_SET = 65535 ;
+    defparam EFBInst_0.GSR = "ENABLED" ;
+    defparam EFBInst_0.TC_CCLK_SEL = 1 ;
+    defparam EFBInst_0.TC_MODE = "CTCM" ;
+    defparam EFBInst_0.TC_SCLK_SEL = "PCLOCK" ;
+    defparam EFBInst_0.EFB_TC_PORTMODE = "WB" ;
+    defparam EFBInst_0.EFB_TC = "DISABLED" ;
+    defparam EFBInst_0.SPI_WAKEUP = "DISABLED" ;
+    defparam EFBInst_0.SPI_INTR_RXOVR = "DISABLED" ;
+    defparam EFBInst_0.SPI_INTR_TXOVR = "DISABLED" ;
+    defparam EFBInst_0.SPI_INTR_RXRDY = "DISABLED" ;
+    defparam EFBInst_0.SPI_INTR_TXRDY = "DISABLED" ;
+    defparam EFBInst_0.SPI_SLAVE_HANDSHAKE = "DISABLED" ;
+    defparam EFBInst_0.SPI_PHASE_ADJ = "DISABLED" ;
+    defparam EFBInst_0.SPI_CLK_INV = "DISABLED" ;
+    defparam EFBInst_0.SPI_LSB_FIRST = "ENABLED" ;
+    defparam EFBInst_0.SPI_CLK_DIVIDER = 50 ;
+    defparam EFBInst_0.SPI_MODE = "BOTH" ;
+    defparam EFBInst_0.EFB_SPI = "ENABLED" ;
+    defparam EFBInst_0.I2C2_WAKEUP = "DISABLED" ;
+    defparam EFBInst_0.I2C2_GEN_CALL = "DISABLED" ;
+    defparam EFBInst_0.I2C2_CLK_DIVIDER = 1 ;
+    defparam EFBInst_0.I2C2_BUS_PERF = "100kHz" ;
+    defparam EFBInst_0.I2C2_SLAVE_ADDR = "0b1000010" ;
+    defparam EFBInst_0.I2C2_ADDRESSING = "7BIT" ;
+    defparam EFBInst_0.EFB_I2C2 = "DISABLED" ;
+    defparam EFBInst_0.I2C1_WAKEUP = "DISABLED" ;
+    defparam EFBInst_0.I2C1_GEN_CALL = "DISABLED" ;
+    defparam EFBInst_0.I2C1_CLK_DIVIDER = 1 ;
+    defparam EFBInst_0.I2C1_BUS_PERF = "100kHz" ;
+    defparam EFBInst_0.I2C1_SLAVE_ADDR = "0b1000001" ;
+    defparam EFBInst_0.I2C1_ADDRESSING = "7BIT" ;
+    defparam EFBInst_0.EFB_I2C1 = "DISABLED" ;
+    defparam EFBInst_0.EFB_WB_CLK_FREQ = "50.0" ;
+    EFB EFBInst_0 (.WBCLKI(wb_clk_i), .WBRSTI(wb_rst_i), .WBCYCI(wb_cyc_i),
+        .WBSTBI(wb_stb_i), .WBWEI(wb_we_i), .WBADRI7(wb_adr_i[7]), .WBADRI6(wb_adr_i[6]),
+        .WBADRI5(wb_adr_i[5]), .WBADRI4(wb_adr_i[4]), .WBADRI3(wb_adr_i[3]),
+        .WBADRI2(wb_adr_i[2]), .WBADRI1(wb_adr_i[1]), .WBADRI0(wb_adr_i[0]),
+        .WBDATI7(wb_dat_i[7]), .WBDATI6(wb_dat_i[6]), .WBDATI5(wb_dat_i[5]),
+        .WBDATI4(wb_dat_i[4]), .WBDATI3(wb_dat_i[3]), .WBDATI2(wb_dat_i[2]),
+        .WBDATI1(wb_dat_i[1]), .WBDATI0(wb_dat_i[0]), .PLL0DATI7(pll0_bus_i[8]),
+        .PLL0DATI6(pll0_bus_i[7]), .PLL0DATI5(pll0_bus_i[6]), .PLL0DATI4(pll0_bus_i[5]),
+        .PLL0DATI3(pll0_bus_i[4]), .PLL0DATI2(pll0_bus_i[3]), .PLL0DATI1(pll0_bus_i[2]),
+        .PLL0DATI0(pll0_bus_i[1]), .PLL0ACKI(pll0_bus_i[0]), .PLL1DATI7(scuba_vlo),
+        .PLL1DATI6(scuba_vlo), .PLL1DATI5(scuba_vlo), .PLL1DATI4(scuba_vlo),
+        .PLL1DATI3(scuba_vlo), .PLL1DATI2(scuba_vlo), .PLL1DATI1(scuba_vlo),
+        .PLL1DATI0(scuba_vlo), .PLL1ACKI(scuba_vlo), .I2C1SCLI(scuba_vlo),
+        .I2C1SDAI(scuba_vlo), .I2C2SCLI(scuba_vlo), .I2C2SDAI(scuba_vlo),
+        .SPISCKI(spi_clk_i), .SPIMISOI(spi_miso_i), .SPIMOSII(spi_mosi_i),
+        .SPISCSN(spi_scsn), .TCCLKI(scuba_vlo), .TCRSTN(scuba_vlo), .TCIC(scuba_vlo),
+        .UFMSN(scuba_vhi), .WBDATO7(wb_dat_o[7]), .WBDATO6(wb_dat_o[6]),
+        .WBDATO5(wb_dat_o[5]), .WBDATO4(wb_dat_o[4]), .WBDATO3(wb_dat_o[3]),
+        .WBDATO2(wb_dat_o[2]), .WBDATO1(wb_dat_o[1]), .WBDATO0(wb_dat_o[0]),
+        .WBACKO(wb_ack_o), .PLLCLKO(pll0_bus_o[16]), .PLLRSTO(pll0_bus_o[15]),
+        .PLL0STBO(pll0_bus_o[14]), .PLL1STBO(), .PLLWEO(pll0_bus_o[13]),
+        .PLLADRO4(pll0_bus_o[12]), .PLLADRO3(pll0_bus_o[11]), .PLLADRO2(pll0_bus_o[10]),
+        .PLLADRO1(pll0_bus_o[9]), .PLLADRO0(pll0_bus_o[8]), .PLLDATO7(pll0_bus_o[7]),
+        .PLLDATO6(pll0_bus_o[6]), .PLLDATO5(pll0_bus_o[5]), .PLLDATO4(pll0_bus_o[4]),
+        .PLLDATO3(pll0_bus_o[3]), .PLLDATO2(pll0_bus_o[2]), .PLLDATO1(pll0_bus_o[1]),
+        .PLLDATO0(pll0_bus_o[0]), .I2C1SCLO(), .I2C1SCLOEN(), .I2C1SDAO(),
+        .I2C1SDAOEN(), .I2C2SCLO(), .I2C2SCLOEN(), .I2C2SDAO(), .I2C2SDAOEN(),
+        .I2C1IRQO(), .I2C2IRQO(), .SPISCKO(spi_clk_o), .SPISCKEN(spi_clk_oe),
+        .SPIMISOO(spi_miso_o), .SPIMISOEN(spi_miso_oe), .SPIMOSIO(spi_mosi_o),
+        .SPIMOSIEN(spi_mosi_oe), .SPIMCSN7(), .SPIMCSN6(), .SPIMCSN5(),
+        .SPIMCSN4(), .SPIMCSN3(), .SPIMCSN2(spi_csn[2]), .SPIMCSN1(spi_csn[1]),
+        .SPIMCSN0(spi_csn[0]), .SPICSNEN(), .SPIIRQO(), .TCINT(), .TCOC(),
+        .WBCUFMIRQ(), .CFGWAKE(), .CFGSTDBY());
+
+
+
+    // exemplar begin
+    // exemplar end
+
+endmodule
diff --git a/minitests/machxo2/osch/osch.lpf b/minitests/machxo2/osch/osch.lpf
new file mode 100644
index 0000000..cfc4ddd
--- /dev/null
+++ b/minitests/machxo2/osch/osch.lpf
@@ -0,0 +1,3 @@
+BLOCK RESETPATHS ;
+BLOCK ASYNCPATHS ;
+LOCATE COMP "clk" SITE "13" ;
diff --git a/minitests/machxo2/osch/osch.v b/minitests/machxo2/osch/osch.v
new file mode 100644
index 0000000..6374c14
--- /dev/null
+++ b/minitests/machxo2/osch/osch.v
@@ -0,0 +1,17 @@
+module osch (
+  input clk,
+  output stdby
+);
+
+  wire out;
+
+  OSCH #(
+    .NOM_FREQ("2.08")
+  ) osch_clk (
+    .STDBY(stdby),
+    .OSC(out)
+  );
+
+  assign clk = stdby;
+
+endmodule
diff --git a/minitests/machxo2/pio/bb_machxo2.v b/minitests/machxo2/pio/bb_machxo2.v
new file mode 100644
index 0000000..473981a
--- /dev/null
+++ b/minitests/machxo2/pio/bb_machxo2.v
@@ -0,0 +1,15 @@
+module top(input pad);
+
+wire dummyo, dummyi;
+
+(* LOC="PB11C" *)
+(* IO_TYPE="LVTTL33" *)
+BB i_b(.B(pad), .O(dummyo), .I(1'b1), .T(dummyi));
+
+// Dummy load
+GSR gsr_i(.GSR(dummyo));
+
+// Dummy source
+OSCH osc_i(.OSC(dummyi));
+
+endmodule
diff --git a/minitests/machxo2/spr/spr.v b/minitests/machxo2/spr/spr.v
new file mode 100644
index 0000000..0083190
--- /dev/null
+++ b/minitests/machxo2/spr/spr.v
@@ -0,0 +1,7 @@
+module top(input [3:0] di, input ck, input wre, input [3:0] ad, output [3:0] d_o);
+
+SPR16X4C #(.initval ("0xF444")) R1 ( .DI3 (di[3]), .DI2 (di[2]), .DI1 (di[1]), .DI0 (di[0]),
+    .CK (ck), .WRE (wre), .AD3 (ad[3]), .AD2 (ad[2]), .AD1 (ad[1]), .AD0 (ad[0]),
+    .DO3 (d_o[3]), .DO2 (d_o[2]), .DO1 (d_o[1]), .DO0 (d_o[0]));
+
+endmodule
diff --git a/minitests/machxo2/vref/vref.lpf b/minitests/machxo2/vref/vref.lpf
new file mode 100644
index 0000000..8b62fef
--- /dev/null
+++ b/minitests/machxo2/vref/vref.lpf
@@ -0,0 +1,7 @@
+BLOCK RESETPATHS ;
+BLOCK ASYNCPATHS ;
+LOCATE COMP "in" SITE "12" ;
+LOCATE COMP "out" SITE "13" ;
+IOBUF PORT "in" HYSTERESIS=NA IO_TYPE=LVCMOS25R33 VREF="MyVref" ;
+LOCATE VREF "MyVref" SITE "11" ;
+IOBUF PORT "out" IO_TYPE=LVCMOS33 ;
diff --git a/minitests/machxo2/vref/vref.v b/minitests/machxo2/vref/vref.v
new file mode 100644
index 0000000..325a5a8
--- /dev/null
+++ b/minitests/machxo2/vref/vref.v
@@ -0,0 +1,8 @@
+module vref (
+  input in,
+  output out
+);
+
+  assign out = in;
+
+endmodule
\ No newline at end of file
diff --git a/minitests/reg/clk_1.v b/minitests/reg/clk_1.v
new file mode 100644
index 0000000..08895ef
--- /dev/null
+++ b/minitests/reg/clk_1.v
@@ -0,0 +1,4 @@
+module top(input clk, input d, input r, input s, output q);
+    GSR gsr(.GSR(r));
+    FD1P3JX ff(.D(d), .SP(1'b0), .PD(s), .CK(1'b1), .Q(q));
+endmodule
diff --git a/minitests/reg/latch.v b/minitests/reg/latch.v
new file mode 100644
index 0000000..63128b5
--- /dev/null
+++ b/minitests/reg/latch.v
@@ -0,0 +1,9 @@
+module top(input set, input reset, input d, output reg q);
+    always @(set or reset) begin
+        if(reset) begin
+            q <= 0;
+        end else if(set) begin
+            q <= d;
+        end
+    end
+endmodule
diff --git a/minitests/reg/latch_inv.v b/minitests/reg/latch_inv.v
new file mode 100644
index 0000000..53162ef
--- /dev/null
+++ b/minitests/reg/latch_inv.v
@@ -0,0 +1,9 @@
+module top(input set, input reset, input d, output reg q);
+    always @(set or reset) begin
+        if(reset) begin
+            q <= 0;
+        end else if(~set) begin
+            q <= d;
+        end
+    end
+endmodule
diff --git a/misc/basecfgs/empty_machxo2-1200hc.config b/misc/basecfgs/empty_machxo2-1200hc.config
new file mode 100644
index 0000000..3d116fc
--- /dev/null
+++ b/misc/basecfgs/empty_machxo2-1200hc.config
@@ -0,0 +1,30 @@
+.device LCMXO2-1200HC
+
+.tile EBR_R6C11:EBR1
+unknown: F0B12
+
+.tile EBR_R6C15:EBR1
+unknown: F0B12
+
+.tile EBR_R6C18:EBR1
+unknown: F0B12
+
+.tile EBR_R6C21:EBR1
+unknown: F0B12
+
+.tile EBR_R6C2:EBR1
+unknown: F0B12
+
+.tile EBR_R6C5:EBR1
+unknown: F0B12
+
+.tile EBR_R6C8:EBR1
+unknown: F0B12
+
+.tile PT4:CFG0
+unknown: F5B30
+unknown: F5B32
+unknown: F5B36
+
+.tile PT7:CFG3
+unknown: F5B18
diff --git a/timing/resource/picorv32_large.v b/timing/resource/picorv32_large.v
index aded6b1..7a5659f 100644
--- a/timing/resource/picorv32_large.v
+++ b/timing/resource/picorv32_large.v
@@ -44,9 +44,9 @@
 		.mem_la_addr    (mem_la_addr    ),
 		.mem_la_wdata   (mem_la_wdata   ),
 		.mem_la_wstrb   (mem_la_wstrb   ),
-		.irq            (irq            )

-	);

-	
+		.irq            (irq            )
+	);
+
 endmodule
 
 /*
@@ -2368,5 +2368,3 @@
 		end
 	end
 endmodule
-
-
diff --git a/tools/gen_globals.py b/tools/gen_globals.py
new file mode 100644
index 0000000..f7a72bb
--- /dev/null
+++ b/tools/gen_globals.py
@@ -0,0 +1,216 @@
+import pytrellis
+import database
+import itertools
+import json
+import argparse
+
+# This mirrors center_map in libtrellis. Somehow expose center_map.
+center_map = {
+    # 256HC
+    (7, 9): (3, 4),
+    # 640HC
+    (8, 17): (3, 7),
+    # 1200HC
+    (12, 21): (6, 12),
+    # 2000HC
+    (15, 25): (8, 13),
+    # 4000HC
+    (22, 31): (11, 15),
+    # 7000HC
+    (26, 40): (13, 18),
+}
+
+row_spans = {
+    # 1200HC
+    (12, 21): (5, 5),
+}
+
+start_stride = {
+    # 256HC
+    (7, 9): (0, 4),
+    # 640HC
+    (8, 17): (1, 5),
+    # 1200HC
+    (12, 21): (0, 4),
+    # 2000HC
+    (15, 25): (3, 7),
+    # 4000HC
+    (22, 31): (1, 5),
+    # 7000HC
+    (26, 40): (2, 6),
+}
+
+# There are 8 global nets. For a given column, globals are routed in pairs.
+# Convert from a pair to an index of global pairs.
+global_group = {
+    (0, 4): 0,
+    (1, 5): 1,
+    (2, 6): 2,
+    (3, 7): 3
+}
+
+inv_global_group = [(0, 4), (1, 5), (2, 6), (3, 7)]
+
+# Generate which columns route which globals.
+def column_routing(num_cols, col_1=(0, 4)):
+    i = 1
+    stride = (0, 4, 1, 5, 2, 6, 3, 7)
+
+    # Find which globals in column 0 will be routed, given which globals
+    # are routed in column 1.
+    #
+    # Column "0" in prjtrellis ("1" in Lattice numbering) always has six of
+    # the globals routed. The explanation for the final column applies here,
+    # except we are missing the four globals that would span from the right
+    # side of the U/D routing connection (and thus approach column 0 from the
+    # left).
+    col_0 = []
+
+    for g in stride:
+        if g not in col_1:
+            col_0.append(g)
+
+    yield tuple(col_0)
+
+    # Rotate the stride so the correct pair of globals are at the beginning
+    # of the list.
+    idx = global_group[col_1]*2
+    rotated_stride = stride[idx:] + stride[:idx]
+
+    # Take two at a time: https://docs.python.org/3/library/functions.html#zip
+    col_iter = itertools.cycle(zip(*[iter(rotated_stride)]*2))
+
+    for c in col_iter:
+        yield c
+
+        i = i + 1
+        if i >= num_cols:
+            break
+
+    # The final column will have 4 globals routed- the two expected globals
+    # for the column as well as the next two globals in the stride. This is
+    # because BRANCH wires that connect globals to CIBs span two columns to the
+    # right and one column to the left from where they connect to U/D routing.
+    # Since we are at the right bound of the chip, the globals we would expect
+    # to span from the left side of the U/D routing (and thus approach the
+    # final column from the right) don't physically exist! So we take care
+    # of them here.
+    yield next(col_iter) + next(col_iter)
+
+# Generate how far branches span (exclusive span, in tiles) from u/d column
+# routing.
+def branch_spans(num_cols, col_1=(0, 4)):
+    i = 1
+    col_0 = [None, (0, 0), (0, 1), (0, 2)]
+    default = {
+        (0, 4): [(1, 2), None, None, None],
+        (1, 5): [None, (1, 2), None, None],
+        (2, 6): [None, None, (1, 2), None],
+        (3, 7): [None, None, None, (1, 2)]
+    }
+
+    idx = global_group[col_1]
+    # This works, somehow...
+    rotated_col0 = col_0[-idx:] + col_0[:-idx]
+
+    yield rotated_col0
+
+    col_iter = itertools.islice(column_routing(num_cols, col_1), 1, None)
+    for c in col_iter:
+        yield default[c]
+
+        i = i + 1
+        if i > (num_cols - 2):
+            break
+
+    # At the second-to-last row of the chip, the branch, which spans two
+    # columns to the right, will be truncated by the chip's edge.
+    second_last = [None, None, None, None]
+    curr_idx = global_group[next(col_iter)]
+    second_last[curr_idx] = (1, 1)
+    yield second_last
+
+    # At the last row of the chip, BRANCHES connecting to U/D routing (which
+    # which normally span two column to the right) will be truncated by the
+    # chip's edge.
+    last = [None, None, None, None]
+    curr_idx = (curr_idx + 1) % 4
+    last[curr_idx] = (1, 0)
+
+    # The remaining two globals should come from BRANCHES from the right.
+    # But since we run into the chip's edge, we route them to the current
+    # column (and only the current column!) here.
+    curr_idx = (curr_idx + 1) % 4
+    last[curr_idx] = (0, 0)
+    yield last
+
+# 256: 9, (0, 4): L: 3, 7 has DCCs R: 0, 4 has DCCs
+# 640: 17, (1, 5): L: 0, 4 has DCCs R: 1, 5 has DCCs
+# 1200: 21, (0, 4): L: 3, 7 has DCCs R: 0, 4 has DCCs
+# 2000: 25, (3, 7), L: 2, 6 has DCCs R: 3, 7 has DCCs
+# 4000: 31, (1, 5), L: 0, 4 has DCCs R: 3, 7 has DCCs
+# 7000: 40, (2, 6), L: 1, 5 has DCCs R: 1, 5 has DCCs (both top and bottom)
+def main(args):
+    pytrellis.load_database(database.get_db_root())
+    ci = pytrellis.get_chip_info(pytrellis.find_device_by_name(args.device))
+    chip_size = (ci.max_row, ci.max_col)
+
+    globals_json = dict()
+    globals_json["lr-conns"] = {
+        "lr1" : {
+            "row" : center_map[chip_size][0],
+            "row-span" : row_spans[chip_size]
+        }
+    }
+
+    globals_json["ud-conns"] = {}
+
+    for n, c in enumerate(column_routing(chip_size[1], start_stride[chip_size])):
+        globals_json["ud-conns"][str(n)] = c
+        if n == chip_size[1] - 1:
+            last_stride = c
+
+    globals_json["branch-spans"] = {}
+
+    for col, grps in enumerate(branch_spans(chip_size[1], start_stride[chip_size])):
+        span_dict = {}
+        for gn, span in enumerate(grps):
+            if span:
+                for glb_no in inv_global_group[gn]:
+                    span_dict[str(glb_no)] = span
+
+        globals_json["branch-spans"][str(col)] = span_dict
+
+
+    # For the first and last columns, globals at the stride's current
+    # position have DCCs when viewed in EPIC. These DCCs don't appear to
+    # physically exist on-chip. See minitests/machxo2/dcc/dcc2.v. However,
+    # in the bitstream (for the first and last columns) global conns going
+    # into "DCCs" have different bits controlling them as opposed to globals
+    # without DCC connections.
+    zero_col_dccs = set(inv_global_group[(global_group[start_stride[chip_size]] - 1) % 4])
+    zero_col_conns = set(globals_json["ud-conns"]["0"])
+    missing_dccs_l = tuple(zero_col_conns.difference(zero_col_dccs))
+
+    last_col_dccs = set(inv_global_group[(global_group[last_stride] + 1) % 4])
+    last_col_conns = set(globals_json["ud-conns"][str(chip_size[1])])
+    missing_dccs_r = tuple(last_col_conns.difference(last_col_dccs))
+
+    globals_json["missing-dccs"] = {
+        "0" : missing_dccs_l,
+        str(chip_size[1]) : missing_dccs_r
+    }
+
+    with args.outfile as jsonf:
+        jsonf.write(json.dumps(globals_json, indent=4, separators=(',', ': ')))
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser("Store MachXO2 global information into globals.json.")
+    parser.add_argument('device', type=str,
+                        help="Device for which to generate globals.json.")
+    parser.add_argument('outfile', type=argparse.FileType('w'),
+                        help="Output json file (globals.json in the database).")
+    args = parser.parse_args()
+
+    main(args)
diff --git a/tools/read_pinout.py b/tools/read_pinout.py
new file mode 100644
index 0000000..95096d0
--- /dev/null
+++ b/tools/read_pinout.py
@@ -0,0 +1,116 @@
+# Pastebin J2L5zfiT
+#!/usr/bin/env python3
+import sys
+import json
+import argparse
+import pytrellis
+import database
+
+# (X, Y, Z)
+def get_bel(pin):
+    assert pin[0] == "P"
+    edge = pin[1]
+    pos = int(pin[2:-1])
+    pio = pin[-1]
+    if edge == "T":
+        return (pos, 0, pio)
+    elif edge == "B":
+        return (pos, max_row, pio)
+    elif edge == "L":
+        return (0, pos, pio)
+    elif edge == "R":
+        return (max_col, pos, pio)
+    else:
+        assert False
+
+def main(args):
+    global max_row, max_col
+
+    pytrellis.load_database(database.get_db_root())
+    chip = pytrellis.Chip(args.device)
+
+    max_row = chip.get_max_row()
+    max_col = chip.get_max_col()
+
+    if chip.info.family == "MachXO2":
+        # I/O Grouping
+        pkg_index_start = 8
+    else:
+        pkg_index_start = 7
+
+    metadata = dict()
+    package_data = dict()
+    package_indicies = None
+    found_header = False
+    with args.infile as csvf:
+        for line in csvf:
+            trline = line.strip()
+            splitline = trline.split(",")
+            if len(splitline) < (pkg_index_start + 1):
+                continue
+            if len(splitline[0].strip()) == 0:
+                continue
+            if splitline[0] == "PAD":
+                # is header
+                found_header = True
+                package_indicies = splitline[pkg_index_start:]
+                for pkg in package_indicies:
+                    package_data[pkg] = {}
+            elif found_header:
+                if splitline[1][0] != "P" or splitline[1].startswith("PROGRAM"):
+                    continue
+                bel = get_bel(splitline[1])
+                bank = int(splitline[2])
+                function = splitline[3]
+                dqs = splitline[6]
+                if chip.info.family == "MachXO2":
+                    io_grouping = splitline[7]
+                    metadata[bel] = bank, function, dqs, io_grouping
+                else:
+                    metadata[bel] = bank, function, dqs
+                for i in range(len(package_indicies)):
+                    if splitline[7+i] == "-":
+                        continue
+                    package_data[package_indicies[i]][splitline[7+i]] = bel
+    json_data = {"packages": {}, "pio_metadata": []}
+    for pkg, pins in package_data.items():
+        json_data["packages"][pkg] = {}
+        for pin, bel in pins.items():
+            json_data["packages"][pkg][pin] = {"col": bel[0], "row": bel[1], "pio": bel[2]}
+    for bel, data in sorted(metadata.items()):
+        if chip.info.family == "MachXO2":
+            bank, function, dqs, io_grouping = data
+        else:
+            bank, function, dqs = data
+        meta = {
+            "col": bel[0],
+            "row": bel[1],
+            "pio": bel[2],
+            "bank": bank
+        }
+        if function != "-":
+            meta["function"] = function
+        if dqs != "-":
+            meta["dqs"] = dqs
+
+        if chip.info.family == "MachXO2":
+            # Since "+" is used, "-" means "minus" presumably, as opposed to
+            # "not applicable".
+            meta["io_grouping"] = io_grouping
+
+        json_data["pio_metadata"].append(meta)
+    with args.outfile as jsonf:
+        jsonf.write(json.dumps(json_data, sort_keys=True, indent=4, separators=(',', ': ')))
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser("Store pinout information into iodb.json.")
+    parser.add_argument('device', type=str,
+                        help="Device for which to generate iodb.json (family autodetected).")
+    parser.add_argument('infile', type=argparse.FileType('r'),
+                        help="Input pinout CSV file from Lattice.")
+    parser.add_argument('outfile', type=argparse.FileType('w'),
+                        help="Output json file (iodb.json in the database).")
+    args = parser.parse_args()
+
+    main(args)
diff --git a/util/common/nets/general.py b/util/common/nets/general.py
index fa649e2..f1e155c 100644
--- a/util/common/nets/general.py
+++ b/util/common/nets/general.py
@@ -2,6 +2,7 @@
 import tiles
 
 import ecp5
+import machxo2
 
 
 # General inter-tile routing
@@ -27,7 +28,7 @@
 v_wire_regex = re.compile(r'V(\d{2})([NS])(\d{2})(\d{2})')
 
 
-def handle_edge_name(chip_size, tile_pos, wire_pos, netname):
+def handle_edge_name(chip_size, tile_pos, wire_pos, netname, bias):
     """
     At the edges of the device, canonical wire names do not follow normal naming conventions, as they
     would mean the nominal position of the wire would be outside the bounds of the chip. Before we add routing to the
@@ -38,32 +39,36 @@
     tile_pos: tile position as tuple (r, c)
     wire_pos: wire nominal position as tuple (r, c)
     netname: wire name without position prefix
+    bias: If "1", use Lattice's 1-based column indexing
 
     Returns a tuple (netname, wire_pos)
     """
     hm = h_wire_regex.match(netname)
     vm = v_wire_regex.match(netname)
     if hm:
+        # MachXO2 doesn't appear to have edge nets for span-1s.
         if hm.group(1) == "01":
             if tile_pos[1] == chip_size[1] - 1:
                 # H01xyy00 --> x+1, H01xyy01
                 assert hm.group(4) == "00"
                 return "H01{}{}01".format(hm.group(2), hm.group(3)), (wire_pos[0], wire_pos[1] + 1)
         elif hm.group(1) == "02":
-            if tile_pos[1] == 1:
+            if tile_pos[1] == (1 - bias):
                 # H02E0002 --> x-1, H02E0001
                 # H02W0000 --> x-1, H02W00001
-                if hm.group(2) == "E" and wire_pos[1] == 1 and hm.group(4) == "02":
+                if hm.group(2) == "E" and wire_pos[1] == (1 - bias) and hm.group(4) == "02":
                     return "H02E{}01".format(hm.group(3)), (wire_pos[0], wire_pos[1] - 1)
-                elif hm.group(2) == "W" and wire_pos[1] == 1 and hm.group(4) == "00":
+                elif hm.group(2) == "W" and wire_pos[1] == (1 - bias) and hm.group(4) == "00":
                     return "H02W{}01".format(hm.group(3)), (wire_pos[0], wire_pos[1] - 1)
-            elif tile_pos[1] == (chip_size[1] - 1):
+            elif tile_pos[1] == (chip_size[1] - (1 - bias)):
                 # H02E0000 --> x+1, H02E0001
                 # H02W0002 --> x+1, H02W00001
-                if hm.group(2) == "E" and wire_pos[1] == (chip_size[1] - 1) and hm.group(4) == "00":
+                if hm.group(2) == "E" and wire_pos[1] == (chip_size[1] - (1 - bias)) and hm.group(4) == "00":
                     return "H02E{}01".format(hm.group(3)), (wire_pos[0], wire_pos[1] + 1)
-                elif hm.group(2) == "W" and wire_pos[1] == (chip_size[1] - 1) and hm.group(4) == "02":
+                elif hm.group(2) == "W" and wire_pos[1] == (chip_size[1] - (1 - bias)) and hm.group(4) == "02":
                     return "H02W{}01".format(hm.group(3)), (wire_pos[0], wire_pos[1] + 1)
+        # Bias only affects columns... span-6s seem to work fine without
+        # a correction.
         elif hm.group(1) == "06":
             if tile_pos[1] <= 5:
                 # x-2, H06W0302 --> x-3, H06W0303
@@ -158,7 +163,7 @@
     wire: full Lattice name of the wire
     family: Device family to normalise. Affects column indexing (e.g. MachXO2 uses 1-based
           column indexing) and naming of global wires, TAP_DRIVEs, DQS, bank wires,
-          etc.
+          etc..
 
     Returns the normalised netname
     """
@@ -184,7 +189,7 @@
     if family_net:
         return family_net
 
-    netname, prefix_pos = handle_edge_name(chip_size, tile_pos, prefix_pos, netname)
+    netname, prefix_pos = handle_edge_name(chip_size, tile_pos, prefix_pos, netname, bias)
     if tile_pos == prefix_pos:
         return netname
     else:
diff --git a/util/common/nets/machxo2.py b/util/common/nets/machxo2.py
index fbfd3e2..d332428 100644
--- a/util/common/nets/machxo2.py
+++ b/util/common/nets/machxo2.py
@@ -3,12 +3,120 @@
 
 # REGEXs for global/clock signals
 
+# Globals
+global_entry_re = re.compile(r'R\d+C\d+_VPRX(\d){2}00')
+global_left_right_re = re.compile(r'R\d+C\d+_HPSX(\d){2}00')
+global_up_down_re = re.compile(r'R\d+C\d+_VPTX(\d){2}00')
+global_branch_re = re.compile(r'R\d+C\d+_HPBX(\d){2}00')
+
+# High Fanout Secondary Nets
+hfsn_entry_re = re.compile(r'R\d+C\d+_VSRX(\d){2}00')
+hfsn_left_right_re = re.compile(r'R\d+C\d+_HSSX(\d){2}00')
+# L2Rs control bidirectional portion of HFSNs!!
+hfsn_l2r_re = re.compile(r'R\d+C\d+_HSSX(\d){2}00_[RL]2[LR]')
+hfsn_up_down_re = re.compile(r'R\d+C\d+_VSTX(\d){2}00')
+# HSBX(\d){2}00 are fixed connections to HSBX(\d){2}01s.
+hfsn_branch_re = re.compile(r'R\d+C\d+_HSBX(\d){2}0[01]')
+
+# Center Mux
+# Outputs- entry to DCCs connected to globals (VPRXI -> DCC -> VPRX) *
+center_mux_glb_out_re = re.compile(r'R\d+C\d+_VPRXCLKI\d+')
+# Outputs- connected to ECLKBRIDGEs *
+center_mux_ebrg_out_re = re.compile(r'R\d+C\d+_EBRG(\d){1}CLK(\d){1}')
+
+# Inputs- CIB routing to HFSNs
+cib_out_to_hfsn_re = re.compile(r'R\d+C\d+_JSNETCIB([TBRL]|MID)(\d){1}')
+# Inputs- CIB routing to globals
+cib_out_to_glb_re = re.compile(r'R\d+C\d+_J?PCLKCIB(L[TBRL]Q|MID|VIQ[TBRL])(\d){1}')
+# Inputs- CIB routing to ECLKBRIDGEs
+cib_out_to_eclk_re = re.compile(r'R\d+C\d+_J?ECLKCIB[TBRL](\d){1}')
+
+# Inputs- Edge clocks dividers
+eclk_out_re = re.compile(r'R\d+C\d+_J?[TBRL]CDIV(X(\d){1}|(\d){2})')
+# Inputs- PLL
+pll_out_re = re.compile(r'R\d+C\d+_J?[LR]PLLCLK\d+')
+# Inputs- Clock pads
+clock_pin_re = re.compile(r'R\d+C\d+_J?PCLK[TBLR]\d+')
+
+# Part of center-mux but can also be found elsewhere
+# DCCs connected to globals *
+dcc_sig_re = re.compile(r'R\d+C\d+_J?(CLK[IO]|CE)(\d){1}[TB]?_DCC')
+
+# DCMs- connected to DCCs on globals 6 and 7 *
+dcm_sig_re = re.compile(r'R\d+C\d+_J?(CLK(\d){1}_|SEL|DCMOUT)(\d){1}_DCM')
+
+# ECLKBRIDGEs- TODO
+eclkbridge_sig_re = re.compile(r'R\d+C\d+_J?(CLK(\d){1}_|SEL|ECSOUT)(\d){1}_ECLKBRIDGECS')
+
 # Oscillator output
 osc_clk_re = re.compile(r'R\d+C\d+_J?OSC')
 
+# Soft Error Detection Clock *
+sed_clk_re = re.compile(r'R\d+C\d+_J?SEDCLKOUT')
+
+# PLL/DLL Clocks
+pll_clk_re = re.compile(r'R\d+C\d+_[TB]ECLK\d')
+
+# PG/INRD/LVDS
+pg_re = re.compile(r'R\d+C\d+_PG')
+inrd_re = re.compile(r'R\d+C\d+_INRD')
+lvds_re = re.compile(r'R\d+C\d+_LVDS')
+
+# DDR
+ddrclkpol_re = re.compile(r'R\d+C\d+_DDRCLKPOL')
+dqsr90_re = re.compile(r'R\d+C\d+_DQSR90')
+dqsw90_re = re.compile(r'R\d+C\d+_DQSW90')
+
 def is_global(wire):
     """Return true if a wire is part of the global clock network"""
-    return bool(osc_clk_re.match(wire))
+    return bool(global_entry_re.match(wire) or
+        global_left_right_re.match(wire) or
+        global_up_down_re.match(wire) or
+        global_branch_re.match(wire) or
+        hfsn_entry_re.match(wire) or
+        hfsn_left_right_re.match(wire) or
+        hfsn_l2r_re.match(wire) or
+        hfsn_up_down_re.match(wire) or
+        hfsn_branch_re.match(wire) or
+        center_mux_glb_out_re.match(wire) or
+        center_mux_ebrg_out_re.match(wire) or
+        cib_out_to_hfsn_re.match(wire) or
+        cib_out_to_glb_re.match(wire) or
+        cib_out_to_eclk_re.match(wire) or
+        eclk_out_re.match(wire) or
+        pll_out_re.match(wire) or
+        clock_pin_re.match(wire) or
+        dcc_sig_re.match(wire) or
+        dcm_sig_re.match(wire) or
+        eclkbridge_sig_re.match(wire) or
+        osc_clk_re.match(wire) or
+        sed_clk_re.match(wire) or
+        pll_clk_re.match(wire) or
+        pg_re.match(wire) or
+        inrd_re.match(wire) or
+        lvds_re.match(wire) or
+        ddrclkpol_re.match(wire) or
+        dqsr90_re.match(wire) or
+        dqsw90_re.match(wire))
 
 def handle_family_net(tile, wire, prefix_pos, tile_pos, netname):
-    raise NotImplementedError("MachXO2 device family not implemented.")
+    if tile.startswith("CENTER") and global_left_right_re.match(wire):
+        if prefix_pos[1] < tile_pos[1]:
+            return "L_" + netname
+        elif prefix_pos[1] > tile_pos[1]:
+            return "R_" + netname
+        else:
+            assert False, "bad CENTER netname"
+    elif "CIB_EBR" in tile and global_up_down_re.match(wire):
+        if prefix_pos[0] < tile_pos[0]:
+            return "U_" + netname
+        elif prefix_pos[0] > tile_pos[0]:
+            return "D_" + netname
+        else:
+            assert False, "bad CIB_EBR netname"
+    elif global_branch_re.match(wire):
+        return "BRANCH_" + netname
+    elif is_global(wire):
+        return "G_" + netname
+    else:
+        return None
diff --git a/util/fuzz/dbcopy.py b/util/fuzz/dbcopy.py
index 5c8dda7..3194320 100644
--- a/util/fuzz/dbcopy.py
+++ b/util/fuzz/dbcopy.py
@@ -49,3 +49,29 @@
         fcs = srcdb.get_fixed_conns()
         for conn in fcs:
             dstdb.add_fixed_conn(conn)
+
+
+def copy_muxes_with_predicate(family, device, source, dest, predicate):
+    srcdb = pytrellis.get_tile_bitdata(
+        pytrellis.TileLocator(family, device, source))
+    dstdb = pytrellis.get_tile_bitdata(
+        pytrellis.TileLocator(family, device, dest))
+
+    sinks = srcdb.get_sinks()
+    for sink in sinks:
+        mux = srcdb.get_mux_data_for_sink(sink)
+        for src in mux.get_sources():
+            if predicate((src, sink)):
+                dstdb.add_mux_arc(mux.arcs[src])
+
+
+def copy_conns_with_predicate(family, device, source, dest, predicate):
+    srcdb = pytrellis.get_tile_bitdata(
+        pytrellis.TileLocator(family, device, source))
+    dstdb = pytrellis.get_tile_bitdata(
+        pytrellis.TileLocator(family, device, dest))
+
+    fcs = srcdb.get_fixed_conns()
+    for conn in fcs:
+        if predicate(conn):
+            dstdb.add_fixed_conn(conn)