Merge branch 'master' of https://github.com/YosysHQ/prjtrellis into facade
diff --git a/.gitmodules b/.gitmodules
index d18f724..7d0eaf4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "database"]
 	path = database
-	url = https://github.com/YosysHQ/prjtrellis-db
+	url = https://github.com/cr1901/prjtrellis-db.git
+	branch = facade
diff --git a/database b/database
index c137076..fb3fa8c 160000
--- a/database
+++ b/database
@@ -1 +1 @@
-Subproject commit c137076fdd8bfca3d2bf9cdacda9983dbbec599a
+Subproject commit fb3fa8cfc1ce57a8ba81a8a94da01f096664b057
diff --git a/devices.json b/devices.json
index 61eaaa8..bf12869 100644
--- a/devices.json
+++ b/devices.json
@@ -24,7 +24,7 @@
                 "max_row" : 50,
                 "max_col" : 72,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5U-45F": {
                 "packages": ["csfBGA285", "caBGA256", "caBGA381", "caBGA554", "caBGA756"],
@@ -36,7 +36,7 @@
                 "max_row" : 71,
                 "max_col" : 90,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5U-85F": {
                 "packages": ["csfBGA285", "caBGA381", "caBGA554", "caBGA756"],
@@ -48,7 +48,7 @@
                 "max_row" : 95,
                 "max_col" : 126,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5UM-25F": {
                 "packages": ["csfBGA285", "caBGA256", "caBGA381", "caBGA554", "caBGA756"],
@@ -60,7 +60,7 @@
                 "max_row" : 50,
                 "max_col" : 72,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5UM-45F": {
                 "packages": ["csfBGA285", "caBGA256", "caBGA381", "caBGA554", "caBGA756"],
@@ -72,7 +72,7 @@
                 "max_row" : 71,
                 "max_col" : 90,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5UM-85F": {
                 "packages": ["csfBGA285", "caBGA381", "caBGA554", "caBGA756"],
@@ -84,7 +84,7 @@
                 "max_row" : 95,
                 "max_col" : 126,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5UM5G-25F": {
                 "packages": ["csfBGA285", "caBGA256", "caBGA381", "caBGA554", "caBGA756"],
@@ -96,7 +96,7 @@
                 "max_row" : 50,
                 "max_col" : 72,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5UM5G-45F": {
                 "packages": ["csfBGA285", "caBGA256", "caBGA381", "caBGA554", "caBGA756"],
@@ -108,7 +108,7 @@
                 "max_row" : 71,
                 "max_col" : 90,
                 "col_bias" : 0,
-                "fuzz": 1
+                "fuzz": 0
             },
             "LFE5UM5G-85F": {
                 "packages": ["csfBGA285", "caBGA381", "caBGA554", "caBGA756"],
@@ -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_diff.txt b/experiments/machxo2/ccu2_mux/ccu2_diff.txt
new file mode 100644
index 0000000..d11d3d7
--- /dev/null
+++ b/experiments/machxo2/ccu2_mux/ccu2_diff.txt
@@ -0,0 +1,322 @@
+CENTER10:CENTER_DUMMY
+CENTER11:CENTER_B_CIB
+CENTER1:CENTER_T_CIB
+CENTER2:CENTER_DUMMY
+CENTER3:CENTER_DUMMY
+CENTER4:CENTER4   
+CENTER5:CENTER5   
+CENTER6:CENTER_EBR_CIB
+CENTER7:CENTER6   
+CENTER8:CENTER7   
+CENTER9:CENTER8   
+CENTER_B:CENTER_B 
+CENTER_EBR14:CENTER_EBR
+CENTER_T:CENTER_T 
+CIB_R11C10:CIB_PIC_B_DUMMY
+CIB_R11C11:CIB_PIC_B0
+CIB_R11C12:CIB_PIC_B_DUMMY
+CIB_R11C13:CIB_PIC_B_DUMMY
+CIB_R11C14:CIB_PIC_B_DUMMY
+CIB_R11C15:CIB_PIC_B0
+CIB_R11C16:CIB_PIC_B_DUMMY
+CIB_R11C17:CIB_PIC_B_DUMMY
+CIB_R11C18:CIB_PIC_B0
+CIB_R11C19:CIB_PIC_B_DUMMY
+CIB_R11C20:CIB_PIC_B0
+CIB_R11C21:CIB_PIC_B_DUMMY
+CIB_R11C2:CIB_PIC_B_DUMMY
+CIB_R11C3:CIB_PIC_B_DUMMY
+CIB_R11C4:CIB_PIC_B0
+CIB_R11C5:CIB_PIC_B_DUMMY
+CIB_R11C6:CIB_PIC_B0
+CIB_R11C7:CIB_PIC_B_DUMMY
+CIB_R11C8:CIB_PIC_B_DUMMY
+CIB_R11C9:CIB_PIC_B0
+CIB_R1C10:CIB_PIC_T0
+CIB_R1C11:CIB_PIC_T0
+CIB_R1C12:CIB_PIC_T0
+CIB_R1C13:CIB_PIC_T_DUMMY
+CIB_R1C14:CIB_PIC_T_DUMMY
+CIB_R1C15:CIB_PIC_T0
+CIB_R1C16:CIB_PIC_T0
+CIB_R1C17:CIB_PIC_T0
+CIB_R1C18:CIB_PIC_T_DUMMY
+CIB_R1C19:CIB_PIC_T_DUMMY
+CIB_R1C20:CIB_PIC_T_DUMMY
+CIB_R1C21:CIB_PIC_T_DUMMY
+CIB_R1C2:CIB_PIC_T_DUMMY
+CIB_R1C3:CIB_PIC_T_DUMMY
+CIB_R1C4:CIB_CFG0 
+CIB_R1C5:CIB_CFG1 
+CIB_R1C6:CIB_CFG2 
+CIB_R1C7:CIB_CFG3 
+CIB_R1C8:CIB_PIC_T_DUMMY
+CIB_R1C9:CIB_PIC_T0
+CIB_R6C10:CIB_EBR0
+CIB_R6C11:CIB_EBR1
+CIB_R6C12:CIB_EBR2
+CIB_R6C13:CIB_EBR_DUMMY
+CIB_R6C14:CIB_EBR0
+CIB_R6C15:CIB_EBR1
+CIB_R6C16:CIB_EBR2
+CIB_R6C17:CIB_EBR0
+CIB_R6C18:CIB_EBR1
+CIB_R6C19:CIB_EBR2
+CIB_R6C1:CIB_EBR0_END0
+CIB_R6C20:CIB_EBR0
+CIB_R6C21:CIB_EBR1
+CIB_R6C22:CIB_EBR2_END0
+CIB_R6C2:CIB_EBR1 
+CIB_R6C3:CIB_EBR2 
+CIB_R6C4:CIB_EBR0 
+CIB_R6C5:CIB_EBR1 
+CIB_R6C6:CIB_EBR2 
+CIB_R6C7:CIB_EBR0 
+CIB_R6C8:CIB_EBR1 
+CIB_R6C9:CIB_EBR2 
+EBR_R6C10:EBR0    
+EBR_R6C11:EBR1    
+EBR_R6C12:EBR2    
+EBR_R6C13:EBR_DUMMY
+EBR_R6C14:EBR0    
+EBR_R6C15:EBR1    
+EBR_R6C16:EBR2    
+EBR_R6C17:EBR0    
+EBR_R6C18:EBR1    
+EBR_R6C19:EBR2    
+EBR_R6C1:EBR0_END 
+EBR_R6C20:EBR0    
+EBR_R6C21:EBR1    
+EBR_R6C22:EBR2_END
+EBR_R6C2:EBR1     
+EBR_R6C3:EBR2     
+EBR_R6C4:EBR0     
+EBR_R6C5:EBR1     
+EBR_R6C6:EBR2     
+EBR_R6C7:EBR0     
+EBR_R6C8:EBR1     
+EBR_R6C9:EBR2     
+PB10:PIC_B_DUMMY_VREF
+PB11:PIC_B0       
+PB12:PIC_B_DUMMY  
+PB13:PIC_B_DUMMY_VIQ
+PB14:PIC_B_DUMMY  
+PB15:PIC_B0       
+PB16:PIC_B_DUMMY  
+PB17:PIC_B_DUMMY  
+PB18:PIC_B0       
+PB19:PIC_B_DUMMY  
+PB1:B_DUMMY_ENDL  
+PB20:PIC_B0       
+PB21:PIC_B_DUMMY  
+PB22:B_DUMMY_ENDR 
+PB2:DQSDLL_L      
+PB3:PIC_B_DUMMY   
+PB4:PIC_B0        
+PB5:PIC_B_DUMMY   
+PB6:PIC_B0        
+PB7:PIC_B_DUMMY   
+PB8:PIC_B_DUMMY   
+PB9:PIC_B0        
+PL10:PIC_L0       
+PL11:LLC0         
+PL1:ULC0          
+PL2:PIC_L0        
+PL3:PIC_L0        
+PL4:PIC_L0_VREF3  
+PL5:PIC_L0        
+PL7:PIC_L0_DUMMY  
+PL8:PIC_L0        
+PL9:PIC_LS0       
+PR10:PIC_R0       
+PR11:LRC0         
+PR1:URC0          
+PR2:PIC_R0        
+PR3:PIC_RS0       
+PR4:PIC_R0        
+PR5:PIC_R0        
+PR7:PIC_R0_DUMMY  
+PR8:PIC_R0        
+PR9:PIC_R0        
+PT10:PIC_T0       
+PT11:PIC_T0       
+PT12:PIC_T0       
+PT13:PIC_T_DUMMY_VIQ
+PT14:PIC_T_DUMMY  
+PT15:PIC_T0       
+PT16:PIC_T0       
+PT17:PIC_T0       
+PT18:PIC_T_DUMMY  
+PT19:PIC_T_DUMMY  
+PT1:GPLL_L0       
+PT20:PIC_T_DUMMY  
+PT21:DQSDLL_R     
+PT22:T_DUMMY_ENDR 
+PT2:PIC_T_DUMMY   
+PT3:PIC_T_DUMMY   
+PT4:CFG0          
+PT5:CFG1          
+PT6:CFG2          
+PT7:CFG3          
+PT8:PIC_T_DUMMY_OSC
+PT9:PIC_T0        
+R10C10:PLC        
+R10C11:PLC        !F24B13 !F24B14 !F24B15 !F24B16 !F24B17 !F24B18 F24B19 F24B20 F24B29 F24B30 !F24B31 !F24B32 !F24B33 !F24B34 !F24B35 !F24B36 F25B13 F25B14 F25B15 F25B16 F25B17 F25B18 F25B19 F25B20 F25B29 F25B30 F25B31 F25B32 F25B33 F25B34 F25B35 F25B36 F26B13 F26B14 F26B15 F26B16 F26B17 F26B18 F26B19 F26B20 F26B29 F26B30 F26B31 F26B32 F26B33 F26B34 F26B35 F26B36 F26B37 F26B38 F26B39 F26B40 F26B41 F26B42 F26B43 F26B44 !F27B13 !F27B14 !F27B15 !F27B16 !F27B17 !F27B18 F27B19 F27B20 F27B29 F27B30 !F27B31 !F27B32 !F27B33 !F27B34 !F27B35 !F27B36 !F27B37 !F27B38 !F27B39 !F27B40 !F27B41 !F27B42 F27B43 F27B44
+R10C12:PLC        
+R10C13:PLC        
+R10C14:PLC        
+R10C15:PLC        
+R10C16:PLC        
+R10C17:PLC        
+R10C18:PLC        
+R10C19:PLC        
+R10C20:PLC        
+R10C21:PLC        
+R10C2:PLC         
+R10C3:PLC         
+R10C4:PLC         
+R10C5:PLC         
+R10C6:PLC         
+R10C7:PLC         
+R10C8:PLC         
+R10C9:PLC         
+R2C10:PLC         
+R2C11:PLC         
+R2C12:PLC         
+R2C13:PLC         
+R2C14:PLC         
+R2C15:PLC         
+R2C16:PLC         
+R2C17:PLC         
+R2C18:PLC         
+R2C19:PLC         
+R2C20:PLC         
+R2C21:PLC         
+R2C2:PLC          
+R2C3:PLC          
+R2C4:PLC          
+R2C5:PLC          
+R2C6:PLC          
+R2C7:PLC          
+R2C8:PLC          
+R2C9:PLC          
+R3C10:PLC         
+R3C11:PLC         
+R3C12:PLC         
+R3C13:PLC         
+R3C14:PLC         
+R3C15:PLC         
+R3C16:PLC         
+R3C17:PLC         
+R3C18:PLC         
+R3C19:PLC         
+R3C20:PLC         
+R3C21:PLC         
+R3C2:PLC          
+R3C3:PLC          
+R3C4:PLC          
+R3C5:PLC          
+R3C6:PLC          
+R3C7:PLC          
+R3C8:PLC          
+R3C9:PLC          
+R4C10:PLC         
+R4C11:PLC         
+R4C12:PLC         
+R4C13:PLC         
+R4C14:PLC         
+R4C15:PLC         
+R4C16:PLC         
+R4C17:PLC         
+R4C18:PLC         
+R4C19:PLC         
+R4C20:PLC         
+R4C21:PLC         
+R4C2:PLC          
+R4C3:PLC          
+R4C4:PLC          
+R4C5:PLC          
+R4C6:PLC          
+R4C7:PLC          
+R4C8:PLC          
+R4C9:PLC          
+R5C10:PLC         
+R5C11:PLC         
+R5C12:PLC         
+R5C13:PLC         
+R5C14:PLC         
+R5C15:PLC         
+R5C16:PLC         
+R5C17:PLC         
+R5C18:PLC         
+R5C19:PLC         
+R5C20:PLC         
+R5C21:PLC         
+R5C2:PLC          
+R5C3:PLC          
+R5C4:PLC          
+R5C5:PLC          
+R5C6:PLC          
+R5C7:PLC          
+R5C8:PLC          
+R5C9:PLC          
+R7C10:PLC         
+R7C11:PLC         
+R7C12:PLC         
+R7C13:PLC         
+R7C14:PLC         
+R7C15:PLC         
+R7C16:PLC         
+R7C17:PLC         
+R7C18:PLC         
+R7C19:PLC         
+R7C20:PLC         
+R7C21:PLC         
+R7C2:PLC          
+R7C3:PLC          
+R7C4:PLC          
+R7C5:PLC          
+R7C6:PLC          
+R7C7:PLC          
+R7C8:PLC          
+R7C9:PLC          
+R8C10:PLC         
+R8C11:PLC         
+R8C12:PLC         
+R8C13:PLC         
+R8C14:PLC         
+R8C15:PLC         
+R8C16:PLC         
+R8C17:PLC         
+R8C18:PLC         
+R8C19:PLC         
+R8C20:PLC         
+R8C21:PLC         
+R8C2:PLC          
+R8C3:PLC          
+R8C4:PLC          
+R8C5:PLC          
+R8C6:PLC          
+R8C7:PLC          
+R8C8:PLC          
+R8C9:PLC          
+R9C10:PLC         
+R9C11:PLC         
+R9C12:PLC         
+R9C13:PLC         
+R9C14:PLC         
+R9C15:PLC         
+R9C16:PLC         
+R9C17:PLC         
+R9C18:PLC         
+R9C19:PLC         
+R9C20:PLC         
+R9C21:PLC         
+R9C2:PLC          
+R9C3:PLC          
+R9C4:PLC          
+R9C5:PLC          
+R9C6:PLC          
+R9C7:PLC          
+R9C8:PLC          
+R9C9:PLC          
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_diff.txt b/experiments/machxo2/center_mux/center_mux_diff.txt
new file mode 100644
index 0000000..8adfd23
--- /dev/null
+++ b/experiments/machxo2/center_mux/center_mux_diff.txt
@@ -0,0 +1,47 @@
+R1C13_JCLK0 -> R6C13_JPCLKCIBVIQT0
+
+R6C13_JPCLKCIBVIQT0 -> R6C13_PCLKCIBVIQT0
+
+R6C13_PCLKCIBVIQT0 -> R6C13_VPRXCLKI0
+CENTER8:CENTER7               F25B0
+CENTER9:CENTER8               F0B0 F0B1 F1B1
+
+R6C13_VPRXCLKI0 -> R6C13_CLKI0_DCC
+
+R6C13_CLKI0_DCC -> R6C13_CLKO0_DCC
+
+R6C13_CLKO0_DCC -> R6C13_VPRX0000
+
+R6C13_VPRX0000 -> R6C8_HPSX0000
+CENTER6:CENTER_EBR_CIB        F23B0
+
+R6C13_VPRX0000 -> R6C18_HPSX0000
+CENTER6:CENTER_EBR_CIB        F23B1
+
+R6C13_JLPLLCLK1 -> R6C13_VPRXCLKI0
+CENTER8:CENTER7               F28B1
+CENTER9:CENTER8               F1B1
+
+R6C8_HPSX0000 -> R6C10_CLKI0B_DCC
+
+R6C10_CLKI0B_DCC -> R6C10_CLKO0B_DCC
+CIB_R6C10:CIB_EBR0            F26B31
+
+R6C14_CLKI0B_DCC -> R6C14_CLKO0B_DCC
+CIB_R6C14:CIB_EBR0            F26B31
+
+R6C13_JTCDIVX1 -> R6C13_VPRXCLKI5
+CENTER5:CENTER5               F25B0 F27B0
+
+R6C13_PCLKCIBMID2 -> R6C13_VPRXCLK60
+CENTER5:CENTER5               F25B0 F27B0
+
+R6C13_PCLKCIBMID3 -> R6C13_VPRXCLK61
+CENTER5:CENTER5               F25B0 F27B0
+
+R6C13_PCLKCIBMID2 -> R6C13_VPRXCLK71
+CENTER5:CENTER5               F25B0 F27B0
+
+R6C13_PCLKCIBMID3 -> R6C13_VPRXCLK70
+CENTER5:CENTER5               F25B0 F27B0
+
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..36e8597
--- /dev/null
+++ b/fuzzers/machxo2/021-glb-entry/fuzzer.py
@@ -0,0 +1,40 @@
+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():
+    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..dc5af6f
--- /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 loc in "B":
+                nonrouting.fuzz_enum_setting(cfg, "PIO{}.CLAMP".format(pio), ["PCI", "OFF"],
+                                             lambda x: get_substs(iomode="INPUT_LVCMOS33", 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 loc in "T" and pio in "A":
+                nonrouting.fuzz_enum_setting(cfg, "PIO{}.DIFFDRIVE".format(pio), ["1.25", "2.0", "2.5", "3.5"],
+                                             lambda x: get_substs(iomode="INPUT_LVCMOS33", extracfg=("CLAMP", 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..f1c7f1e 100644
--- a/libtrellis/include/RoutingGraph.hpp
+++ b/libtrellis/include/RoutingGraph.hpp
@@ -8,6 +8,9 @@
 #include <set>
 #include <boost/functional/hash.hpp>
 
+#include "Chip.hpp"
+#include "Database.hpp"
+
 using namespace std;
 
 namespace Trellis {
@@ -132,6 +135,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 +158,18 @@
     // 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.
+    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 to pytrellis for now.
+    MachXO2GlobalsInfo global_data_machxo2;
 };
 }
 
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..14808a2 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) {
@@ -118,145 +134,187 @@
         bitdb->add_routing(tile->info, *rg);
         int x, y;
         tie(y, x) = tile->info.get_row_col();
-        // SLICE Bels
+        // SLICE Ecp5Bels
         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
+        // PIO Ecp5Bels
         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
+        // DCC Ecp5Bels
         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));
-        // RAM Bels
+                Ecp5Bels::add_dcc(*rg, x, y, "B", std::to_string(z));
+        // RAM Ecp5Bels
         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);
-        // DSP Bels
+            Ecp5Bels::add_bram(*rg, x, y, 3);
+        // DSP Ecp5Bels
         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);
-        // PLL Bels
+            Ecp5Bels::add_alu54b(*rg, x, y, 7);
+        // PLL Ecp5Bels
         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);
-        // DCU and ancillary Bels
+            Ecp5Bels::add_pll(*rg, "UR", x-1, y);
+        // DCU and ancillary Ecp5Bels
         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);
-        // Config/system Bels
+                Ecp5Bels::add_pcsclkdiv(*rg, x, y-1, z);
+        // Config/system Ecp5Bels
         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 MachXO2Bels
+        if (tile->info.type == "PLC")
+            for (int z = 0; z < 4; z++)
+                MachXO2Bels::add_lc(*rg, x, y, z);
+
+        // PIO MachXO2Bels
+        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 MachXO2Bels
+        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..5f70c08 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..8133e7e 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,8 @@
             .def_readwrite("tiles", &Chip::tiles)
             .def_readwrite("usercode", &Chip::usercode)
             .def_readwrite("metadata", &Chip::metadata)
-            .def_readwrite("global_data", &Chip::global_data)
+            .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 +477,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 +554,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 +570,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..a63abbf 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,100 @@
     }
 }
 
+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) {
+      //size_t sub_idx = 0;
+
+      // if(stripped_name.find("BRANCH_") == 0) {
+      //     sub_idx = 7;
+      // } else {
+      //     sub_idx = 3;
+      // }
+
+      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 +275,241 @@
     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) {
+    // 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 regexs 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);
+
+    // Globals are given their nominal position, even if they span multiple
+    // tiles, by the following rules:
+    smatch m;
+    pair<int, int> center = center_map[make_pair(max_row, max_col)];
+    RoutingId curr_global;
+
+    // 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(regex_match(db_name, m, global_entry) ||
+        regex_match(db_name, m, global_left_right) ||
+        regex_match(db_name, m, center_mux_glb_out) ||
+        regex_match(db_name, m, cib_out_to_glb) ||
+        regex_match(db_name, m, dcm_sig)) {
+
+        // XXX: This is a MachXO2-1200HC-specific hack; VPRXCLKI0's routing
+        // config bits are split across multiple tiles. Arcs do not store data
+        // about their tile position in the routing graph (as that's the DB's
+        // responsibility). Even so, we do _not_ want the same physical arc
+        // that appears in two tiles in the DB appearing twice in the routing
+        // graph. So for now, special-case and remove the second arc at
+        // (12, 9).
+        //
+        // If more than one arc has this problem, we may need to change this
+        // function signature to be device-specific, and find a better way to
+        // store this info along with the routing graph besides special-casing.
+        if((db_name.find("G_VPRXCLKI0") != string::npos) && row == 9 && col == 12)
+            return RoutingId();
+
+        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(regex_match(db_name, m, global_left_right_g)) {
+        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(regex_match(db_name, m, global_up_down) ||
+        regex_match(db_name, m, global_up_down_g)) {
+
+        std::vector<int> & ud_conns_in_col = global_data_machxo2.ud_conns[col];
+
+        // First check whether the requested global is in the current column.
+        // If not, no point in continuing.
+        if(std::find(ud_conns_in_col.begin(), ud_conns_in_col.end(),
+                    std::stoi(m.str(1))) == ud_conns_in_col.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'));
+
+            curr_global.id = ident(db_name);
+            curr_global.loc.x = col;
+            curr_global.loc.y = center.first;
+            return curr_global;
+        } 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');
+
+            std::string db_copy = db_name;
+
+            // 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(regex_match(db_name, m, global_branch)) {
+        std::vector<int> candidate_cols;
+
+        if(col == 0) {
+            candidate_cols.push_back(0);
+            candidate_cols.push_back(1);
+        } else if(col == 1) {
+            candidate_cols.push_back(0);
+            candidate_cols.push_back(1);
+            candidate_cols.push_back(2);
+        } else if(col == max_col) {
+            candidate_cols.push_back(max_col - 2);
+            candidate_cols.push_back(max_col - 1);
+            candidate_cols.push_back(max_col);
+        } else {
+            candidate_cols.push_back(col - 2);
+            candidate_cols.push_back(col - 1);
+            candidate_cols.push_back(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];
+
+            // First check whether the requested global is in the current column.
+            // If not, no point in continuing.
+            if(std::find(ud_conns_in_col.begin(), ud_conns_in_col.end(),
+                        std::stoi(m.str(1))) != ud_conns_in_col.end()) {
+                curr_global.id = ident(db_name);
+                curr_global.loc.x = curr_col;
+                curr_global.loc.y = row;
+                return curr_global;
+            }
+        }
+
+        // One of the candidate columns should have had the correct U/D
+        // connection to route to.
+        assert(false);
+        return RoutingId();
+
+    // 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(regex_match(db_name, m, dcc_sig) ||
+        regex_match(db_name, m, osc_clk)) {
+        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();
+    }
+}
+
 }
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/metadata/MachXO2/LCMXO2-1200HC/globals.json b/metadata/MachXO2/LCMXO2-1200HC/globals.json
new file mode 100644
index 0000000..a408e0b
--- /dev/null
+++ b/metadata/MachXO2/LCMXO2-1200HC/globals.json
@@ -0,0 +1,8 @@
+{
+  "quadrants": {
+  },
+  "taps": {
+  },
+  "spines": {
+  }
+}
diff --git a/metadata/MachXO2/LCMXO2-256HC/globals.json b/metadata/MachXO2/LCMXO2-256HC/globals.json
new file mode 100644
index 0000000..a408e0b
--- /dev/null
+++ b/metadata/MachXO2/LCMXO2-256HC/globals.json
@@ -0,0 +1,8 @@
+{
+  "quadrants": {
+  },
+  "taps": {
+  },
+  "spines": {
+  }
+}
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/efb/EFB.v b/minitests/machxo2/efb/EFB.v
new file mode 100644
index 0000000..03ef199
--- /dev/null
+++ b/minitests/machxo2/efb/EFB.v
@@ -0,0 +1,140 @@
+/* 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 -i2c1 -i2c1_freq 100 -i2c1_sa 0001000001 -i2c1_addr 10 -i2c2 -i2c2_freq 100 -i2c2_sa 0001000010 -i2c2_addr 10 -wb -dev 1200  */
+/* Tue Nov 13 20:08:07 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, i2c1_scl, i2c1_sda, i2c1_irqo,
+    i2c2_scl, i2c2_sda, i2c2_irqo)/* 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;
+    output wire [7:0] wb_dat_o;
+    output wire wb_ack_o;
+    output wire i2c1_irqo;
+    output wire i2c2_irqo;
+    inout wire i2c1_scl;
+    inout wire i2c1_sda;
+    inout wire i2c2_scl;
+    inout wire i2c2_sda;
+
+    wire scuba_vhi;
+    wire scuba_vlo;
+    wire i2c2_sdaoen;
+    wire i2c2_sdao;
+    wire i2c2_scloen;
+    wire i2c2_sclo;
+    wire i2c2_sdai;
+    wire i2c2_scli;
+    wire i2c1_sdaoen;
+    wire i2c1_sdao;
+    wire i2c1_scloen;
+    wire i2c1_sclo;
+    wire i2c1_sdai;
+    wire i2c1_scli;
+
+    VHI scuba_vhi_inst (.Z(scuba_vhi));
+
+    VLO scuba_vlo_inst (.Z(scuba_vlo));
+
+    BB BB2_sda (.I(i2c2_sdao), .T(i2c2_sdaoen), .O(i2c2_sdai), .B(i2c2_sda));
+
+    BB BB2_scl (.I(i2c2_sclo), .T(i2c2_scloen), .O(i2c2_scli), .B(i2c2_scl));
+
+    BB BB1_sda (.I(i2c1_sdao), .T(i2c1_sdaoen), .O(i2c1_sdai), .B(i2c1_sda));
+
+    BB BB1_scl (.I(i2c1_sclo), .T(i2c1_scloen), .O(i2c1_scli), .B(i2c1_scl));
+
+    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 = "DISABLED" ;
+    defparam EFBInst_0.SPI_CLK_DIVIDER = 1 ;
+    defparam EFBInst_0.SPI_MODE = "MASTER" ;
+    defparam EFBInst_0.EFB_SPI = "DISABLED" ;
+    defparam EFBInst_0.I2C2_WAKEUP = "DISABLED" ;
+    defparam EFBInst_0.I2C2_GEN_CALL = "DISABLED" ;
+    defparam EFBInst_0.I2C2_CLK_DIVIDER = 125 ;
+    defparam EFBInst_0.I2C2_BUS_PERF = "100kHz" ;
+    defparam EFBInst_0.I2C2_SLAVE_ADDR = "0b0001000010" ;
+    defparam EFBInst_0.I2C2_ADDRESSING = "10BIT" ;
+    defparam EFBInst_0.EFB_I2C2 = "ENABLED" ;
+    defparam EFBInst_0.I2C1_WAKEUP = "DISABLED" ;
+    defparam EFBInst_0.I2C1_GEN_CALL = "DISABLED" ;
+    defparam EFBInst_0.I2C1_CLK_DIVIDER = 125 ;
+    defparam EFBInst_0.I2C1_BUS_PERF = "100kHz" ;
+    defparam EFBInst_0.I2C1_SLAVE_ADDR = "0b0001000001" ;
+    defparam EFBInst_0.I2C1_ADDRESSING = "10BIT" ;
+    defparam EFBInst_0.EFB_I2C1 = "ENABLED" ;
+    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(scuba_vlo),
+        .PLL0DATI6(scuba_vlo), .PLL0DATI5(scuba_vlo), .PLL0DATI4(scuba_vlo),
+        .PLL0DATI3(scuba_vlo), .PLL0DATI2(scuba_vlo), .PLL0DATI1(scuba_vlo),
+        .PLL0DATI0(scuba_vlo), .PLL0ACKI(scuba_vlo), .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(i2c1_scli),
+        .I2C1SDAI(i2c1_sdai), .I2C2SCLI(i2c2_scli), .I2C2SDAI(i2c2_sdai),
+        .SPISCKI(scuba_vlo), .SPIMISOI(scuba_vlo), .SPIMOSII(scuba_vlo),
+        .SPISCSN(scuba_vlo), .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(), .PLLRSTO(), .PLL0STBO(), .PLL1STBO(),
+        .PLLWEO(), .PLLADRO4(), .PLLADRO3(), .PLLADRO2(), .PLLADRO1(), .PLLADRO0(),
+        .PLLDATO7(), .PLLDATO6(), .PLLDATO5(), .PLLDATO4(), .PLLDATO3(),
+        .PLLDATO2(), .PLLDATO1(), .PLLDATO0(), .I2C1SCLO(i2c1_sclo), .I2C1SCLOEN(i2c1_scloen),
+        .I2C1SDAO(i2c1_sdao), .I2C1SDAOEN(i2c1_sdaoen), .I2C2SCLO(i2c2_sclo),
+        .I2C2SCLOEN(i2c2_scloen), .I2C2SDAO(i2c2_sdao), .I2C2SDAOEN(i2c2_sdaoen),
+        .I2C1IRQO(i2c1_irqo), .I2C2IRQO(i2c2_irqo), .SPISCKO(), .SPISCKEN(),
+        .SPIMISOO(), .SPIMISOEN(), .SPIMOSIO(), .SPIMOSIEN(), .SPIMCSN7(),
+        .SPIMCSN6(), .SPIMCSN5(), .SPIMCSN4(), .SPIMCSN3(), .SPIMCSN2(),
+        .SPIMCSN1(), .SPIMCSN0(), .SPICSNEN(), .SPIIRQO(), .TCINT(), .TCOC(),
+        .WBCUFMIRQ(), .CFGWAKE(), .CFGSTDBY());
+
+
+
+    // exemplar begin
+    // exemplar end
+
+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..6d8d8c8
--- /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)