Latest version.

Signed-off-by: Tim 'mithro' Ansell <tansell@google.com>
diff --git a/spec/bram_init.py b/spec/bram_init.py
new file mode 100644
index 0000000..897311b
--- /dev/null
+++ b/spec/bram_init.py
@@ -0,0 +1,66 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import numpy as np
+
+bram_init = [] # ypos -> init; initp
+
+for y in range(0, 24):
+	bram_init.append((
+		np.random.randint(0, 2, size=16384),
+		np.random.randint(0, 2, size=2048),
+	))
+
+with open(sys.argv[1] + "/braminit/bram.v", "w") as f:
+	print("module top;", file=f)
+	for y in range(0, 24):
+		print("(* KEEP, DONT_TOUCH, LOC=\"RAMB18_X1Y%d\" *)" % y, file=f)
+		print("RAMB18E2 #(", file=f)
+		for i in range(0, 64):
+			print("    .INIT_%02X(256'b%s)," % (i, "".join([str(x) for x in reversed(bram_init[y][0][ i*256 : (i+1)*256 ])])), file=f)
+		for i in range(0, 8):
+			print("    .INITP_%02X(256'b%s)," % (i, "".join([str(x) for x in reversed(bram_init[y][1][ i*256 : (i+1)*256 ])])), file=f)
+		print("    .DOA_REG(1'b0)", file=f) # just here for simpler commas
+		print(") bram_%d ();" % y, file=f)
+	print("endmodule", file=f)
+
+with open(sys.argv[1] + "/braminit/bram.txt", "w") as f:
+	for y in range(0, 24):
+		site_y = y % 2
+		tile_y = y // 2
+		startbit = tile_y * 240
+		if tile_y >= 6:
+			startbit += 96
+		for frame in range(0, 256):
+			print("%d %d %d %s %s" %
+				(
+					frame,
+					startbit,
+					site_y,
+					"".join(str(x) for x in bram_init[y][0][ frame*64 : (frame+1)*64 ]),
+					"".join(str(x) for x in bram_init[y][1][ frame*8 : (frame+1)*8 ]),
+				), file=f)
+with open(sys.argv[1] + "/braminit/build.tcl", "w") as f:
+	print("add_files %s" % (sys.argv[1] + "/braminit/bram.v"), file=f)
+	print("synth_design -top top -part xczu7ev-ffvc1156-2-e", file=f)
+	print("# set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets]", file=f)
+	print("opt_design", file=f)
+	print("place_design", file=f)
+	print("route_design", file=f)
+	print("set_property SEVERITY {Warning} [get_drc_checks]", file=f)
+	print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+	print("write_checkpoint -force bram.dcp", file=f)
+	print("write_edif -force bram.edf", file=f)
+	print("write_bitstream -force bram.bit", file=f)
diff --git a/spec/bufce_fsr_tap.tcl b/spec/bufce_fsr_tap.tcl
new file mode 100644
index 0000000..e1ec1d9
--- /dev/null
+++ b/spec/bufce_fsr_tap.tcl
@@ -0,0 +1,81 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+proc random_site {object type} {
+	while {1} {
+		set sites [get_sites -of_objects $object -filter "SITE_TYPE == $type" ]
+		if {[llength $sites] == 0} {
+			return {}
+		}
+		set site [lindex $sites [expr {int(rand()*[llength $sites])}]]
+		if {[llength [get_cells -of_objects $site]] == 0} {
+			break
+		}
+	}
+	return $site
+}
+
+set run $::argv
+create_project -force -part xczu7ev-ffvf1517-2-e design${run} design${run}
+
+
+link_design
+create_cell -reference LUT6 "misc_lut"
+place_design
+route_design
+
+remove_cell drv* ff*
+remove_net clk*
+
+foreach tile [get_tiles -filter {TILE_TYPE == CMT_L}] {
+	if {[expr rand()] > 0.6} {
+		continue
+	}
+	set drv_site [random_site $tile "BUFGCE_DIV"]
+	create_cell -reference BUFGCE_DIV "driver_$tile"
+	set drv_bel "$drv_site/BUFGCE_DIV"
+	place_cell  "driver_$tile" $drv_bel
+	create_net "clk_dly_fuzz_$tile"
+	set clk [get_nets "clk_dly_fuzz_$tile"]
+	connect_net -net $clk -objects [get_pins "driver_$tile/O"]
+	create_clock $clk -period 10
+	foreach region [get_clock_regions] {
+		set ssite [random_site $region "SLICEL"]
+		if {$ssite == {}} {
+			continue
+		}
+		create_cell -reference FDRE "sink_$ssite"
+		place_cell "sink_$ssite" "$ssite/AFF"
+		connect_net -net $clk -objects [get_pins "sink_$ssite/C"]
+	}
+	set success 0
+	while {$success == 0} {
+		set regions [get_clock_regions]
+		set region [lindex $regions [expr {int(rand()*[llength $regions])}]]
+		set_property USER_CLOCK_ROOT $region $clk
+		if {[catch {update_clock_routing}]} {
+			continue
+		}
+		set success 1
+	}
+}
+
+route_design
+
+set_property SEVERITY {Warning} [get_drc_checks]
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+write_checkpoint -force ../specimen_clk/bufce_tap_a${run}.dcp
+write_edif -force ../specimen_clk/bufce_tap_a${run}.edf
+write_bitstream -force ../specimen_clk/bufce_tap_a${run}.bit
+
diff --git a/spec/bufce_leaf_tap.tcl b/spec/bufce_leaf_tap.tcl
new file mode 100644
index 0000000..16fef86
--- /dev/null
+++ b/spec/bufce_leaf_tap.tcl
@@ -0,0 +1,60 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+remove_cell ff* dlylut* driver*
+remove_net clk* loop*
+
+set drv_site [get_sites "BUFGCE_DIV_X0Y16"]
+create_cell -reference BUFGCE_DIV "driver_0"
+set drv_bel "$drv_site/BUFGCE_DIV"
+place_cell  "driver_0" $drv_bel
+create_net "clk_fuzz"
+set clk [get_nets "clk_fuzz"]
+connect_net -net $clk -objects [get_pins "driver_0/O"]
+
+create_clock $clk -period 2.7
+
+for {set x 0} {$x < 112} {incr x 4} {
+	set site [get_sites "SLICE_X${x}Y260"]
+	set luts [get_bels -of_objects $site "*LUT"]
+	create_cell -reference FDRE "ff_launch_${site}"
+	place_cell "ff_launch_${site}" "${site}/AFF"
+	connect_net -net $clk -objects [get_pins "ff_launch_${site}/C"]
+	create_net "loop_init_${site}"
+	set q [get_nets "loop_init_${site}"]
+	connect_net -net $q -objects [get_pins "ff_launch_${site}/Q"]
+	set depth [expr {int(10 + 6*rand())}]
+	for {set i 0} {$i < $depth} {incr i} {
+		create_cell -reference LUT1 "dlylut_${i}_${site}"
+		set_property INIT 1 [get_cells "dlylut_${i}_${site}"]
+		place_cell "dlylut_${i}_${site}" [lindex $luts $i]
+		connect_net -net $q -objects [get_pins "dlylut_${i}_${site}/I0"]
+		create_net "loop_${i}_${site}"
+		set q [get_nets "loop_${i}_${site}"]
+		connect_net -net $q -objects [get_pins "dlylut_${i}_${site}/O"]
+	}
+
+	set cx [expr {$x+2}]
+	set csite [get_sites "SLICE_X${cx}Y260"]
+	create_cell -reference FDRE "ff_capture_${site}"
+	place_cell "ff_capture_${site}" "${csite}/HFF"
+	connect_net -net $clk -objects [get_pins "ff_capture_${site}/C"]
+	connect_net -net $q -objects [get_pins "ff_capture_${site}/D"]
+}
+
+route_design
+
+set_property SEVERITY {Warning} [get_drc_checks]
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+
diff --git a/spec/clkroute.py b/spec/clkroute.py
new file mode 100644
index 0000000..a9107a6
--- /dev/null
+++ b/spec/clkroute.py
@@ -0,0 +1,44 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+root = sys.argv[1]
+
+X = 8
+
+with open(root + "/rclkroute_leafdly/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	print("vivado -mode batch -nolog -nojournal -source ../../ultra/spec/delayfuzz.tcl -tclargs $1", file=f)
+	print("if [ $? -eq 0 ]; then", file=f)
+	print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/rclkroute_leafdly$1.bit %s/frames.txt > %s/specimen_clk/rclkroute_leafdly$1.dump" % (root, root, root), file=f)
+	print("    CLOCK_ONLY=1 python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_clk/rclkroute_leafdly$1.dump > %s/specimen_clk/rclkroute_leafdly$1.tbits" % (root, root, root), file=f)
+	#print("    rm %s/specimen_clk/rclkroute$1.bit" % (root, ), file=f)
+	#print("    rm %s/specimen_clk/rclkroute$1.dump" % (root, ), file=f)
+	print("else", file=f)
+	print("   rm %s/specimen_clk/rclkroute_leafdly$1.dump" % (root, ), file=f)
+	print("   rm %s/specimen_clk/rclkroute_leafdly$1.tbits" % (root, ), file=f)
+	print("   rm %s/specimen_clk/rclkroute_leafdly$1.dcp" % (root, ), file=f)
+	print("   rm %s/specimen_clk/rclkroute_leafdly$1.bit" % (root, ), file=f)
+	print("   rm %s/specimen_clk/rclkroute_leafdly$1.features" % (root, ), file=f)
+	print("fi", file=f)
+with open(root + "/rclkroute_leafdly/Makefile", "w") as f:
+	print("all: %s" % " ".join(["%d.done" % i for i in range(X)]), file=f)
+	print("", file=f)
+	print("%.done: ", file=f)
+	print("\tbash run.sh $*", file=f)
+	print("\ttouch $@", file=f)
+	print("", file=f)
+	print("clean: ", file=f)
+	print("\trm -f *.done", file=f)
diff --git a/spec/clkroute_io.py b/spec/clkroute_io.py
new file mode 100644
index 0000000..1151814
--- /dev/null
+++ b/spec/clkroute_io.py
@@ -0,0 +1,44 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+root = sys.argv[1]
+
+X = 40
+
+with open(root + "/xclkroute/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	print("vivado -mode batch -nolog -nojournal -source ../../ultra/spec/xiphy_clk_mux.tcl -tclargs $1", file=f)
+	print("if [ $? -eq 0 ]; then", file=f)
+	print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/xclkroute_a$1.bit %s/frames.txt > %s/specimen_clk/xclkroute_a$1.dump" % (root, root, root), file=f)
+	print("    CLOCK_ONLY=1 python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_clk/xclkroute_a$1.dump > %s/specimen_clk/xclkroute_a$1.tbits" % (root, root, root), file=f)
+	#print("    rm %s/specimen_clk/rclkroute$1.bit" % (root, ), file=f)
+	#print("    rm %s/specimen_clk/rclkroute$1.dump" % (root, ), file=f)
+	print("else", file=f)
+	print("   rm %s/specimen_clk/xclkroute_a$1.dump" % (root, ), file=f)
+	print("   rm %s/specimen_clk/xclkroute_a$1.tbits" % (root, ), file=f)
+	print("   rm %s/specimen_clk/xclkroute_a$1.dcp" % (root, ), file=f)
+	print("   rm %s/specimen_clk/xclkroute_a$1.bit" % (root, ), file=f)
+	print("   rm %s/specimen_clk/xclkroute_a$1.features" % (root, ), file=f)
+	print("fi", file=f)
+with open(root + "/xclkroute/Makefile", "w") as f:
+	print("all: %s" % " ".join(["%d.done" % i for i in range(X)]), file=f)
+	print("", file=f)
+	print("%.done: ", file=f)
+	print("\tbash run.sh $*", file=f)
+	print("\ttouch $@", file=f)
+	print("", file=f)
+	print("clean: ", file=f)
+	print("\trm -f *.done", file=f)
diff --git a/spec/clkroute_tap.py b/spec/clkroute_tap.py
new file mode 100644
index 0000000..60ae37e
--- /dev/null
+++ b/spec/clkroute_tap.py
@@ -0,0 +1,44 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+root = sys.argv[1]
+
+X = 20
+
+with open(root + "/fsrtap/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	print("vivado -mode batch -nolog -nojournal -source ../../ultra/spec/bufce_fsr_tap.tcl -tclargs $1", file=f)
+	print("if [ $? -eq 0 ]; then", file=f)
+	print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/bufce_tap_a$1.bit %s/frames.txt > %s/specimen_clk/bufce_tap_a$1.dump" % (root, root, root), file=f)
+	print("    CLOCK_ONLY=1 python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_clk/bufce_tap_a$1.dump > %s/specimen_clk/bufce_tap_a$1.tbits" % (root, root, root), file=f)
+	#print("    rm %s/specimen_clk/rclkroute$1.bit" % (root, ), file=f)
+	#print("    rm %s/specimen_clk/rclkroute$1.dump" % (root, ), file=f)
+	print("else", file=f)
+	print("   rm %s/specimen_clk/bufce_tap_a$1.dump" % (root, ), file=f)
+	print("   rm %s/specimen_clk/bufce_tap_a$1.tbits" % (root, ), file=f)
+	print("   rm %s/specimen_clk/bufce_tap_a$1.dcp" % (root, ), file=f)
+	print("   rm %s/specimen_clk/bufce_tap_a$1.bit" % (root, ), file=f)
+	print("   rm %s/specimen_clk/bufce_tap_a$1.features" % (root, ), file=f)
+	print("fi", file=f)
+with open(root + "/fsrtap/Makefile", "w") as f:
+	print("all: %s" % " ".join(["%d.done" % i for i in range(X)]), file=f)
+	print("", file=f)
+	print("%.done: ", file=f)
+	print("\tbash run.sh $*", file=f)
+	print("\ttouch $@", file=f)
+	print("", file=f)
+	print("clean: ", file=f)
+	print("\trm -f *.done", file=f)
diff --git a/spec/delayfuzz.tcl b/spec/delayfuzz.tcl
new file mode 100644
index 0000000..82cd975
--- /dev/null
+++ b/spec/delayfuzz.tcl
@@ -0,0 +1,281 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+proc make_driver_cell {name netname celltype loc pinname} {
+    create_net $netname
+    set net [get_nets $netname]
+    create_cell -reference $celltype "${name}_drv"
+    place_cell "${name}_drv" $loc
+    connect_net -net $net -objects [get_pins "${name}_drv/${pinname}"]
+    return [list [get_cells "${name}_drv"] $net]
+}
+
+proc make_user_cell {name celltype loc pinname net} {
+    create_cell -reference $celltype "${name}_usr"
+    place_cell "${name}_usr" $loc
+    connect_net -net $net -objects [get_pins "${name}_usr/${pinname}"]
+    return [list [get_cells "${name}_usr"] $net]
+}
+
+
+proc make_site_pin_driver {pin netname} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_ROW" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname $netname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_ROW_FSR" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname $netname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname $netname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname $netname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_OUT"} {
+        set result [make_driver_cell $objname $netname "BUFGCE_DIV" "$site/BUFGCE_DIV" "O"]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "O"]
+    } else {
+        return {}
+    }
+}
+
+proc make_site_pin_user {pin net} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK1"} {
+        return [make_user_cell $objname "FDRE" "$site/AFF" "C" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK2"} {
+        return [make_user_cell $objname "FDRE" "$site/EFF" "C" $net]
+    } else {
+        return {}
+    }
+}
+
+proc string_insert {string index insertString} {
+    # Convert end-relative and TIP 176 indexes to simple integers.
+    if {[regexp -expanded {
+        ^(end(?![\t\n\v\f\r ])      # "end" is never followed by whitespace
+        |[\t\n\v\f\r ]*[+-]?\d+)    # m, with optional leading whitespace
+        (?:([+-])                   # op, omitted when index is "end"
+        ([+-]?\d+))?                # n, omitted when index is "end"
+        [\t\n\v\f\r ]*$             # optional whitespace (unless "end")
+    } $index _ m op n]} {
+        # Convert first index to an integer.
+        switch $m {
+            end     {set index [string length $string]}
+            default {scan $m %d index}
+        }
+
+        # Add or subtract second index, if provided.
+        switch $op {
+            + {set index [expr {$index + $n}]}
+            - {set index [expr {$index - $n}]}
+        }
+    } elseif {![string is integer -strict $index]} {
+        # Reject invalid indexes.
+        return -code error "bad index \"$index\": must be\
+                integer?\[+-\]integer? or end?\[+-\]integer?"
+    }
+
+    # Concatenate the pre-insert, insertion, and post-insert strings.
+    set a [string range $string 0 [expr {$index - 1}]]
+    set b [string range $string $index end]
+    return "${a}${insertString}${b}"
+}
+
+
+proc route_via {net sink_pin extra_node pip} {
+    set success 0
+    set src [lindex [get_nodes -of_objects [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]] 0]
+    set sink [get_nodes -of_objects [get_site_pins -filter {DIRECTION == IN} -of_objects $net ]]
+    if {$sink == {}} {
+        set sink [get_nodes -of_objects $sink_pin]
+    }
+    set_property IS_ROUTE_FIXED 0 $net
+    #route_design -unroute -nets $net
+
+    set from_node [get_nodes -uphill -of_objects $pip]
+    set to_node [get_nodes -downhill -of_objects $pip]
+    if {$src == $from_node} {
+        set route_a $from_node
+    } else {
+        if {[catch {set route_a [find_routing_path -max_nodes 12 -from $src -to $from_node]}]} {
+            return 0
+        }
+        if {$route_a == ""} {
+            return 0
+        }
+    }
+    if {$sink == $to_node} {
+        set route_b $to_node
+    } else {
+        if {[catch {set route_b [find_routing_path -max_nodes 8  -from $to_node -to $sink]}]} {
+            return 0
+        }
+        if {$route_b == ""} {
+            return 0
+        }
+    }
+
+    set route_b [string_insert $route_b [string first " " $route_b] " $extra_node "]
+
+    if {[catch {set_property FIXED_ROUTE "$route_a $route_b" $net}]} {
+        return 0
+    }
+    set_property IS_ROUTE_FIXED 1 $net
+    if {[get_property ROUTE_STATUS $net] != "ROUTED"} {
+        set_property IS_ROUTE_FIXED 0 $net
+        route_design -unroute -nets $net
+        return 0
+    }
+    return 1
+}
+
+# https://wiki.tcl-lang.org/page/Shuffle+a+list
+proc shuffle6 { list } {
+     set n [llength $list]
+     for { set i 1 } { $i < $n } { incr i } {
+         set j [expr { int( rand() * $n ) }]
+         set temp [lindex $list $i]
+         lset list $i [lindex $list $j]
+         lset list $j $temp
+     }
+     return $list
+ }
+
+set tile_types [list "RCLK_INT_L"  "RCLK_INT_R"]
+
+set run $::argv
+create_project -force -part xczu7ev-ffvf1517-2-e design${run} design${run}
+
+
+link_design
+create_cell -reference LUT6 "misc_lut"
+place_design
+route_design
+
+#remove_cell [get_cells]
+#remove_net [get_nets]
+
+set count [expr {int(120 + rand()*80)}]
+set delay_values [list 0 1 2 4 8]
+
+for {set i 0} {$i < $count} {incr i} {
+    puts "$i/$count"
+    for {set j 0} {$j < 20} {incr j} {
+        set tt [lindex $tile_types [expr {int(rand()*[llength $tile_types])}]]
+        set tiles [get_tiles -filter "TILE_TYPE == $tt"]
+        set tile [lindex $tiles [expr {int(rand()*[llength $tiles])}]]
+        set tile_pips [get_pips -of_objects $tile -filter {NAME !~ "*VCC_WIRE*"} "*CLK_IN*CLK_LEAF"]
+        puts $tile_pips
+        set pip [lindex $tile_pips [expr {int(rand()*[llength $tile_pips])}]]
+        if {[string first "CE_INT" $pip] != -1} {
+        	continue
+        }
+        puts $pip
+        set wavefront_bwd $pip
+        set driver {}
+        set dly [lindex $delay_values [expr {int(rand()*[llength $delay_values])}]]
+        set netname "leaf_delay_${dly}_${i}"
+        for {set k 0} {$k < 12} {incr k} {
+            set wavefront_bwd [get_nodes -uphill -of_objects $wavefront_bwd]
+
+            set wavefront_pins [get_site_pins -of_objects $wavefront_bwd]
+            foreach pin $wavefront_pins {
+                set driver [make_site_pin_driver $pin $netname]
+                if {$driver != {}} {
+                    break
+                }
+            }
+            
+            if {$driver != {}} {
+                break
+            }
+            if {[llength $wavefront_bwd] > 20000} {
+                break
+            }
+        }
+
+        puts $driver
+
+        if {$driver == {}} {
+            continue
+        }
+
+        set drv_cell [lindex $driver 0]
+        set net [lindex $driver 1]
+
+        set wavefront_fwd $pip
+        set user {}
+        for {set k 0} {$k < 8} {incr k} {
+            set wavefront_fwd [get_nodes -downhill -of_objects $wavefront_fwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.5} {
+                set wavefront_pins [get_site_pins -of_objects $wavefront_fwd]
+                foreach pin $wavefront_pins {
+                    set user [make_site_pin_user $pin $net]
+                    set sink_pin $pin
+                    if {$user != {}} {
+                        break
+                    }
+                }
+            }
+            if {$user != {}} {
+                break
+            }
+            if {[llength $wavefront_fwd] > 20000} {
+                break
+            }
+        }
+
+        if {$user == {}} {
+            remove_cell $drv_cell
+            remove_net $net
+            continue
+        }
+
+        set success [route_via $net $sink_pin "NoTile/Delay_Arc_Index=${dly}" $pip]
+        if { $success == 0 } {
+            remove_cell $drv_cell
+            remove_cell [lindex $user 0]
+            remove_net $net
+        } else {
+            break
+        }
+    }
+}
+
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+set_property SEVERITY {Warning} [get_drc_checks]
+write_checkpoint -force ../specimen_clk/rclkroute_leafdly${run}.dcp
+write_edif -force ../specimen_clk/rclkroute_leafdly${run}.edf
+write_bitstream -force ../specimen_clk/rclkroute_leafdly${run}.bit
+
diff --git a/spec/diff_io.py b/spec/diff_io.py
new file mode 100644
index 0000000..a0116bf
--- /dev/null
+++ b/spec/diff_io.py
@@ -0,0 +1,276 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+X = 40
+
+root = sys.argv[1]
+
+pins = []
+bank_to_pins = {}
+bank_to_iotype = {}
+site_to_pin = {}
+pin_to_bank = {}
+with open (root + "/iopins.txt", "r") as iof:
+	for line in iof:
+		sl = line.strip().split(",")
+		if len(sl) < 5:
+			continue
+		pin = sl[0]
+		bank = int(sl[1])
+		func = sl[2]
+		site_name = sl[3]
+		site_type = sl[4]
+		pins.append((pin, bank, func, site_name, site_type))
+		if bank not in bank_to_pins:
+			bank_to_pins[bank] = []
+		bank_to_pins[bank].append(pin)
+		pin_to_bank[pin] = bank
+		if bank not in bank_to_iotype:
+			bank_to_iotype[bank] = site_type.split("_")[0]
+		site_to_pin[site_name] = pin
+
+for x in range(X):
+	used_pins = []
+	io_config = []
+
+	bank_to_vcc = {}
+	bank_pod_used = set()
+	bank_pod_not_used = set()
+	lut_inputs = []
+	lut_outputs = []
+	bank_iref = {}
+	bound_pins = set()
+	def inp():
+		sig = "li[%d]" % len(lut_inputs)
+		lut_inputs.append(sig)
+		return sig
+	def outp():
+		sig = "lo[%d]" % len(lut_outputs)
+		lut_outputs.append(sig)
+		return sig
+	def maybe_inp():
+		return inp() if np.random.choice([True, False]) else ""
+	def maybe_outp():
+		return outp() if np.random.choice([True, False]) else ""
+
+	for b, t in sorted(bank_to_iotype.items()):
+		if t == "HPIOB":
+			bank_to_vcc[b] = np.random.choice(
+				["1.0", "1.2", "1.35", "1.5", "1.8"]
+			)
+		elif t == "HDIOB":
+			bank_to_vcc[b] = np.random.choice(
+				["1.2", "1.5", "1.8", "2.5", "3.3"]
+			)
+		else:
+			assert False, t
+	standards = {
+		("HPIOB", "1.8"): ["DIFF_SSTL18_I", "DIFF_SSTL18_I_DCI", "SLVS_400_18", "LVDS"],
+		("HPIOB", "1.5"): ["DIFF_SSTL15", "DIFF_SSTL15_DCI"],
+		("HPIOB", "1.35"): ["DIFF_SSTL135", "DIFF_SSTL135_DCI"],
+		("HPIOB", "1.2"): ["DIFF_SSTL12", "DIFF_SSTL12_DCI",
+					"DIFF_HSUL_12", "DIFF_HSUL_12_DCI",
+					"DIFF_POD12", "DIFF_POD12_DCI"],
+		("HPIOB", "1.0"): ["DIFF_POD10", "DIFF_POD10_DCI"],
+	}
+
+	prims = {
+		"HPIOB": ["IBUFDS", "OBUFDS", "OBUFTDS", "IOBUFDS", "IOBUFDSE3"],
+		#"HDIOB": ["IBUF","OBUF", "OBUFT", "IOBUF"]
+	}
+	for (pin, bank, func, sn, st) in pins:
+		if "VREF" in func:
+			continue # conflict
+		if "VRP" in func or "VRN" in func:
+			continue # conflict
+		if "HPIOB_M" not in st:
+			continue
+		if np.random.choice([True, True, False]):
+			continue # improved fuzzing
+		params = {}
+		iot = st.split("_")[0]
+		assert (iot, bank_to_vcc[bank]) in standards, (pin, bank, iot, bank_to_vcc[bank])
+		ios = np.random.choice(standards[iot, bank_to_vcc[bank]])
+		if bank in bank_pod_used and ios in ("DIFF_HSTL_I_12", "DIFF_HSTL_I_DCI_12", "DIFF_SSTL12", "DIFF_SSTL12_DCI", "DIFF_HSUL_12", "DIFF_HSUL_12_DCI"):
+			continue
+		if bank in bank_pod_not_used and ios in ("DIFF_POD12", "DIFF_POD12_DCI"):
+			continue
+		params["IOSTANDARD"] = ios
+		prim = np.random.choice(prims[iot])
+
+		if ios == "SLVS_400_18":
+			prim = "IBUFDS"
+
+		params["prim"] = prim
+		if prim in ("OBUFDS", "OBUFTDS", "IOBUFDS", "IOBUFDSE3"):
+			if iot == "HPIOB":
+				params["SLEW"] = np.random.choice(["SLOW", "MEDIUM", "FAST"])
+			else:
+				params["SLEW"] = np.random.choice(["SLOW", "FAST"])
+			if iot == "HPIOB" and ("POD" in ios or "SSTL" in ios):
+				params["OUTPUT_IMPEDANCE"] = np.random.choice(["RDRV_40_40", "RDRV_48_48", "RDRV_60_60"])
+		if prim in ("IBUFDS", "IOBUFDS", "IOBUFDSE3", "IBUFDS_IBUFDISABLE", "IBUFDS_INTERMDISABLE"):
+			if "POD" in ios:
+				odt_choices = []
+				if "DCI" not in ios:
+					odt_choices.append("RTT_NONE")
+				if "OUTPUT_IMPEDANCE" not in params or params["OUTPUT_IMPEDANCE"] != "RDRV_48_48":
+					odt_choices += ["RTT_40", "RTT_60"]
+				else:
+					odt_choices += ["RTT_48"]
+				params["ODT"] = np.random.choice(odt_choices)
+				if "POD12" in ios:
+					params["EQUALIZATION"] = np.random.choice(["EQ_LEVEL0", "EQ_LEVEL1", "EQ_LEVEL2", "EQ_LEVEL3", "EQ_LEVEL4", "EQ_NONE"])
+			if "LVDS" in ios or "SLVS" in ios:
+				params["DIFF_TERM_ADV"] = np.random.choice(["TERM_NONE", "TERM_100"])
+		used_pins.append(pin)
+		io_config.append(params)
+
+		if ios in ("DIFF_POD12", "DIFF_POD12_DCI"):
+			bank_pod_used.add(bank)
+		if ios in ("DIFF_HSTL_I_12", "DIFF_HSTL_I_DCI_12", "DIFF_SSTL12", "DIFF_SSTL12_DCI", "DIFF_HSUL_12", "DIFF_HSUL_12_DCI"):
+			bank_pod_not_used.add(bank)
+	with open(root + "/diffio/diffio%d.v" % x, "w") as f:
+		print("module top(", file=f);
+		for i, params in enumerate(io_config):
+			prim = params["prim"]
+			bank = pin_to_bank[used_pins[i]]
+			if bank in bank_iref and bank_iref[bank] is not None:
+				params["int_vref"] = bank_iref[bank]
+			print ("(* %s *)" % ", ".join('X_%s="%s"' % (k, v) for k, v in sorted(params.items())), file=f)
+			if prim in ("IBUFDS", "IBUFDS_IBUFDISABLE"):
+				print("input p%d, pn%d%s" % (i, i, "," if i < len(io_config)-1 else ""), file=f)
+			elif prim in ("OBUFDS", "OBUFTDS"):
+				print("output p%d, pn%d%s" % (i, i, "," if i < len(io_config)-1 else ""), file=f)
+			elif prim in ("IOBUFDS", "IOBUFDSE3"):
+				print("inout p%d, pn%d%s" % (i, i, "," if i < len(io_config)-1 else ""), file=f)
+		print(");", file=f)
+		for i, params in enumerate(io_config):
+			print("(* KEEP, DONT_TOUCH *)", file=f)
+			prim = params["prim"]
+			if prim == "IBUFDS":
+				print("""
+					IBUFDS buf_{i} (
+						.I(p{i}), .IB(pn{i}),
+						.O({sig_o})
+					);
+				""".format(i=i, sig_o=maybe_inp()), file=f)
+			elif prim == "OBUFDS":
+				print("""
+					OBUFDS buf_{i} (
+						.O(p{i}), .OB(pn{i}),
+						.I({sig_i})
+					);
+				""".format(i=i, sig_i=maybe_outp()), file=f)
+			elif prim == "OBUFTDS":
+				print("""
+					OBUFTDS buf_{i} (
+						.O(p{i}), .OB(pn{i}),
+						.T({sig_t}),
+						.I({sig_i})
+					);
+				""".format(i=i, sig_t=outp(), sig_i=maybe_outp()), file=f)
+			elif prim == "IOBUFDS":
+				print("""
+					IOBUFDS buf_{i} (
+						.IO(p{i}), .IOB(pn{i}),
+						.T({sig_t}),
+						.I({sig_i}),
+						.O({sig_o})
+					);
+				""".format(i=i, sig_t=maybe_outp(), sig_i=maybe_outp(), sig_o=maybe_inp()), file=f)
+			elif prim == "IOBUFDSE3":
+				if np.random.choice([True, False]):
+					print("(* KEEP, DONT_TOUCH *)", file=f)
+				print("""
+					IOBUFDSE3 buf_{i} (
+						.IO(p{i}), .IOB(pn{i}),
+						.T({sig_t}),
+						.I({sig_i}),
+						.O({sig_o}),
+						.DCITERMDISABLE({dci_dis}),
+						.IBUFDISABLE({ibuf_dis})
+					);
+				""".format(i=i, sig_t=maybe_outp(), sig_i=maybe_outp(), sig_o=maybe_inp(),
+					dci_dis=maybe_outp(), ibuf_dis=maybe_outp()), file=f)		
+			print("", file=f)
+		print("wire [%d:0] li;" % (len(lut_inputs)-1), file=f)
+		print("wire [%d:0] lo;" % (len(lut_outputs)-1), file=f)
+
+		for i in range(max(len(lut_inputs)//6 + 1, len(lut_outputs))):
+			ip = ["1'b0" if (i * 6 + j) >= len(lut_inputs) else "li[%d]" % (i * 6 + j) for j in range(6)]
+			op = "lo[%d]" % i if i < len(lut_outputs) else ""
+			print("""
+				(* KEEP, DONT_TOUCH *)
+				LUT6 lut{i} (.I0({i0}), .I1({i1}), .I2({i2}), .I3({i3}), .I4({i4}), .I5({i5}), .O({o}));
+				""".format(
+					i=i, i0=ip[0], i1=ip[1], i2=ip[2], i3=ip[3], i4=ip[4], i5=ip[5], o=op,
+				), file=f)
+		print("endmodule", file=f)
+	with open(root + "/diffio/diffio%d.xdc" % x, "w") as f:
+		for i, params in enumerate(io_config):
+			pin = used_pins[i]
+			print("set_property PACKAGE_PIN %s [get_ports {p%d}]" % (pin, i), file=f)
+			for k, v in sorted(params.items()):
+				if k.isupper():
+					print("set_property %s %s [get_ports {p%d}]" % (k, v, i), file=f)
+		for bank, iref in sorted(bank_iref.items()):
+			if iref is None:
+				continue
+			print("set_property INTERNAL_VREF {%s} [get_iobanks %d]" % (iref, bank), file=f)
+	with open(root + "/diffio/diffio%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/diffio/diffio%d.v" % x)), file=f)
+		print("add_files %s" % (root + ("/diffio/diffio%d.xdc" % x)), file=f)
+		print("synth_design -top top -part xczu7ev-ffvf1517-2-e", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks NSTD-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks UCIO-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks AVAL-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks REQP-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks BIVR-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks PLHDIO-1]", file=f)
+		print("opt_design", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_io/diffio%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_io/diffio%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_io/diffio%d.bit" % (root, x), file=f)
+with open(root + "/diffio/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	print("vivado -mode batch -nolog -nojournal -source diffio$1.tcl", file=f)
+	print("if [ $? -eq 0 ]; then", file=f)
+	print("    ../../ultra/tools/dump_bitstream %s/specimen_io/diffio$1.bit %s/frames.txt > %s/specimen_io/diffio$1.dump" % (root, root, root), file=f)
+	print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_io/diffio$1.dump > %s/specimen_io/diffio$1.tbits" % (root, root, root), file=f)
+	#print("    rm %s/specimen_io/diffio$1.bit" % (root, ), file=f)
+	#print("    rm %s/specimen_io/diffio$1.dump" % (root, ), file=f)
+	print("else", file=f)
+	print("   rm %s/specimen_io/diffio$1.dump" % (root, ), file=f)
+	print("   rm %s/specimen_io/diffio$1.tbits" % (root, ), file=f)
+	print("   rm %s/specimen_io/diffio$1.dcp" % (root, ), file=f)
+	print("   rm %s/specimen_io/diffio$1.bit" % (root, ), file=f)
+	print("   rm %s/specimen_io/diffio$1.features" % (root, ), file=f)
+	print("fi", file=f)
+with open(root + "/diffio/Makefile", "w") as f:
+	print("all: %s" % " ".join(["%d.done" % i for i in range(X)]), file=f)
+	print("", file=f)
+	print("%.done: ", file=f)
+	print("\tbash run.sh $*", file=f)
+	print("\ttouch $@", file=f)
+	print("", file=f)
+	print("clean: ", file=f)
+	print("\trm -f *.done", file=f)
\ No newline at end of file
diff --git a/spec/fuzz_dump.sh b/spec/fuzz_dump.sh
new file mode 100644
index 0000000..d8ee615
--- /dev/null
+++ b/spec/fuzz_dump.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -ex
+for bit in $1/specimen_clk/rclkroute*.bit; do
+	../tools/dump_bitstream $bit $1/frames.txt > ${bit%.bit}.dump
+	python3 ../tools/bits_to_tiles.py $1/tilebits.json ${bit%.bit}.dump > ${bit%.bit}.tbits
+done
diff --git a/spec/fuzzpins.tcl b/spec/fuzzpins.tcl
new file mode 100644
index 0000000..ee612fa
--- /dev/null
+++ b/spec/fuzzpins.tcl
@@ -0,0 +1,76 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+set gnd_pips [get_pips -of_objects [get_nets <const0>]]
+set vcc_pips [get_pips -of_objects [get_nets <const1>]]
+
+foreach site [get_sites -filter {SITE_TYPE == BITSLICE_RX_TX && IS_USED == FALSE} ] {
+	if { rand() > 0.2 } {
+		continue
+	}
+	if {[llength [get_nets -of_objects [get_nodes -of_objects [get_site_pins -of_objects $site]]]] > 0} {
+		continue
+	}
+	set gnd_route 0
+	foreach rtpip [get_pips -of_objects [get_nodes -of_objects [get_site_pins -of_objects $site]]]] {
+		if {[lsearch $gnd_pips $rtpip] != -1} {
+			set gnd_route 1
+		}
+		if {[lsearch $vcc_pips $rtpip] != -1} {
+			set gnd_route 1
+		}
+	}
+	if {$gnd_route == 1} {
+		continue
+	}
+	set sn [get_property NAME $site]
+	if { rand() > 0.5 } {
+		create_cell -reference IDELAYE3 "IDELAY_$sn"
+		if {[catch {place_cell  "IDELAY_$sn" "$sn/IDELAY"}]} {
+			remove_cell "IDELAY_$sn"
+			continue
+		}
+	} else {
+		create_cell -reference OSERDESE3 "OSERDES_$sn"
+		if {[catch {place_cell  "OSERDES_$sn" "$sn/OSERDES"}]} {
+			remove_cell "OSERDES_$sn"
+			continue
+		}
+	}
+	set_property MANUAL_ROUTING TRUE $site
+	set sitepips [get_property SITE_PIPS $site]
+	foreach sp [get_site_pips -of_objects $site] {
+		if { rand() > 0.2 } {
+			continue
+		}
+		if {[string match "*OPTINV*" $sp]} {
+			continue
+		}
+		regex {([A-Z0-9_]+)/([A-Z0-9_]+):([A-Z0-9_]+)} $sp match sp_site sp_bel sp_pin
+		if {[string first $sp_bel $sitepips] != -1} {
+			continue
+		}
+		puts $sp
+		append sitepips " " $sp_bel ":" $sp_pin
+	}
+	set_property SITE_PIPS $sitepips $site
+}
+set_property SEVERITY {Warning} [get_drc_checks AVAL-*]
+set_property SEVERITY {Warning} [get_drc_checks REQP-*]
+set_property SEVERITY {Warning} [get_drc_checks BIVR-*]
+set_property SEVERITY {Warning} [get_drc_checks BSCK-*]
+set_property SEVERITY {Warning} [get_drc_checks PLHDIO-1]
+set_property SEVERITY {Warning} [get_drc_checks PDRC-203]
+set_property SEVERITY {Warning} [get_drc_checks ADEF-911]
+
diff --git a/spec/gclk_casc.py b/spec/gclk_casc.py
new file mode 100644
index 0000000..d5d1aef
--- /dev/null
+++ b/spec/gclk_casc.py
@@ -0,0 +1,110 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+bufgctrls_by_tile = {}
+with open(sys.argv[1], "r") as tf:
+	for line in tf:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		for site in sl[4:]:
+			if ("BUFGCTRL" in site) and "HDIO" not in site:
+				if sl[2] not in bufgctrls_by_tile:
+					bufgctrls_by_tile[sl[2]] = []
+				bufgctrls_by_tile[sl[2]].append(site.split(":")[0])
+
+X = 10
+
+root = sys.argv[2]
+
+for x in range(X):
+	buffers = []
+
+	tiles = list(sorted(bufgctrls_by_tile.keys()))
+	np.random.shuffle(tiles)
+
+	def random_inversion(pins):
+		return ", ".join([".IS_%s_INVERTED(%d)" % (p, np.random.randint(2)) for p in pins])
+
+	def random_control(pins):
+		return ", ".join([".%s(aux[%d])" % (p, np.random.randint(10)) for p in pins])
+
+	with open(root + "/clkroute7/gclk_casc_%d.v" % x, "w") as f:
+		print("module top(input [9:0] aux, output o);", file=f)
+		
+		for tile in tiles:
+			print("", file=f)
+			if np.random.randint(0, 3) == 0:
+				continue
+			sorted_bufgs = list(sorted(bufgctrls_by_tile[tile], key=lambda x: int(x[x.rfind('Y')+1:])))
+			bufg_used = [np.random.choice([True, True, False]) for i in range(8)]
+			print("(* KEEP, DONT_TOUCH *) wire [7:0] %s_o;" % tile, file=f)
+			for i, loc in enumerate(sorted_bufgs):
+				if not bufg_used[i]:
+					continue
+				def get_inp(d):
+					if d == 0:
+						return ""
+					elif d == -1:
+						x = 7 if i == 0 else (i-1)
+					elif d == 1:
+						x = 0 if i == 7 else (i+1)
+					if bufg_used[x]:
+						return "%s_o[%d]" % (tile, x)
+					else:
+						return ""
+				i0 = get_inp(np.random.choice([-1, 0, +1]))
+				i1 = get_inp(np.random.choice([-1, 0, +1]))
+				print("    (* KEEP, DONT_TOUCH, LOC=\"%s\" *)" % loc, file=f)
+				print("    BUFGCTRL #(", file=f)
+				print("        %s," % random_inversion(["I0", "I1", "S0", "S1", "CE0", "CE1", "IGNORE0", "IGNORE1"]), file=f)
+				print("        .INIT_OUT(0), .PRESELECT_I0(\"TRUE\"), .PRESELECT_I1(\"FALSE\")", file=f)
+				print("    ) bufgctrl_%s_%d (" % (tile, i), file=f)
+				print("        .I0(%s), .I1(%s), " % (i0, i1), file=f)
+				print("        %s," % random_control(["S0", "S1", "CE0", "CE1", "IGNORE0", "IGNORE1"]), file=f)
+				print("        .O(%s_o[%d])" % (tile, i), file=f)
+				print("    );", file=f)
+		print("endmodule", file=f)
+	with open(root + "/clkroute7/gclk_casc_%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/clkroute7/gclk_casc_%d.v" % x)), file=f)
+		#print("read_xdc %s" % (root + ("/clkroute7/gclk_casc_%d.xdc" % x)), file=f)
+		print("synth_design -top top -part xczu7ev-ffvc1156-2-e", file=f)
+		print("# set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets]", file=f)
+		print("opt_design", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks NSTD-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks UCIO-1]", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_clk/gclk_casc_%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_clk/gclk_casc_%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_clk/gclk_casc_%d.bit" % (root, x), file=f)
+with open(root + "/clkroute7/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	#print("set -ex", file=f)
+	for x in range(X):
+		print("vivado -mode batch -nolog -nojournal -source gclk_casc_%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/gclk_casc_%d.bit %s/frames.txt > %s/specimen_clk/gclk_casc_%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_clk/gclk_casc_%d.dump > %s/specimen_clk/gclk_casc_%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_clk/gclk_casc_%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk_casc_%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk_casc_%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk_casc_%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk_casc_%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/gclk_pins.py b/spec/gclk_pins.py
new file mode 100644
index 0000000..117641e
--- /dev/null
+++ b/spec/gclk_pins.py
@@ -0,0 +1,174 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+bufgces_by_tile = {}
+tiles_by_xy = {}
+rclk_int_l = []
+slices_by_tile = {}
+with open(sys.argv[1], "r") as tf:
+	for line in tf:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		tiles_by_xy[int(sl[0]), int(sl[1])] = sl[2]
+		if sl[2].startswith("RCLK_INT_L"):
+			rclk_int_l.append((int(sl[0]), int(sl[1]), sl[2]))
+		for site in sl[4:]:
+			if ("BUFGCE" in site or "BUFGCTRL" in site) and "HDIO" not in site:
+				if sl[2] not in bufgces_by_tile:
+					bufgces_by_tile[sl[2]] = []
+				bufgces_by_tile[sl[2]].append(site.split(":"))
+			elif "SLICE_" in site:
+				slices_by_tile[int(sl[0]), int(sl[1])] = site.split(":")[0]
+
+halfcolumn_slices_by_row = {}
+for x, y, rclk in rclk_int_l:
+	hc_up = []
+	hc_down = []
+	if y not in halfcolumn_slices_by_row:
+		halfcolumn_slices_by_row[y] = []
+	for yplus in range(y+1, y+31):
+		if (x, yplus) not in tiles_by_xy:
+			continue
+		if not tiles_by_xy[x, yplus].startswith("INT_"):
+			break
+		slice_x = x + np.random.choice([+1, -1])
+		if (slice_x, yplus) not in slices_by_tile:
+			continue
+		hc_up.append(slices_by_tile[slice_x, yplus])
+	for yminus in range(y-1, y-31, -1):
+		if (x, yminus) not in tiles_by_xy:
+			continue
+		if not tiles_by_xy[x, yminus].startswith("INT_"):
+			break
+		slice_x = x + np.random.choice([+1, -1])
+		if (slice_x, yminus) not in slices_by_tile:
+			continue
+		hc_down.append(slices_by_tile[slice_x, yminus])
+	halfcolumn_slices_by_row[y].append(hc_up)
+	halfcolumn_slices_by_row[y].append(hc_down)
+
+X = 2
+
+root = sys.argv[2]
+
+for x in range(X):
+	buffers_by_type = {"BUFGCTRL": [], "BUFGCE": [], "BUFGCE_DIV": []}
+
+	tiles = list(sorted(bufgces_by_tile.keys()))
+	np.random.shuffle(tiles)
+
+	for tile in tiles:
+		shuffled_bufs = list(bufgces_by_tile[tile])
+		np.random.shuffle(shuffled_bufs)
+		#tile_buffers = 8
+		found_buffers = 0
+		for buf, buftype in shuffled_bufs:
+			buffers_by_type[buftype].append(buf)
+			print("%s %s" % (tile, buf))
+			found_buffers += 1
+	np.random.shuffle(buffers_by_type["BUFGCE"])
+	np.random.shuffle(buffers_by_type["BUFGCTRL"])
+	np.random.shuffle(buffers_by_type["BUFGCE_DIV"])
+	def random_inversion(pins):
+		return ", ".join([".IS_%s_INVERTED(%d)" % (p, np.random.randint(2)) for p in pins])
+
+	def random_control(pins):
+		return ", ".join([".%s(aux[%d])" % (p, np.random.randint(10)) for p in pins])
+
+	with open(root + "/clkroute6/gclkd_%d.v" % x, "w") as f:
+		print("module top(input [29:0] i, input [9:0] aux, input d, output o, q);", file=f)
+		N = 30
+		print("    wire [29:0] r;", file=f)
+		# print("    assign r[0] = i;", file=f)
+		# print("    assign o = r[%d];" % N, file=f)
+		# for i in range(N):
+		# 	bg, buftype = buffers[i]
+		# 	#print("(* LOC=\"%s\" *)" % bg, file=f)
+		# 	if "BUFGCTRL" in buftype:
+		# 		print("    BUFGCTRL #(", file=f)
+		# 		print("        %s," % random_inversion(["I0", "I1", "S0", "S1", "CE0", "CE1", "IGNORE0", "IGNORE1"]), file=f)
+		# 		print("        .INIT_OUT(%d), .PRESELECT_I0(\"%s\"), .PRESELECT_I1(\"%s\")" %
+		# 				(np.random.randint(2), np.random.choice(["TRUE", "FALSE"]), np.random.choice(["TRUE", "FALSE"])), file=f)
+		# 		print("    ) bufgctrl_%d (" % i, file=f)
+		# 		print("        .I0(r[%d]), .I1(r[%d]), " % (i, np.random.randint(i+1)), file=f)
+		# 		print("        %s," % random_control(["S0", "S1", "CE0", "CE1", "IGNORE0", "IGNORE1"]), file=f)
+		# 		print("        .O(r[%d])" % (i+1), file=f)
+		# 		print("    );", file=f)
+		for i in range(15):
+			print("    (* LOC=\"%s\", KEEP, DONT_TOUCH *)" % buffers_by_type["BUFGCE_DIV"].pop(), file=f)
+			print("    BUFGCE_DIV #(", file=f)
+			print("        .BUFGCE_DIVIDE(%d)," % np.random.randint(1, 9), file=f)
+			print("        %s" % random_inversion(["I", "CE", "CLR"]), file=f)
+			print("    ) bufgce_div_%d (" % i, file=f)
+			if np.random.choice([True, False]):
+				print("        .I(i[%d])," % i, file=f)
+			print("        %s," % random_control(["CE", "CLR"]), file=f)
+			print("        .O(r[%d])" % (i), file=f)
+			print("    );", file=f)
+		for i in range(15):
+			print("    (* LOC=\"%s\", KEEP, DONT_TOUCH *)" % buffers_by_type["BUFGCTRL"].pop(), file=f)
+			print("    BUFGCTRL", file=f)
+			print("    bufgctrl_%d (" % i, file=f)
+			if np.random.choice([True, False]):
+				print("        .I0(i[%d])," % (i + 15), file=f)
+			elif np.random.choice([True, False]):
+				print("        .I1(i[%d])," % (i + 15), file=f)
+			print("        %s," % random_control(["S0", "S1", "CE0", "CE1", "IGNORE0", "IGNORE1"]), file=f)
+			print("        .O(r[%d])" % (i+15), file=f)
+			print("    );", file=f)
+		# for i in range(8):
+		# 	print("    (* LOC=\"%s\" *)" % buffers_by_type["BUFGCE"].pop(), file=f)
+		# 	print("    BUFGCE #(", file=f)
+		# 	print("        .CE_TYPE(\"%s\")," % np.random.choice(["SYNC", "ASYNC"]), file=f)
+		# 	print("        %s" % random_inversion(["I", "CE"]), file=f)
+		# 	print("    ) bufgce_%d (" % i, file=f)
+		# 	print("        .I(i[%d])," % (i+16), file=f)
+		# 	print("        %s," % random_control(["CE"]), file=f)
+		# 	print("        .O(r[%d])" % (i+16), file=f)
+		# 	print("    );", file=f)
+
+		R2=0
+		NS=64
+		ffs=""
+		for i in range(30):
+			ffs += "FDCE ff_%d (.C(r[%d]), .CE(aux[%d]), .CLR(~aux[%d]), .D(r2[%d]), .Q(r2[%d]));\n" % (i, i, np.random.randint(10), np.random.randint(10), R2, R2+1)
+			R2 += 1
+		print("    wire [%d:0] r2;" % R2, file=f)
+		print("    assign r2[0] = d;", file=f)
+		print("    assign q = r2[%d];" % R2, file=f)
+		print(ffs, file=f)
+		print("endmodule", file=f)
+	with open(root + "/clkroute6/gclkd_%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/clkroute6/gclkd_%d.v" % x)), file=f)
+		#print("read_xdc %s" % (root + ("/clkroute6/gclkd_%d.xdc" % x)), file=f)
+		print("synth_design -top top -part xczu7ev-ffvf1517-2-e", file=f)
+		print("set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets]", file=f)
+		print("opt_design", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks NSTD-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks UCIO-1]", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/ioexp/gclkd_%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/ioexp/gclkd_%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/ioexp/gclkd_%d.bit" % (root, x), file=f)
+with open(root + "/clkroute6/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	#print("set -ex", file=f)
+	for x in range(X):
+		print("vivado -mode batch -nolog -nojournal -source gclkd_%d.tcl" % x, file=f)
diff --git a/spec/io.py b/spec/io.py
new file mode 100644
index 0000000..4153cfa
--- /dev/null
+++ b/spec/io.py
@@ -0,0 +1,313 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+X = 110
+
+root = sys.argv[1]
+
+pins = []
+bank_to_pins = {}
+bank_to_iotype = {}
+site_to_pin = {}
+pin_to_bank = {}
+with open (root + "/iopins.txt", "r") as iof:
+	for line in iof:
+		sl = line.strip().split(",")
+		if len(sl) < 5:
+			continue
+		pin = sl[0]
+		bank = int(sl[1])
+		func = sl[2]
+		site_name = sl[3]
+		site_type = sl[4]
+		pins.append((pin, bank, func, site_name, site_type))
+		if bank not in bank_to_pins:
+			bank_to_pins[bank] = []
+		bank_to_pins[bank].append(pin)
+		pin_to_bank[pin] = bank
+		if bank not in bank_to_iotype:
+			bank_to_iotype[bank] = site_type.split("_")[0]
+		site_to_pin[site_name] = pin
+
+for x in range(X):
+	used_pins = []
+	io_config = []
+
+	bank_to_vcc = {}
+	bank_pod_used = set()
+	bank_pod_not_used = set()
+	lut_inputs = []
+	lut_outputs = []
+	bank_iref = {}
+	bound_pins = set()
+	def inp():
+		sig = "li[%d]" % len(lut_inputs)
+		lut_inputs.append(sig)
+		return sig
+	def outp():
+		sig = "lo[%d]" % len(lut_outputs)
+		lut_outputs.append(sig)
+		return sig
+	def maybe_inp():
+		return inp() if np.random.choice([True, False]) else ""
+	def maybe_outp():
+		return outp() if np.random.choice([True, False]) else ""
+
+	for b, t in sorted(bank_to_iotype.items()):
+		if t == "HPIOB":
+			bank_to_vcc[b] = np.random.choice(
+				["1.0", "1.2", "1.35", "1.5", "1.8"]
+			)
+		elif t == "HDIOB":
+			bank_to_vcc[b] = np.random.choice(
+				["1.2", "1.5", "1.8", "2.5", "3.3"]
+			)
+		else:
+			assert False, t
+	standards = {
+		("HPIOB", "1.8"): ["LVCMOS18", "LVDCI_18", "HSLVDCI_18",
+					"HSTL_I_18", "HSTL_I_DCI_18",
+					"SSTL18_I", "SSTL18_I_DCI"],
+		("HPIOB", "1.5"): ["LVCMOS15", "LVDCI_15", "HSLVDCI_15",
+					"HSTL_I", "HSTL_I_DCI",
+					"SSTL15", "SSTL15_DCI"],
+		("HPIOB", "1.35"): ["SSTL135", "SSTL135_DCI"],
+		("HPIOB", "1.2"): ["LVCMOS12", "HSTL_I_12", "HSTL_I_DCI_12",
+					"SSTL12", "SSTL12_DCI",
+					"HSUL_12", "HSUL_12_DCI",
+					"POD12", "POD12_DCI"],
+		("HPIOB", "1.0"): ["POD10", "POD10_DCI"],
+
+		("HDIOB", "3.3"): ["LVCMOS33", "LVTTL"],
+		("HDIOB", "2.5"): ["LVCMOS25"],
+		("HDIOB", "1.8"): ["LVCMOS18"],
+		("HDIOB", "1.5"): ["LVCMOS15"],
+		("HDIOB", "1.2"): ["LVCMOS12"]
+	}
+
+	prims = {
+		"HPIOB": ["IBUF", "OBUF", "OBUFT", "IOBUF", "IOBUFE3", "IBUF_IBUFDISABLE"],
+		"HDIOB": ["IBUF","OBUF", "OBUFT", "IOBUF"]
+	}
+
+	drives = {
+		("HPIOB", "LVCMOS18"): ["2", "4", "6", "8", "12"],
+		("HPIOB", "LVCMOS15"): ["2", "4", "6", "8", "12"],
+		("HPIOB", "LVCMOS12"): ["2", "4", "6", "8"],
+		("HDIOB", "LVCMOS33"): ["4", "8", "12", "16"],
+		("HDIOB", "LVCMOS25"): ["4", "8", "12", "16"],
+		("HDIOB", "LVCMOS18"): ["4", "8", "12", "16"],
+		("HDIOB", "LVCMOS15"): ["4", "8", "12", "16"],
+		("HDIOB", "LVCMOS12"): ["4", "8", "12"],
+	}
+
+	for (pin, bank, func, sn, st) in pins:
+		if "VREF" in func:
+			continue # conflict
+		if "VRP" in func or "VRN" in func:
+			continue # conflict
+		if np.random.randint(1, 3) == 1:
+			continue # improved fuzzing
+		params = {}
+		iot = st.split("_")[0]
+		assert (iot, bank_to_vcc[bank]) in standards, (pin, bank, iot, bank_to_vcc[bank])
+		ios = np.random.choice(standards[iot, bank_to_vcc[bank]])
+		if bank in bank_pod_used and ios in ("HSTL_I_12", "HSTL_I_DCI_12", "SSTL12", "SSTL12_DCI", "HSUL_12", "HSUL_12_DCI"):
+			ios = "LVCMOS12"
+		if bank in bank_pod_not_used and ios in ("POD12", "POD12_DCI"):
+			ios = "LVCMOS12"
+		params["IOSTANDARD"] = ios
+		prim = np.random.choice(prims[iot])
+		params["prim"] = prim
+		if prim in ("IBUF", "IOBUF", "IOBUFE3", "IBUF_IBUFDISABLE", "IBUF_INTERMDISABLE"):
+			params["PULLTYPE"] = np.random.choice(["NONE", "PULLUP", "PULLDOWN", "KEEPER"])
+		if prim in ("OBUF", "OBUFT", "IOBUF", "IOBUFE3"):
+			if (iot, ios) in drives:
+				params["DRIVE"] = np.random.choice(drives[iot, ios])
+			if iot == "HPIOB":
+				params["SLEW"] = np.random.choice(["SLOW", "MEDIUM", "FAST"])
+			else:
+				params["SLEW"] = np.random.choice(["SLOW", "FAST"])
+			if iot == "HPIOB" and ("POD" in ios or "SSTL" in ios):
+				params["OUTPUT_IMPEDANCE"] = np.random.choice(["RDRV_40_40", "RDRV_48_48", "RDRV_60_60"])
+		if prim in ("IBUF", "IOBUF", "IOBUFE3", "IBUF_IBUFDISABLE", "IBUF_INTERMDISABLE"):
+			if "POD" in ios:
+				odt_choices = []
+				if "DCI" not in ios:
+					odt_choices.append("RTT_NONE")
+				if "OUTPUT_IMPEDANCE" not in params or params["OUTPUT_IMPEDANCE"] != "RDRV_48_48":
+					odt_choices += ["RTT_40", "RTT_60"]
+				else:
+					odt_choices += ["RTT_48"]
+				params["ODT"] = np.random.choice(odt_choices)
+				if "POD12" in ios:
+					params["EQUALIZATION"] = np.random.choice(["EQ_LEVEL0", "EQ_LEVEL1", "EQ_LEVEL2", "EQ_LEVEL3", "EQ_LEVEL4", "EQ_NONE"])
+		used_pins.append(pin)
+		io_config.append(params)
+		if iot == "HDIOB":
+			if ios in ( "HSTL_I_18", "SSTL18_I", "SSTL18_II"):
+				bank_iref[bank] = "0.90"
+			elif ios in ( "HSTL_I", "SSTL15", "SSTL15_II"):
+				bank_iref[bank] = "0.75"
+			elif ios in ("SSTL135", "SSTL135_II"):
+				bank_iref[bank] = "0.675"
+			elif ios == "SSTL12":
+				bank_iref[bank] = "0.60"
+		if ios in ("POD12", "POD12_DCI"):
+			bank_pod_used.add(bank)
+			bank_iref[bank] = np.random.choice([None, "0.84"])
+		if ios in ("HSTL_I_12", "HSTL_I_DCI_12", "SSTL12", "SSTL12_DCI", "HSUL_12", "HSUL_12_DCI"):
+			bank_pod_not_used.add(bank)
+			bank_iref[bank] = np.random.choice([None, "0.60"])
+	with open(root + "/io/io%d.v" % x, "w") as f:
+		print("module top(", file=f);
+		for i, params in enumerate(io_config):
+			prim = params["prim"]
+			bank = pin_to_bank[used_pins[i]]
+			if bank in bank_iref and bank_iref[bank] is not None:
+				params["internal_vref"] = bank_iref[bank]
+			print ("(* %s *)" % ", ".join('X_%s="%s"' % (k, v) for k, v in sorted(params.items())), file=f)
+			if prim in ("IBUF", "IBUF_IBUFDISABLE"):
+				print("input p%d%s" % (i, "," if i < len(io_config)-1 else ""), file=f)
+			elif prim in ("OBUF", "OBUFT"):
+				print("output p%d%s" % (i, "," if i < len(io_config)-1 else ""), file=f)
+			elif prim in ("IOBUF", "IOBUFE3"):
+				print("inout p%d%s" % (i, "," if i < len(io_config)-1 else ""), file=f)
+		print(");", file=f)
+		for i, params in enumerate(io_config):
+			print("(* KEEP, DONT_TOUCH *)", file=f)
+			prim = params["prim"]
+			if prim == "IBUF":
+				print("""
+					IBUF buf_{i} (
+						.I(p{i}),
+						.O({sig_o})
+					);
+				""".format(i=i, sig_o=maybe_inp()), file=f)
+			elif prim == "OBUF":
+				print("""
+					OBUF buf_{i} (
+						.O(p{i}),
+						.I({sig_i})
+					);
+				""".format(i=i, sig_i=maybe_outp()), file=f)
+			elif prim == "OBUFT":
+				print("""
+					OBUFT buf_{i} (
+						.O(p{i}),
+						.T({sig_t}),
+						.I({sig_i})
+					);
+				""".format(i=i, sig_t=outp(), sig_i=maybe_outp()), file=f)
+			elif prim == "IOBUF":
+				print("""
+					IOBUF buf_{i} (
+						.IO(p{i}),
+						.T({sig_t}),
+						.I({sig_i}),
+						.O({sig_o})
+					);
+				""".format(i=i, sig_t=maybe_outp(), sig_i=maybe_outp(), sig_o=maybe_inp()), file=f)
+			elif prim == "IBUF_IBUFDISABLE":
+				print("""
+					IBUF_IBUFDISABLE buf_{i} (
+						.I(p{i}),
+						.O({sig_o}),
+						.IBUFDISABLE({ibuf_dis})
+					);
+				""".format(i=i, sig_o=maybe_inp(), ibuf_dis=maybe_outp()), file=f)
+			elif prim == "IOBUFE3":
+				if np.random.choice([True, False]):
+					print("(* KEEP, DONT_TOUCH *)", file=f)
+				print("""
+					IOBUFE3 buf_{i} (
+						.IO(p{i}),
+						.T({sig_t}),
+						.I({sig_i}),
+						.O({sig_o}),
+						.DCITERMDISABLE({dci_dis}),
+						.IBUFDISABLE({ibuf_dis})
+					);
+				""".format(i=i, sig_t=maybe_outp(), sig_i=maybe_outp(), sig_o=maybe_inp(),
+					dci_dis=maybe_outp(), ibuf_dis=maybe_outp()), file=f)		
+			print("", file=f)
+		print("wire [%d:0] li;" % (len(lut_inputs)-1), file=f)
+		print("wire [%d:0] lo;" % (len(lut_outputs)-1), file=f)
+
+		for i in range(max(len(lut_inputs)//6 + 1, len(lut_outputs))):
+			ip = ["1'b0" if (i * 6 + j) >= len(lut_inputs) else "li[%d]" % (i * 6 + j) for j in range(6)]
+			op = "lo[%d]" % i if i < len(lut_outputs) else ""
+			print("""
+				(* KEEP, DONT_TOUCH *)
+				LUT6 lut{i} (.I0({i0}), .I1({i1}), .I2({i2}), .I3({i3}), .I4({i4}), .I5({i5}), .O({o}));
+				""".format(
+					i=i, i0=ip[0], i1=ip[1], i2=ip[2], i3=ip[3], i4=ip[4], i5=ip[5], o=op,
+				), file=f)
+		print("endmodule", file=f)
+	with open(root + "/io/io%d.xdc" % x, "w") as f:
+		for i, params in enumerate(io_config):
+			pin = used_pins[i]
+			print("set_property PACKAGE_PIN %s [get_ports {p%d}]" % (pin, i), file=f)
+			for k, v in sorted(params.items()):
+				if k.isupper():
+					print("set_property %s %s [get_ports {p%d}]" % (k, v, i), file=f)
+		for bank, iref in sorted(bank_iref.items()):
+			if iref is None:
+				continue
+			print("set_property INTERNAL_VREF {%s} [get_iobanks %d]" % (iref, bank), file=f)
+	with open(root + "/io/io%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/io/io%d.v" % x)), file=f)
+		print("add_files %s" % (root + ("/io/io%d.xdc" % x)), file=f)
+		print("synth_design -top top -part xczu7ev-ffvf1517-2-e", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks NSTD-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks UCIO-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks AVAL-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks REQP-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks BIVR-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks PLHDIO-1]", file=f)
+		print("opt_design", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_io/io%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_io/io%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_io/io%d.bit" % (root, x), file=f)
+with open(root + "/io/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	print("vivado -mode batch -nolog -nojournal -source io$1.tcl", file=f)
+	print("if [ $? -eq 0 ]; then", file=f)
+	print("    ../../ultra/tools/dump_bitstream %s/specimen_io/io$1.bit %s/frames.txt > %s/specimen_io/io$1.dump" % (root, root, root), file=f)
+	print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_io/io$1.dump > %s/specimen_io/io$1.tbits" % (root, root, root), file=f)
+	#print("    rm %s/specimen_io/io$1.bit" % (root, ), file=f)
+	#print("    rm %s/specimen_io/io$1.dump" % (root, ), file=f)
+	print("else", file=f)
+	print("   rm %s/specimen_io/io$1.dump" % (root, ), file=f)
+	print("   rm %s/specimen_io/io$1.tbits" % (root, ), file=f)
+	print("   rm %s/specimen_io/io$1.dcp" % (root, ), file=f)
+	print("   rm %s/specimen_io/io$1.bit" % (root, ), file=f)
+	print("   rm %s/specimen_io/io$1.features" % (root, ), file=f)
+	print("fi", file=f)
+with open(root + "/io/Makefile", "w") as f:
+	print("all: %s" % " ".join(["%d.done" % i for i in range(X)]), file=f)
+	print("", file=f)
+	print("%.done: ", file=f)
+	print("\tbash run.sh $*", file=f)
+	print("\ttouch $@", file=f)
+	print("", file=f)
+	print("clean: ", file=f)
+	print("\trm -f *.done", file=f)
\ No newline at end of file
diff --git a/spec/iologic.py b/spec/iologic.py
new file mode 100644
index 0000000..2053a43
--- /dev/null
+++ b/spec/iologic.py
@@ -0,0 +1,455 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+X = 100
+
+root = sys.argv[1]
+
+pins = []
+bank_to_pins = {}
+bank_to_iotype = {}
+site_to_pin = {}
+pin_to_iol = {}
+
+with open (root + "/iologic.txt", "r") as iof:
+	for line in iof:
+		sl = line.strip().split(",")
+		if len(sl) < 5:
+			continue
+		pin = sl[0]
+		bank = int(sl[1])
+		func = sl[2]
+		site_name = sl[3]
+		site_type = sl[4]
+		iol_site = sl[5]
+		pins.append((pin, bank, func, site_name, site_type, iol_site))
+		if bank not in bank_to_pins:
+			bank_to_pins[bank] = []
+		bank_to_pins[bank].append(pin)
+		if bank not in bank_to_iotype:
+			bank_to_iotype[bank] = site_type.split("_")[0]
+		site_to_pin[site_name] = pin
+
+sr_sig = {
+	"FDRE": "R", "FDSE": "S", "FDPE": "PRE", "FDCE": "CLR",
+}
+
+def make_ioff(name, sig_c, sig_d, sig_r, sig_e, sig_q, f):
+	fftype = np.random.choice(["FDRE", "FDSE", "FDPE", "FDCE"])
+	print("""
+		{t} #(.INIT({init})) {n} (
+			.C({c}), .{sr}({r}), .CE({e}), .D({d}), .Q({q})
+		);
+		""".format(
+			t=fftype,
+			init=np.random.randint(0, 2),
+			n=name,
+			sr=sr_sig[fftype],
+			c=sig_c, r=sig_r, e=sig_e, d=sig_d, q=sig_q
+		), file=f)
+
+def make_iddr(name, allow_inv, sig_c, sig_cb, sig_r, sig_d, sig_q1, sig_q2, f):
+	print("""
+		IDDRE1 #(.DDR_CLK_EDGE("{e}"), .IS_C_INVERTED({ci}), .IS_CB_INVERTED({cbi})) {n} (
+			.C({c}), .CB({cb}), .R({r}), .D({d}), .Q1({q1}), .Q2({q2})
+		);
+		""".format(
+			e=np.random.choice(["OPPOSITE_EDGE", "SAME_EDGE", "SAME_EDGE_PIPELINED"]),
+			ci=np.random.randint(0, 2) if allow_inv else 0,
+			cbi=np.random.randint(0, 2) if allow_inv else 1,
+			n=name,
+			c=sig_c, cb=sig_cb, r=sig_r, d=sig_d, q1=sig_q1, q2=sig_q2
+		), file=f)
+
+def make_oddr(name, srval, sig_c, c_inv, sig_d1, sig_d2, sig_sr, sig_q, f):
+	print("""
+		ODDRE1 #(.IS_C_INVERTED({ci}), .SRVAL({srv})) {n} (
+			.C({c}), .D1({d1}), .D2({d2}), .SR({sr}), .Q({q})
+		);
+		""".format(
+			ci=1 if c_inv else 0,
+			srv=srval,
+			n=name,
+			c=sig_c, d1=sig_d1, d2=sig_d2, sr=sig_sr, q=sig_q
+		), file=f)
+def make_iserdes(name, sig_clk, sig_clkdiv, sig_clk_b, sig_d, sig_rst, sig_q, f):
+	print("""
+		ISERDESE3 #(
+			.DATA_WIDTH({dw}),
+			.IS_CLK_B_INVERTED({cbi}),
+			.IS_CLK_INVERTED({ci}),
+			.IS_RST_INVERTED({ri})
+		) {n} (
+			.Q({q}),
+			.CLK({clk}), .CLKDIV({clkdiv}), .CLK_B({clk_b}), .D({d}), .RST({rst})
+		);
+	""".format(
+		dw=np.random.choice([4, 8]),
+		cbi=np.random.randint(0, 2),
+		ci=np.random.randint(0, 2),
+		ri=np.random.randint(0, 2),
+		n=name,
+		q=sig_q,
+		clk=sig_clk, clkdiv=sig_clkdiv, clk_b=sig_clk_b, rst=sig_rst, d=sig_d
+	), file=f)
+def make_oserdes(name, sig_clk, sig_clkdiv, sig_d, sig_rst, sig_t, sig_oq, sig_t_out, f):
+	print("""
+		OSERDESE3 #(
+			.DATA_WIDTH({dw}),
+			.INIT({init}),
+			.IS_CLKDIV_INVERTED({cdi}),
+			.IS_CLK_INVERTED({ci}),
+			.IS_RST_INVERTED({ri}),
+			.OSERDES_D_BYPASS("{db}"),
+			.OSERDES_T_BYPASS("{dt}")
+		) {n} (
+			.OQ({oq}), .T_OUT({t_out}),
+			.CLK({clk}), .CLKDIV({clkdiv}), .D({d}), .RST({rst}), .T({t})
+		);
+	""".format(
+		dw=np.random.choice([4, 8]),
+		init=np.random.randint(0, 2),
+		cdi=np.random.randint(0, 2),
+		ci=np.random.randint(0, 2),
+		ri=np.random.randint(0, 2),
+		db=np.random.choice(["TRUE", "FALSE"]),
+		dt=np.random.choice(["TRUE", "FALSE"]),
+		n=name,
+		oq=sig_oq, t_out=sig_t_out,
+		clk=sig_clk, clkdiv=sig_clkdiv, d=sig_d, rst=sig_rst, t=sig_t,
+	), file=f)
+
+def make_idelay(name, del_fmt, time_dly, sig_clk, sig_ce, sig_inc, sig_load, sig_rst, sig_en_vtc, sig_datain, sig_idatain, sig_dataout, f):
+	print("""
+		IDELAYE3 #(
+			.DELAY_SRC("{ds}"),
+			.DELAY_TYPE("{dt}"),
+			.DELAY_VALUE({dv}),
+			.DELAY_FORMAT("{df}"),
+			.UPDATE_MODE("{um}"),
+			.IS_CLK_INVERTED({ci}),
+			.IS_RST_INVERTED({ri}),
+			.LOOPBACK("{lb}"),
+			.REFCLK_FREQUENCY(300.0)
+		) {n} (
+			.CLK({clk}), .CE({ce}), .INC({inc}), .LOAD({load}), .RST({rst}), .EN_VTC({en_vtc}),
+			.DATAIN({datain}), .IDATAIN({idatain}), .DATAOUT({dataout})
+		);
+	""".format(
+		ds=np.random.choice(["DATAIN", "IDATAIN"]),
+		dt=np.random.choice(["FIXED", "VAR_LOAD", "VARIABLE"]),
+		dv=np.random.randint(0, 512) if del_fmt == "COUNT" else time_dly,
+		df=del_fmt,
+		um=np.random.choice(["ASYNC", "SYNC", "MANUAL"]),
+		ci=np.random.randint(0, 2),
+		ri=np.random.randint(0, 2),
+		lb=np.random.choice(["FALSE", "TRUE"]),
+		n=name,
+		clk=sig_clk, ce=sig_ce, inc=sig_inc, load=sig_load, rst=sig_rst, en_vtc=sig_en_vtc,
+		datain=sig_datain, idatain=sig_idatain, dataout=sig_dataout
+	), file=f)
+
+def make_odelay(name, del_fmt, time_dly, sig_clk, sig_ce, sig_inc, sig_load, sig_rst, sig_en_vtc, sig_odatain, sig_dataout, f):
+	print("""
+		ODELAYE3 #(
+			.DELAY_TYPE("{dt}"),
+			.DELAY_VALUE({dv}),
+			.DELAY_FORMAT("{df}"),
+			.UPDATE_MODE("{um}"),
+			.IS_CLK_INVERTED({ci}),
+			.IS_RST_INVERTED({ri}),
+			.REFCLK_FREQUENCY(300.0)
+		) {n} (
+			.CLK({clk}), .CE({ce}), .INC({inc}), .LOAD({load}), .RST({rst}), .EN_VTC({en_vtc}),
+			.ODATAIN({odatain}), .DATAOUT({dataout})
+		);
+	""".format(
+		dt=np.random.choice(["FIXED", "VAR_LOAD", "VARIABLE"]),
+		dv=np.random.randint(0, 512) if del_fmt == "COUNT" else time_dly,
+		df=del_fmt,
+		um=np.random.choice(["ASYNC", "SYNC", "MANUAL"]),
+		ci=np.random.randint(0, 2),
+		ri=np.random.randint(0, 2),
+		n=name,
+		clk=sig_clk, ce=sig_ce, inc=sig_inc, load=sig_load, rst=sig_rst, en_vtc=sig_en_vtc,
+		odatain=sig_odatain, dataout=sig_dataout
+	), file=f)
+
+for x in range(X):
+	used_pins = []
+	io_config = []
+
+	lut_inputs = []
+	lut_outputs = []
+	bank_iref = {}
+	def inp():
+		sig = "li[%d]" % len(lut_inputs)
+		lut_inputs.append(sig)
+		return sig
+	def outp():
+		sig = "lo[%d]" % len(lut_outputs)
+		lut_outputs.append(sig)
+		return sig
+	def maybe_inp():
+		return inp() if np.random.choice([True, False]) else ""
+	def maybe_outp():
+		return outp() if np.random.choice([True, False]) else ""
+	def clock():
+		return ("gclk[%d]" % np.random.randint(0, 6))
+	def maybe_outp_z():
+		return outp() if np.random.choice([True, False]) else "null"
+	def maybe(x):
+		return x if np.random.choice([True, False]) else ""
+	io_config = []
+	used_pins = []
+	for i in range(len(pins)):
+		if "XIPHY" in pins[i][5]:
+			prim = np.random.choice([None, None, None, None, None, "IBUF", "OBUF", "OBUFT", "IOBUF"])
+		else:
+			prim = np.random.choice([None, None, None, None, None, "IBUF", "OBUF", "OBUFT", "IOBUF", "IOBUFE3"])
+		if prim is not None:
+			io_config.append(prim)
+			used_pins.append(pins[i])
+	with open(root + "/iologic/iologic%d.v" % x, "w") as f:
+		print("module top(", file=f);
+		for i, prim in enumerate(io_config):
+			if prim == "IBUF":
+				print("input p%d%s" % (i, "," if i < len(io_config)-1 else ""), file=f)
+			elif prim in ("OBUF", "OBUFT"):
+				print("output p%d%s" % (i, "," if i < len(io_config)-1 else ""), file=f)
+			elif prim in ("IOBUF", "IOBUFE3"):
+				print("inout p%d%s" % (i, "," if i < len(io_config)-1 else ""), file=f)
+		print(");", file=f)
+		print("wire null;", file=f)
+		print("wire [5:0] gclk;", file=f)
+		print("BUFG bufg_i[5:0] (.I({%s, %s, %s, %s, %s, %s}), .O(gclk));" % tuple(outp() for i in range(6)), file=f)
+		del_groups = set()
+		for i, prim in enumerate(io_config):
+			print("wire p{i}i, p{i}i_d, p{i}o, p{i}o_d, p{i}t;".format(i=i), file=f)
+			print("(* KEEP, DONT_TOUCH *)", file=f)
+			if prim == "IBUF":
+				print("""
+					IBUF buf_{i} (
+						.I(p{i}),
+						.O(p{i}o)
+					);
+				""".format(i=i), file=f)
+			elif prim == "OBUF":
+				print("""
+					OBUF buf_{i} (
+						.O(p{i}),
+						.I(p{i}i_d)
+					);
+				""".format(i=i), file=f)
+			elif prim == "OBUFT":
+				print("""
+					OBUFT buf_{i} (
+						.O(p{i}),
+						.T(p{i}t),
+						.I(p{i}i_d)
+					);
+				""".format(i=i), file=f)
+			elif prim == "IOBUF":
+				print("""
+					IOBUF buf_{i} (
+						.IO(p{i}),
+						.T(p{i}t),
+						.I(p{i}i_d),
+						.O(p{i}o)
+					);
+				""".format(i=i), file=f)
+			elif prim == "IOBUFE3":
+				print("""
+					IOBUFE3 buf_{i} (
+						.IO(p{i}),
+						.T(p{i}t),
+						.I(p{i}i_d),
+						.O(p{i}o),
+						.DCITERMDISABLE({dci_dis})
+					);
+				""".format(i=i, dci_dis=maybe_outp()), file=f)
+			iol_site = used_pins[i][5]
+			idelay_used = False
+			odelay_used = False
+			if "BITSLICE" in iol_site:
+				idelay_used = np.random.choice([False, True])
+				odelay_used = np.random.choice([False, True])
+				del_fmt = np.random.choice(["TIME", "COUNT"])
+				time_dly = int(6666.667/np.random.uniform(7, 666))
+				slow_clk = clock()
+				group = "B" + str(used_pins[i][1])
+				if idelay_used:
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"IDELAY\", IODELAY_GROUP=\"%s\" *)" % (iol_site, group), file=f)
+					make_idelay(name="idelay%d" % i, del_fmt=del_fmt, time_dly=time_dly, sig_clk=maybe(slow_clk), sig_ce=maybe_outp(),
+						sig_inc=maybe_outp(), sig_load=maybe_outp(), sig_rst=maybe_outp(), sig_en_vtc=maybe_outp(),
+						sig_datain=maybe_outp(), sig_idatain="p%do"%i, sig_dataout="p%do_d"%i, f=f)
+					del_groups.add(group)
+				if odelay_used:
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"ODELAY\", IODELAY_GROUP=\"%s\" *)" % (iol_site, group), file=f)
+					make_odelay(name="odelay%d" % i, del_fmt=del_fmt, time_dly=time_dly, sig_clk=maybe(slow_clk), sig_ce=maybe_outp(),
+						sig_inc=maybe_outp(), sig_load=maybe_outp(), sig_rst=maybe_outp(), sig_en_vtc=maybe_outp(),
+						sig_odatain="p%di"%i, sig_dataout="p%di_d"%i, f=f)
+					del_groups.add(group)
+				input_mode = np.random.choice(["NONE", "BYP", "FF", "DDR", "SERDES"])
+				if input_mode == "BYP":
+					print("assign %s = p%do%s;" % (inp(), i, "_d" if idelay_used else ""), file=f)
+				elif input_mode == "FF" and prim in ("IBUF", "IOBUF", "IOBUFE3"):
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"IN_FF\" *)" % iol_site, file=f)
+					make_ioff(name="iff%d" % i, sig_c=slow_clk, sig_d="p%do_d" % i if idelay_used else ("p%do" % i),
+						sig_r=maybe_outp(), sig_e=maybe_outp(), sig_q=maybe_inp(), f=f)
+				elif input_mode == "DDR" and prim in ("IBUF", "IOBUF", "IOBUFE3"):
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"ISERDES\" *)" % iol_site, file=f)
+					make_iddr(name="iddr%d" % i, allow_inv=True, sig_c=slow_clk, sig_cb=clock(),
+						sig_r=maybe_outp(), sig_d="p%do_d" % i if idelay_used else "p%do" % i,
+						sig_q1=maybe_inp(), sig_q2=maybe_inp(), f=f)
+				elif input_mode == "SERDES":
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"ISERDES\" *)" % iol_site, file=f)
+					make_iserdes(name="iserdes%d" % i, sig_clk=clock(), sig_clkdiv=slow_clk, sig_clk_b=clock(),
+						sig_d="p%do_d" % i if idelay_used else "p%do" % i, sig_rst=maybe_outp(),
+						sig_q="{%s}" % (", ".join([inp() for i in range(8)])), f=f)
+				output_mode = np.random.choice(["NONE", "BYP", "FF", "DDR", "SERDES"])
+				if output_mode == "SERDES":
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"OSERDES\" *)" % iol_site, file=f)
+					make_oserdes(name="oserdes%d" % i, sig_clk=clock(), sig_clkdiv=slow_clk, sig_d="{%s}" % (", ".join([maybe_outp_z() for i in range(8)])),
+						sig_rst=maybe_outp(), sig_t=maybe_outp(), sig_oq="p%di" % i, sig_t_out="p%dt" % i, f=f)
+				else:
+					if output_mode == "BYP" or odelay_used:
+						print("assign p%di = %s;" % (i, outp()), file=f)
+						print("assign p%dt = %s;" % (i, outp()), file=f)
+					elif output_mode == "FF" and prim in ("OBUF", "OBUFT", "IOBUF", "IOBUFE3"):
+						print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"OUT_FF\" *)" % iol_site, file=f)
+						make_ioff(name="off%d" % i, sig_c=slow_clk, sig_d=maybe_inp(),
+							sig_r=maybe_outp(), sig_e=maybe_outp(), sig_q="p%di" % i, f=f)
+					elif output_mode == "DDR":
+						print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"OSERDES\" *)" % iol_site, file=f)
+						make_oddr(name="oddr%d" % i, srval=np.random.randint(0, 2), sig_c=slow_clk, c_inv=np.random.choice([True, False]),
+							sig_d1=outp(), sig_d2=outp(), sig_sr=maybe_outp(), sig_q="p%di" % i, f=f)
+			elif "HDIO" in iol_site:
+				slow_clk = clock()
+				reset = maybe_outp()
+				enable = maybe_outp()
+				input_mode = np.random.choice(["NONE", "BYP", "FF", "DDR"])
+				if input_mode == "BYP":
+					print("assign %s = p%do;" % (inp(), i), file=f)
+				elif input_mode == "FF" and prim in ("IBUF", "IOBUF"):
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"IPFF\" *)" % iol_site, file=f)
+					make_ioff(name="iff%d" % i, sig_c=slow_clk, sig_d="p%do" % i,
+						sig_r=reset, sig_e=maybe_outp(), sig_q=maybe_inp(), f=f)
+				elif input_mode == "DDR":
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"IDDR\" *)" % iol_site, file=f)
+					make_iddr(name="iddr%d" % i, allow_inv=False, sig_c=slow_clk, sig_cb=clock(),
+						sig_r=reset, sig_d="p%do" % i,
+						sig_q1=maybe_inp(), sig_q2=maybe_inp(), f=f)
+				output_mode = np.random.choice(["NONE", "BYP", "FF", "DDR"])
+				tristate_mode = np.random.choice(["NONE", "BYP", "FF", "DDR"])
+				srval = np.random.randint(0, 2)
+				if output_mode == "BYP":
+					print("assign p%di = %s;" % (i, outp()), file=f)
+				elif output_mode == "FF" and prim in ("OBUF", "OBUFT", "IOBUF"):
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"OPFF\" *)" % iol_site, file=f)
+					make_ioff(name="off%d" % i, sig_c=slow_clk, sig_d=maybe_inp(),
+						sig_r=reset, sig_e=enable, sig_q="p%di" % i, f=f)
+				elif output_mode == "DDR" and prim in ("OBUF", "OBUFT", "IOBUF"):
+					#print("(* KEEP, DONT_TOUCH, LOC=\"%s\" *)" % iol_site, file=f)
+					make_oddr(name="oddr%d" % i, srval=srval, sig_c=slow_clk, c_inv=False,
+						sig_d1=maybe_outp(), sig_d2=maybe_outp(), sig_sr=reset, sig_q="p%di" % i, f=f)
+					tristate_mode = "DDR"
+				if tristate_mode == "BYP":
+					print("assign p%dt = %s;" % (i, outp()), file=f)
+				elif tristate_mode == "FF"  and prim in ("OBUFT", "IOBUF"):
+					print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"TFF\" *)" % iol_site, file=f)
+					make_ioff(name="tff%d" % i, sig_c=slow_clk, sig_d=maybe_inp(),
+						sig_r=reset, sig_e=enable, sig_q="p%dt" % i, f=f)
+				elif tristate_mode == "DDR" and prim in ("OBUFT", "IOBUF") and output_mode == "DDR":
+					#print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"OPTFF\" *)" % iol_site, file=f)
+					make_oddr(name="tddr%d" % i, srval=srval, sig_c=slow_clk, c_inv=False,
+						sig_d1=maybe_outp(), sig_d2=maybe_outp(), sig_sr=reset, sig_q="p%dt" % i, f=f)
+			if not odelay_used:	
+				print("assign p%di_d = p%di;" % (i, i), file=f)
+
+			print("", file=f)
+		for group in sorted(del_groups):
+			print("(* KEEP, DONT_TOUCH, IODELAY_GROUP=\"%s\" *)" % group, file=f)
+			print(" IDELAYCTRL ctrl_{n} (.REFCLK({c}), .RST({r}), .RDY({rd}));".format(
+					n=group, c=clock(), r=maybe_outp(), rd=maybe_inp(),
+				), file=f)
+		print("wire [%d:0] li;" % (len(lut_inputs)-1), file=f)
+		print("wire [%d:0] lo;" % (len(lut_outputs)-1), file=f)
+
+		for i in range(max(len(lut_inputs)//6 + 1, len(lut_outputs))):
+			ip = ["1'b0" if (i * 6 + j) >= len(lut_inputs) else "li[%d]" % (i * 6 + j) for j in range(6)]
+			op = "lo[%d]" % i if i < len(lut_outputs) else ""
+			print("""
+				(* KEEP, DONT_TOUCH *)
+				LUT6 lut{i} (.I0({i0}), .I1({i1}), .I2({i2}), .I3({i3}), .I4({i4}), .I5({i5}), .O({o}));
+				""".format(
+					i=i, i0=ip[0], i1=ip[1], i2=ip[2], i3=ip[3], i4=ip[4], i5=ip[5], o=op,
+				), file=f)
+		print("endmodule", file=f)
+	with open(root + "/iologic/iologic%d.xdc" % x, "w") as f:
+		for i, prim in enumerate(io_config):
+			pin = used_pins[i][0]
+			print("set_property PACKAGE_PIN %s [get_ports {p%d}]" % (pin, i), file=f)
+			print("set_property IOSTANDARD LVCMOS18 [get_ports {p%d}]" % (i), file=f)
+
+	with open(root + "/iologic/iologic%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/iologic/iologic%d.v" % x)), file=f)
+		print("add_files %s" % (root + ("/iologic/iologic%d.xdc" % x)), file=f)
+		print("synth_design -top top -part xczu7ev-ffvf1517-2-e", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks NSTD-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks UCIO-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks AVAL-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks REQP-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks BIVR-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks BSCK-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks PLHDIO-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks PDRC-203]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks ADEF-911]", file=f)
+		print("set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets]", file=f)
+		print("opt_design", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("source ../fuzzpins.tcl", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_io/iologic%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_io/iologic%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_io/iologic%d.bit" % (root, x), file=f)
+with open(root + "/iologic/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	print("vivado -mode batch -nolog -nojournal -source iologic$1.tcl", file=f)
+	print("if [ $? -eq 0 ]; then", file=f)
+	print("    ../../ultra/tools/dump_bitstream %s/specimen_io/iologic$1.bit %s/frames.txt > %s/specimen_io/iologic$1.dump" % (root, root, root), file=f)
+	print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_io/iologic$1.dump > %s/specimen_io/iologic$1.tbits" % (root, root, root), file=f)
+	#print("    rm %s/specimen_io/iologic$1.bit" % (root, ), file=f)
+	#print("    rm %s/specimen_io/iologic$1.dump" % (root, ), file=f)
+	print("else", file=f)
+	print("   rm %s/specimen_io/iologic$1.dump" % (root, ), file=f)
+	print("   rm %s/specimen_io/iologic$1.tbits" % (root, ), file=f)
+	print("   rm %s/specimen_io/iologic$1.dcp" % (root, ), file=f)
+	print("   rm %s/specimen_io/iologic$1.bit" % (root, ), file=f)
+	print("   rm %s/specimen_io/iologic$1.features" % (root, ), file=f)
+	print("fi", file=f)
+with open(root + "/iologic/Makefile", "w") as f:
+	print("all: %s" % " ".join(["%d.done" % i for i in range(X)]), file=f)
+	print("", file=f)
+	print("%.done: ", file=f)
+	print("\tbash run.sh $*", file=f)
+	print("\ttouch $@", file=f)
+	print("", file=f)
+	print("clean: ", file=f)
+	print("\trm -f *.done", file=f)
\ No newline at end of file
diff --git a/spec/laguna_ff.py b/spec/laguna_ff.py
new file mode 100644
index 0000000..aad9efe
--- /dev/null
+++ b/spec/laguna_ff.py
@@ -0,0 +1,137 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+top_slices_orig = []
+bot_slices_orig = []
+top_laguna_orig = []
+bot_laguna_orig = []
+
+with open(sys.argv[1], "r") as tf:
+	for line in tf:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		for site in sl[4:]:
+			if site.startswith("SLICE_"):
+				if int(sl[1]) > 310 and int(sl[1]) < 355:
+					bot_slices_orig.append(site.split(":")[0])
+				elif int(sl[1]) < 310 and int(sl[1]) >  275:
+					top_slices_orig.append(site.split(":")[0])
+			elif site.startswith("LAGUNA"):
+				if int(sl[1]) > 310 and int(sl[1]) < 355:
+					bot_laguna_orig.append(site.split(":")[0])
+				elif int(sl[1]) < 310 and int(sl[1]) >  249:
+					top_laguna_orig.append(site.split(":")[0])
+
+X = 10
+root = sys.argv[2]
+
+for x in range(X):
+	top_slices = list(top_slices_orig)
+	bot_slices = list(bot_slices_orig)
+	top_laguna = list(top_laguna_orig)
+	bot_laguna = list(bot_laguna_orig)
+
+	np.random.shuffle(top_slices)
+	np.random.shuffle(bot_slices)
+
+	np.random.shuffle(top_laguna)
+	np.random.shuffle(bot_laguna)
+
+	with open(root + "/lagff/lagff%d.v" % x, "w") as f:
+		print("module top;", file=f)
+		print("wire clk, sr, ce;", file=f)
+		print("(* KEEP, DONT_TOUCH *) LUT1 drv_clk (.O(clk));", file=f)
+		print("(* KEEP, DONT_TOUCH *) LUT1 drv_sr (.O(sr));", file=f)
+		print("(* KEEP, DONT_TOUCH *) LUT1 drv_ce (.O(ce));", file=f)
+		N = np.random.randint(1000, len(bot_laguna))
+		for i in range(N):
+			top = False
+			rx = np.random.choice([True, False])
+			print("wire d%d, q%d;" % (i, i), file=f)
+			if top:
+				lag = top_laguna.pop()
+			else:
+				lag = bot_laguna.pop()
+			if rx:
+				bel = "RX_REG%d" % (np.random.randint(0, 6))
+				if top:
+					sink_slice = top_slices.pop()
+					source_slice = bot_slices.pop()
+				else:
+					sink_slice = bot_slices.pop()
+					source_slice = top_slices.pop()
+			else:
+				bel = "TX_REG%d" % (np.random.randint(0, 6))
+				if top:
+					sink_slice = bot_slices.pop()
+					source_slice = top_slices.pop()
+				else:
+					sink_slice = top_slices.pop()
+					source_slice = bot_slices.pop()
+			if np.random.choice([True, False, False]):
+				# bypass
+				print("assign q%d = d%d;" % (i, i), file=f)
+			else:
+				prim, sr = np.random.choice(["FDRE_R", "FDSE_S", "FDPE_PRE", "FDCE_CLR"]).split("_")
+				print("(* KEEP, DONT_TOUCH, LOC=\"%s\", BEL=\"%s\" *)" % (lag, bel), file=f)
+				print("%s  #(" % prim, file=f)
+				print("   .INIT(%d)," % np.random.randint(2), file=f)
+				print("   .IS_C_INVERTED(%d)," % np.random.randint(2), file=f)
+				print("   .IS_%s_INVERTED(%d)" % (sr, np.random.randint(2)), file=f)
+				print(") ff_%d (" % i, file=f)
+				print("   .C(%s)," % np.random.choice(["clk", ""]), file=f)
+				if np.random.choice([True, False]):
+					print("   .CE(ce),", file=f)
+				if np.random.choice([True, False]):
+					print("   .%s(sr)," % sr, file=f)
+				print("   .D(d%d)," % i, file=f)
+				print("   .Q(q%d)" % i, file=f)
+				print(");", file=f)
+			if np.random.choice([True, True, False]) or rx:
+				print("(* KEEP, DONT_TOUCH, LOC=\"%s\" *) LUT1 drv%d(.O(d%d));" % (source_slice, i, i), file=f)
+			if np.random.choice([True, True, False]) or not rx:
+				print("(* KEEP, DONT_TOUCH, LOC=\"%s\" *) LUT1 usr%d(.I0(q%d));" % (sink_slice, i, i), file=f)
+		print("endmodule", file=f)
+
+	with open(root + "/lagff/lagff%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/lagff/lagff%d.v" % x)), file=f)
+		print("synth_design -top top -part xcvu5p-flvc2104-2-e", file=f)
+		print("opt_design", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks NSTD-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks UCIO-1]", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_lag/lagff%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_lag/lagff%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_lag/lagff%d.bit" % (root, x), file=f)
+with open(root + "/lagff/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	#print("set -ex", file=f)
+	for x in range(X):
+		print("vivado -mode batch -nolog -nojournal -source lagff%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_lag/lagff%d.bit %s/frames.txt > %s/specimen_lag/lagff%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_lag/lagff%d.dump > %s/specimen_lag/lagff%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_lag/lagff%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_lag/lagff%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_lag/lagff%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_lag/lagff%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_lag/lagff%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/laguna_route.py b/spec/laguna_route.py
new file mode 100644
index 0000000..ee8ca65
--- /dev/null
+++ b/spec/laguna_route.py
@@ -0,0 +1,46 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+top_slices = []
+bot_slices = []
+
+with open(sys.argv[1], "r") as tf:
+	for line in tf:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		for site in sl[4:]:
+			if site.startswith("SLICE_"):
+				if int(sl[1]) > 310 and int(sl[1]) < 355:
+					bot_slices.append(site.split(":")[0])
+				elif int(sl[1]) < 310 and int(sl[1]) >  275:
+					top_slices.append(site.split(":")[0])
+
+np.random.shuffle(top_slices)
+np.random.shuffle(bot_slices)
+
+with open(sys.argv[2], "w") as f:
+	print("module top;", file=f)
+	for i in range(3000):
+		print("wire w%d;\n" % i, file=f)
+		sl1 = top_slices.pop()
+		sl2 = bot_slices.pop()
+		if np.random.choice([True, False]):
+			sl1, sl2 = sl2, sl1
+		print("(* KEEP, DONT_TOUCH, LOC=\"%s\" *) LUT1 drv%d(.O(w%d));" % (sl1, i, i), file=f)
+		print("(* KEEP, DONT_TOUCH, LOC=\"%s\" *) LUT1 usr%d(.I0(w%d));" % (sl2, i, i), file=f)
+	print("endmodule", file=f)
diff --git a/spec/leaf_tap.py b/spec/leaf_tap.py
new file mode 100644
index 0000000..067b02b
--- /dev/null
+++ b/spec/leaf_tap.py
@@ -0,0 +1,34 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import numpy as np
+
+root = sys.argv[1]
+
+with open(root + "/leaftap/top.sv", "w") as f:
+	print("module top(input [7:0] clk, input d, output q);", file=f)
+	q = "d"
+	for c in range(8):
+		for x in range(200*c, 200*(c+1)):
+			depth = np.random.randint(5, 38)
+			print("wire [%d:0] lutchain_%d;" % (depth, x), file=f)
+			print("assign lutchain_%d[0] = %s;" % (x, q), file=f)
+			for i in range(depth):
+				print("(* keep, dont_touch *) LUT1 #(.INIT(2)) lut_%d_%d (.I0(lutchain_%d[%d]), .O(lutchain_%d[%d]));" % (x, i, x, i, x, i+1), file=f)
+			print("reg q_%d;" % x, file=f)
+			q = "q_%d" % x
+			print("always @(posedge clk[%d]) %s <= lutchain_%d[%d];" % (c, q, x, depth), file=f)
+	print("assign q = %s;\n" % q, file=f)
+	print("endmodule", file=f)
diff --git a/spec/mmcm_pll_used.py b/spec/mmcm_pll_used.py
new file mode 100644
index 0000000..8e9bf27
--- /dev/null
+++ b/spec/mmcm_pll_used.py
@@ -0,0 +1,206 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+X = 60
+root = sys.argv[1]
+
+def random_vector(size):
+	return "{%s}" % ", ".join(["d[%d]" % np.random.randint(40) for k in range(size)])
+
+cmts = []
+with open(root + "/tiles.txt", "r") as tf:
+	for line in tf:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		if sl[3] != "CMT_L":
+			continue
+		mmcm = None
+		plls = []
+		bufgces = []
+		for site in sl[4:]:
+			sitename, sitetype = site.split(':')
+			if sitetype == "MMCM":
+				mmcm = site
+			elif sitetype == "PLL":
+				plls.append(sitename)
+			elif sitetype == "BUFGCE":
+				bufgces.append(sitename)
+		print(sl[2], plls)
+		cmts.append((sl[2], mmcm, plls, bufgces))
+
+site_to_pin = {}
+with open (root + "/iopins.txt", "r") as iof:
+	for line in iof:
+		sl = line.strip().split(",")
+		if len(sl) < 5:
+			continue
+		pin = sl[0]
+		bank = int(sl[1])
+		func = sl[2]
+		site_name = sl[3]
+		site_type = sl[4]
+		if "GC" not in func or "HPIOB_M" not in site_type:
+			continue
+		site_to_pin[site_name] = pin
+
+for x in range(X):
+	np.random.shuffle(cmts)
+	with open(root + "/pllused/pllused%d.v" % x, "w") as f:
+		print("module top(input [%d:0] ccio, input psen, psincdec, pwrdwn, rst);" % (len(cmts)*4-1), file=f)
+		print("wire gr0, gr1;", file=f)
+		print("(* keep, dont_touch *) LUT6 lut0 (.O(gr0));", file=f)
+		print("(* keep, dont_touch *) LUT6 lut1 (.O(gr1));", file=f)
+		bufg_outputs = []
+		for i, cmt in enumerate(cmts):
+			tile, mmcm, plls, bufgces = cmt
+			if np.random.choice([True, False, False, False]):
+				continue
+			bufg_inputs = ["1'b1"]
+			clock_inputs = ["1'b1"]
+			clock_inputs += ["ccio[%d]" % (i*4+j) for j in range(4)]
+			clock_inputs += ["gr0", "gr1"]
+			if len(bufg_outputs) > 0:
+				clock_inputs.append(np.random.choice(bufg_outputs))
+				clock_inputs.append(np.random.choice(bufg_outputs))
+			if np.random.choice([True, True, True, True, False]):
+				fb_mode = np.random.choice([
+					"AUTO", "ZHOLD", "EXTERNAL", "INTERNAL", "BUF_IN",
+					])
+				print("    wire [12:0] mmcm_out_%d;\n" % i, file=f)
+				print("(* keep, dont_touch, LOC=\"%s\" *) MMCME4_ADV #(" % mmcm, file=f)
+				print("    .CLKIN1_PERIOD(10.0),", file=f)
+				print("    .CLKIN2_PERIOD(10.0),", file=f)
+				print("    .IS_PSEN_INVERTED(%d)," % np.random.randint(2), file=f)
+				print("    .IS_PSINCDEC_INVERTED(%d)," % np.random.randint(2), file=f)
+				print("    .IS_PWRDWN_INVERTED(%d)," % np.random.randint(2), file=f)
+				print("    .IS_RST_INVERTED(%d)," % np.random.randint(2), file=f)
+				print("    .COMPENSATION(\"%s\")" % fb_mode, file=f)
+				print(") mmcm_%d (" % i, file=f)
+				print("    .CLKIN1(%s)," % np.random.choice(clock_inputs), file=f)
+				print("    .CLKIN2(%s)," % np.random.choice(clock_inputs), file=f)
+				print("    .CLKFBIN(%s)," % (("mmcm_out_%d[0]" % i) if fb_mode in ("INTERNAL", "ZHOLD") else np.random.choice(clock_inputs)), file=f)
+				print("    .PSEN(psen),", file=f)
+				print("    .PSINCDEC(psincdec),", file=f)
+				print("    .PWRDWN(pwrdwn),", file=f)
+				print("    .RST(rst),", file=f)
+				print("    .CLKFBOUT(mmcm_out_%d[0])," % i, file=f)
+				print("    .CLKFBOUTB(mmcm_out_%d[1])," % i, file=f)
+				print("    .CLKOUT0(mmcm_out_%d[2])," % i, file=f)
+				print("    .CLKOUT0B(mmcm_out_%d[3])," % i, file=f)
+				print("    .CLKOUT1(mmcm_out_%d[4])," % i, file=f)
+				print("    .CLKOUT1B(mmcm_out_%d[5])," % i, file=f)
+				print("    .CLKOUT2(mmcm_out_%d[6])," % i, file=f)
+				print("    .CLKOUT2B(mmcm_out_%d[7])," % i, file=f)
+				print("    .CLKOUT3(mmcm_out_%d[8])," % i, file=f)
+				print("    .CLKOUT3B(mmcm_out_%d[9])," % i, file=f)
+				print("    .CLKOUT4(mmcm_out_%d[10])," % i, file=f)
+				print("    .CLKOUT5(mmcm_out_%d[11])," % i, file=f)
+				print("    .CLKOUT6(mmcm_out_%d[12])" % i, file=f)
+				print(");", file=f)
+				bufg_inputs += ["mmcm_out_%d[%d]" % (i, j) for j in range(13)]
+				# MMCM --> PLL
+				clock_inputs.append("mmcm_out_%d[2]" % i)
+				clock_inputs.append("mmcm_out_%d[4]" % i)
+				clock_inputs.append("mmcm_out_%d[6]" % i)
+				clock_inputs.append("mmcm_out_%d[8]" % i)
+			for p in range(2):
+				if np.random.choice([True, True, False]):
+					fb_mode = np.random.choice([
+					"AUTO", "INTERNAL", "BUF_IN",
+					])
+					print("    wire [3:0] pll_out_%d_%d;\n" % (i, p), file=f)
+					print("(* keep, dont_touch, LOC=\"%s\" *) PLLE4_ADV #(" % plls[p], file=f)
+					print("    .CLKIN_PERIOD(10.0),", file=f)
+					print("    .IS_PWRDWN_INVERTED(%d)," % np.random.randint(2), file=f)
+					print("    .IS_RST_INVERTED(%d)," % np.random.randint(2), file=f)
+					print("    .COMPENSATION(\"%s\")" % fb_mode, file=f)
+					print(") pll_%d_%d (" % (i, p), file=f)
+					print("    .CLKIN(%s)," % np.random.choice(clock_inputs), file=f)
+					print("    .PWRDWN(pwrdwn),", file=f)
+					print("    .RST(rst),", file=f)
+					print("    .CLKFBIN(%s)," % (("pll_out_%d_%d[0]" % (i, p)) if fb_mode in ("INTERNAL", "ZHOLD") else np.random.choice(clock_inputs)), file=f)
+					print("    .CLKFBOUT(pll_out_%d_%d[0])," % (i, p), file=f)
+					print("    .CLKOUT0(pll_out_%d_%d[1])," % (i, p), file=f)
+					print("    .CLKOUT1(pll_out_%d_%d[2])," % (i, p), file=f)
+					print("    .CLKOUT1B(pll_out_%d_%d[3])" % (i, p), file=f)
+					print(");", file=f)
+					bufg_inputs += ["pll_out_%d_%d[%d]" % (i, p, j) for j in range(4)]
+			for j, bufg in enumerate(bufgces):
+				if np.random.choice([True, True, True, False]):
+					print("wire bufg_out_%d_%d;" % (i, j), file=f)
+					print("(* keep, dont_touch, LOC=\"%s\" *)" % bufg, file=f)
+					print("BUFGCE bufg_%d_%d (" % (i, j), file=f)
+					print("  .I(%s)," % np.random.choice(bufg_inputs), file=f)
+					print("  .O(bufg_out_%d_%d)" % (i, j), file=f)
+					print(");", file=f)
+					bufg_outputs.append("bufg_out_%d_%d" % (i, j))
+		print("endmodule", file=f)
+	with open(root + "/pllused/pllused%d.xdc" % x, "w") as f:
+		for i, cmt in enumerate(cmts):
+			tile = cmt[0]
+			cmt_x = int(tile[tile.rfind('X')+1:tile.rfind('Y')])
+			cmt_y = int(tile[tile.rfind('Y')+1:])
+			ccio = 0
+			for site, pin in site_to_pin.items():
+				if ccio >= 4:
+					break
+				site_x = int(site[site.rfind('X')+1:site.rfind('Y')])
+				site_y = int(site[site.rfind('Y')+1:])
+				if (site_x//2) != (cmt_x//40) or (site_y//52) != (cmt_y//60):
+					continue
+				print("set_property PACKAGE_PIN %s [get_ports {ccio[%d]}]" % (pin, i*4+ccio), file=f)
+				print("set_property IOSTANDARD LVCMOS18 [get_ports {ccio[%d]}]" % (i*4+ccio), file=f)
+				ccio += 1
+			assert ccio == 4, (ccio, tile)
+		print("set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets gr*]", file=f)
+		print("set_property CLOCK_DEDICATED_ROUTE ANY_CMT_COLUMN [get_nets bufg_out*]", file=f)
+	with open(root + "/pllused/pllused%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/pllused/pllused%d.v" % x)), file=f)
+		print("add_files %s" % (root + ("/pllused/pllused%d.xdc" % x)), file=f)
+		print("synth_design -top top -part xczu7ev-ffvf1517-2-e", file=f)
+		print("opt_design", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks]", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_clk/pllused%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_clk/pllused%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_clk/pllused%d.bit" % (root, x), file=f)
+with open(root + "/pllused/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	print("vivado -mode batch -nolog -nojournal -source pllused$1.tcl", file=f)
+	print("if [ $? -eq 0 ]; then", file=f)
+	print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/pllused$1.bit %s/frames.txt > %s/specimen_clk/pllused$1.dump" % (root, root, root), file=f)
+	print("    CLOCK_ONLY=1 python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_clk/pllused$1.dump > %s/specimen_clk/pllused$1.tbits" % (root, root, root), file=f)
+	print("else", file=f)
+	print("   rm %s/specimen_clk/pllused$1.dump" % (root, ), file=f)
+	print("   rm %s/specimen_clk/pllused$1.tbits" % (root, ), file=f)
+	print("   rm %s/specimen_clk/pllused$1.dcp" % (root, ), file=f)
+	print("   rm %s/specimen_clk/pllused$1.bit" % (root, ), file=f)
+	print("   rm %s/specimen_clk/pllused$1.features" % (root, ), file=f)
+	print("fi", file=f)
+with open(root + "/pllused/Makefile", "w") as f:
+	print("all: %s" % " ".join(["%d.done" % i for i in range(X)]), file=f)
+	print("", file=f)
+	print("%.done: ", file=f)
+	print("\tbash run.sh $*", file=f)
+	print("\ttouch $@", file=f)
+	print("", file=f)
+	print("clean: ", file=f)
+	print("\trm -f *.done", file=f)
diff --git a/spec/pin_routing_fuzz.tcl b/spec/pin_routing_fuzz.tcl
new file mode 100644
index 0000000..05ef046
--- /dev/null
+++ b/spec/pin_routing_fuzz.tcl
@@ -0,0 +1,88 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+set cells [get_cells -filter {PRIMITIVE_TYPE =~ *BUFG*}]
+for {set k 0} {$k < 25} {incr k} {
+foreach cell $cells {
+	set net [get_nets -of_objects [get_pins $cell/O]]
+	set success 0
+	set src [lindex [get_nodes -of_objects [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]] 0]
+	set sink [get_nodes -of_objects [get_site_pins -filter {DIRECTION == IN} -of_objects $net ]]
+	set orig_route [get_property FIXED_ROUTE $net]
+	set_property IS_ROUTE_FIXED 0 $net
+	route_design -unroute -nets $net
+	for {set i 0} {$i < 15} {incr i} {
+		# try and route using an interesting pip
+		set cursor $src
+		for {set j 0} {$j < 4} {incr j} {
+			if {[llength [get_nodes -downhill -of_objects $cursor]] > 1} {
+				break
+			}
+			set cursor [lindex [get_nodes -downhill -of_objects $cursor] 0]
+		}
+		set nodes [get_nodes -downhill -of_objects $cursor]
+		set from_node $cursor
+		set to_node [lindex $nodes [expr {int(rand()*[llength $nodes])}]]
+		puts "$k $i $net $src $from_node $to_node $sink"
+		# pre-reject dud pips
+		#if {[llength [get_nodes -uphill -of_objects $from_node]] == 0 ||  [llength [get_nodes -downhill -of_objects $to_node]] == 0} {
+		#	continue
+		#}
+		if {[string first $from_node $orig_route] != -1} {
+			continue
+		}
+		if {$src != $from_node} {
+			if {[catch {set route_a [find_routing_path -max_nodes 10 -from $src -to $from_node]}]} {
+				continue
+			}
+			if {$route_a == ""} {
+				continue
+			}
+		} else {
+			set route_a $from_node
+		}
+		if {[catch {set route_b [find_routing_path -max_nodes 100  -from $to_node -to $sink]}]} {
+			continue
+		}
+		if {$route_b == ""} {
+			continue
+		}
+		if {[catch {set_property FIXED_ROUTE "$route_a $route_b" $net}]} {
+			continue
+		}
+		set_property IS_ROUTE_FIXED 1 $net
+		if {[get_property ROUTE_STATUS $net] != "ROUTED"} {
+			set_property IS_ROUTE_FIXED 0 $net
+			route_design -unroute -nets $net
+			continue
+		}
+		set success 1
+		break
+	}
+	if {$success == 0} {
+		# route simply\\\\\\
+		set_property FIXED_ROUTE [find_routing_path -from $src -to $sink] $net
+	}
+}
+set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
+set_property SEVERITY {Warning} [get_drc_checks UCIO-1]
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+
+#write_edif -force "./results/specimen_clk/bufgroute$k.edf"
+#write_checkpoint -force "./results/specimen_clk/bufgroute$k.dcp"
+#write_bitstream -force "./results/specimen_clk/bufgroute$k.bit"
+
+break
+
+}
\ No newline at end of file
diff --git a/spec/pll_drp.py b/spec/pll_drp.py
new file mode 100644
index 0000000..c7c0882
--- /dev/null
+++ b/spec/pll_drp.py
@@ -0,0 +1,294 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# From UG572
+
+mmcm_regions = [
+	(0x00, [
+		(1, "IS_CLKIN1_INVERTED"),
+		(2, "IS_CLKIN2_INVERTED"),
+		(3, "IS_CLKFBIN_INVERTED"),
+		((15, 4), "RESERVED_00", 4),
+	]),
+	(0x02, [
+		((15, 0), "RESERVED_02", 0),
+	]),
+	(0x04, [
+		((2, 0), "SS_STEPS", 0),
+		((5, 3), "SS_STEPS_INIT", 0),
+		((15, 6), "RESERVED_04", 6)
+	]),
+	(0x05, [
+		((15, 0), "RESERVED_05", 0),
+	]),
+	(0x06, [
+		((15, 13), "CLKOUT5_PM", 0),
+		(12, "CLKOUT5_EN"),
+		((11, 6), "CLKOUT5_HT", 0),
+		((5, 0), "CLKOUT5_LT", 0),
+	]),
+	(0x07, [
+		((15, 13), "CLKOUT0_PM_F", 0),
+		(12, "CLKOUT0_FRAC_WF_F"),
+		#(10, "CLKOUT0_CDDC_EN"),
+		(7, "CLKOUT5_EDGE"),
+		(6, "CLKOUT5_NOCOUNT"),
+		((5, 0), "CLKOUT5_DT", 0),
+	]),
+	(0x08, [
+		((15, 13), "CLKOUT0_PM_R", 0),
+		(12, "CLKOUT0_EN"),
+		((11, 6), "CLKOUT0_HT", 0),
+		((5, 0), "CLKOUT0_LT", 0),
+	]),
+	(0x09, [
+		#(15, "CLKOUT0_CDDC_EN")
+		((14, 12), "CLKOUT0_FRAC", 0),
+		(11, "CLKOUT0_FRAC_EN"),
+		(10, "CLKOUT0_FRAC_WF_R"),
+		(7, "CLKOUT0_EDGE"),
+		(6, "CLKOUT0_NOCOUNT"),
+		((5, 0), "CLKOUT0_DT", 0),
+	]),
+	(0x0A, [
+		((15, 13), "CLKOUT1_PM", 0),
+		(12, "CLKOUT1_EN"),
+		((11, 6), "CLKOUT1_HT", 0),
+		((5, 0), "CLKOUT1_LT", 0),
+	]),
+	(0x0B, [
+		(7, "CLKOUT1_EDGE"),
+		(6, "CLKOUT1_NOCOUNT"),
+		((5, 0), "CLKOUT1_DT", 0),
+	]),
+	(0x0C, [
+		((15, 13), "CLKOUT2_PM", 0),
+		(12, "CLKOUT2_EN"),
+		((11, 6), "CLKOUT2_HT", 0),
+		((5, 0), "CLKOUT2_LT", 0),
+	]),
+	(0x0D, [
+		(7, "CLKOUT2_EDGE"),
+		(6, "CLKOUT2_NOCOUNT"),
+		((5, 0), "CLKOUT2_DT", 0),
+	]),
+	(0x0E, [
+		((15, 13), "CLKOUT3_PM", 0),
+		(12, "CLKOUT3_EN"),
+		((11, 6), "CLKOUT3_HT", 0),
+		((5, 0), "CLKOUT3_LT", 0),
+	]),
+	(0x0F, [
+		(7, "CLKOUT3_EDGE"),
+		(6, "CLKOUT3_NOCOUNT"),
+		((5, 0), "CLKOUT3_DT", 0),
+	]),
+	(0x10, [
+		((15, 13), "CLKOUT4_PM", 0),
+		(12, "CLKOUT4_EN"),
+		((11, 6), "CLKOUT4_HT", 0),
+		((5, 0), "CLKOUT4_LT", 0),
+	]),
+	(0x11, [
+		(7, "CLKOUT4_EDGE"),
+		(6, "CLKOUT4_NOCOUNT"),
+		((5, 0), "CLKOUT4_DT", 0),
+	]),
+	(0x12, [
+		((15, 13), "CLKOUT6_PM", 0),
+		(12, "CLKOUT6_EN"),
+		((11, 6), "CLKOUT6_HT", 0),
+		((5, 0), "CLKOUT6_LT", 0),
+	]),
+	(0x13, [
+		((15, 13), "CLKFBOUT_PM_F", 0),
+		(12, "CLKFBOUT_FRAC_WF_F"),
+		(7, "CLKOUT6_EDGE"),
+		(6, "CLKOUT6_NOCOUNT"),
+		((5, 0), "CLKOUT6_DT", 0),
+	]),
+	(0x14, [
+		((15, 13), "CLKFBOUT_PM_R", 0),
+		(12, "CLKFBOUT_EN"),
+		((11, 6), "CLKFBOUT_HT", 0),
+		((5, 0), "CLKFBOUT_LT", 0),
+	]),
+	(0x15, [
+		((14, 12), "CLKFBOUT_FRAC", 0),
+		(11, "CLKFBOUT_FRAC_EN"),
+		(10, "CLKFBOUT_FRAC_WF_R"),
+		(7, "CLKFBOUT_EDGE"),
+		(6, "CLKFBOUT_NOCOUNT"),
+		((5, 0), "CLKFBOUT_DT", 0),
+	]),
+	(0x16, [
+		(13, "DIVCLK_EDGE"),
+		(12, "DIVCLK_NOCOUNT"),
+		((11, 6), "DIVCLK_HT", 0),
+		((5, 0), "DIVCLK_LT", 0),
+	]),
+	(0x18, [
+		((9, 0), "LOCK_COUNT", 0)
+	]),
+	(0x19, [
+		((14, 10), "LOCK_FB_DLY", 0),
+		((9, 0), "LOCK_FB_SAT_HIGH", 0),
+	]),
+	(0x1A, [
+		((14, 10), "LOCK_REF_DLY", 0),
+		((9, 0), "LOCK_REF_SAT_HIGH", 0),
+	]),
+	(0x20, [
+		((15, 0), "RESERVED_20", 0),
+	]),
+	(0x21, [
+		((15, 0), "RESERVED_21", 0),
+	]),
+	(0x27, [
+		(15, "INTERP_EN[7]"),
+		(12, "INTERP_EN[6]"),
+		(11, "INTERP_EN[5]"),
+		(8, "INTERP_EN[4]"),
+		(7, "INTERP_EN[3]"),
+		(4, "INTERP_EN[2]"),
+		(3, "INTERP_EN[1]"),
+		(0, "INTERP_EN[0]"),
+	]),
+	(0x2C, [
+		((15, 0), "RESERVED_2C", 0),
+	]),
+	(0x2D, [
+		((15, 0), "RESERVED_2D", 0),
+	]),
+	(0x2F, [
+		((15, 0), "RESERVED_2F", 0),
+	]),
+	(0x30, [
+		((15, 0), "RESERVED_30", 0),
+	]),
+	(0x31, [
+		((15, 0), "RESERVED_31", 0),
+	]),
+	(0x37, [
+		((15, 0), "RESERVED_37", 0),
+	]),
+	(0x38, [
+		((15, 0), "RESERVED_38", 0),
+	]),
+	(0x3C, [
+		((15, 0), "RESERVED_3C", 0),
+	]),
+	(0x49, [
+		((15, 0), "RESERVED_49", 0),
+	]),
+	(0x4A, [
+		((15, 0), "RESERVED_4A", 0),
+	]),
+	(0x4E, [
+		(15, "CHARGE_PUMP[3]"),
+		(12, "CHARGE_PUMP[2]"),
+		(11, "CHARGE_PUMP[1]"),
+		(8, "CHARGE_PUMP[0]"),
+		((7, 0), "RESERVED_4E", 0),
+	]),
+	(0x4F, [
+		(15, "RES[3]"),
+		(12, "RES[2]"),
+		(11, "RES[1]"),
+		(8, "RES[0]"),
+		(7, "LFHF[1]"),
+		(4, "LFHF[0]"),
+	]),
+	(0x72, [
+		((15, 0), "RESERVED_72", 0),
+	]),
+	(0x73, [
+		(15, "GTS_WAIT"),
+		(14, "STARTUP_WAIT"),
+		(13, "SS_EN"),
+		(12, "MMCM_EN"),
+	]),
+	(0x74, [
+		((15, 0), "RESERVED_74", 0),
+	]),
+	(0x76, [
+		((15, 0), "RESERVED_76", 0),
+	]),
+	(0x77, [
+		((15, 0), "RESERVED_77", 0),
+	]),
+	(0x78, [
+		((15, 0), "RESERVED_78", 0),
+	]),
+	(0x79, [
+		((15, 0), "RESERVED_79", 0),
+	]),
+	(0x7A, [
+		((15, 0), "RESERVED_7A", 0),
+	]),
+	(0x7B, [
+		((15, 0), "RESERVED_7B", 0),
+	]),
+	(0x7C, [
+		((15, 0), "RESERVED_7C", 0),
+	]),
+	(0x7D, [
+		((15, 0), "RESERVED_7D", 0),
+	]),
+	(0x7E, [
+		((15, 0), "RESERVED_7E", 0),
+	]),
+	(0x7F, [
+		((15, 0), "RESERVED_7F", 0),
+	]),
+]
+
+cmt_height = 60*48
+mmcm_start_bit = 1824
+
+def drp_to_cmt_bit(addr, bit):
+	frame = (bit % 2)
+	if addr >= 0x0C and addr <= 0x15:
+		addr -= 0x08
+		frame += 2
+	elif addr in (0x16, 0x17):
+		addr -= 0x10
+	elif addr in (0x06, 0x17):
+		addr += 0x10
+	elif addr >= 0x40 and addr <= 0x60:
+		addr -= 2
+	elif addr >= 0x18 and addr <= 0x1A:
+		addr += 6
+	elif addr >= 0x20 and addr <= 0x22: # fixme
+		addr -= 20
+	elif addr == 0x27:
+		addr = 0x23
+	elif addr >= 0x16:
+		addr -= 12
+	data_bit = addr * 8
+	data_bit += (bit // 2)
+	return frame * cmt_height + mmcm_start_bit + data_bit
+
+p = "MMCM."
+
+for addr, dbits in mmcm_regions:
+	for dbit in dbits:
+		if isinstance(dbit[0], tuple):
+			r, n, o = dbit
+			for i in range(r[1], r[0]+1):
+				print("%s%s[%d] %d" % (p, n, (i - r[1]) + o, drp_to_cmt_bit(addr, i)))
+		else:
+			b, n = dbit
+			print("%s%s %d" % (p, n, drp_to_cmt_bit(addr, b)))
+
diff --git a/spec/process_bram_init.py b/spec/process_bram_init.py
new file mode 100644
index 0000000..d81965f
--- /dev/null
+++ b/spec/process_bram_init.py
@@ -0,0 +1,92 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+frame_set_bits = {}
+group_set_bits = {}
+
+line_re = re.compile(r'F(0x[0-9A-Fa-f]+)W(\d+)B(\d+)')
+
+with open("bram.dump", "r") as df:
+	for line in df:
+			m = line_re.match(line)
+			if not m:
+				continue
+			frame = int(m[1], 16)
+			if (frame >> 24) != 0x01:
+				continue
+			frame = frame & 0xFF
+			if frame not in frame_set_bits:
+				frame_set_bits[frame] = set()
+			framebit = int(m[2]) * 32 + int(m[3])
+			frame_set_bits[frame].add(framebit)
+with open("bram.txt", "r") as ef:
+	for line in ef:
+		frame, startbit, site_y, data, parity = line.strip().split(" ")
+		frame = int(frame)
+		startbit = int(startbit)
+		site_y = int(site_y)
+		data = [int(x) for x in data]
+		parity = [int(x) for x in parity]
+		if (frame, startbit) not in group_set_bits:
+			group_set_bits[frame, startbit] = set()
+		for i, d in enumerate(data):
+			if d != 1:
+				continue
+			group_set_bits[frame, startbit].add("%d_%d" % (site_y, i))
+		for i, d in enumerate(parity):
+			if d != 1:
+				continue
+			group_set_bits[frame, startbit].add("%d_P%d" % (site_y, i))
+
+#features = []
+#for y in range(2):
+#	features += ["%d_%d" % (y, i) for i in range(64)]
+#	features += ["%d_P%d" % (y, i) for i in range(8)]
+
+
+bram_permute = []
+for i, prefix in enumerate(("0_", "0_P", "1_", "1_P")):
+	permute = []
+	for j in range(8 if prefix.endswith("P") else 64):
+		f = "%s%d" % (prefix, j)
+		candidates = None
+		for group, features in group_set_bits.items():
+			if f not in features:
+				continue
+			frame, startbit = group
+			if frame not in frame_set_bits:
+				continue
+			set_bits = frame_set_bits[frame]
+			if candidates is None:
+				candidates = set()
+				for b in range(startbit, startbit + 240):
+					if b in set_bits:
+						candidates.add(b - startbit)
+			else:
+				todelete = []
+				for c in candidates:
+					if (startbit + c) not in set_bits:
+						todelete.append(c)
+				for t in todelete:
+					candidates.remove(t)
+		print("%s: %s" % (f, " ".join([str(c) for c in sorted(candidates)])))
+		permute.append(list(candidates)[0])
+	bram_permute.append(permute)
+
+for i, cname in enumerate(("bram_y0_permute", "bram_p_y0_permute", "bram_y1_permute", "bram_p_y1_permute")):
+	p = bram_permute[i]
+	print("static const int %s[%d] = {%s};" % (
+		cname, len(p), ", ".join(str(x) for x in p)
+	))
diff --git a/spec/rclk_routing_fuzz_2.tcl b/spec/rclk_routing_fuzz_2.tcl
new file mode 100644
index 0000000..53d97e8
--- /dev/null
+++ b/spec/rclk_routing_fuzz_2.tcl
@@ -0,0 +1,275 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+proc make_driver_cell {name celltype loc pinname} {
+    create_net "${name}_net"
+    set net [get_nets "${name}_net"]
+    create_cell -reference $celltype "${name}_drv"
+    place_cell "${name}_drv" $loc
+    connect_net -net $net -objects [get_pins "${name}_drv/${pinname}"]
+    return [list [get_cells "${name}_drv"] $net]
+}
+
+proc make_user_cell {name celltype loc pinname net} {
+    create_cell -reference $celltype "${name}_usr"
+    place_cell "${name}_usr" $loc
+    connect_net -net $net -objects [get_pins "${name}_usr/${pinname}"]
+    return [list [get_cells "${name}_usr"] $net]
+}
+
+
+proc make_site_pin_driver {pin} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_ROW" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_ROW_FSR" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_LEAF" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_OUT"} {
+        set result [make_driver_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "O"]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "O"]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFG_GT" "$site/BUFG_GT" "O"]
+    } elseif {($sitetype == "SLICEL" || $sitetype == "SLICEM") && [string range $pinname 1 4] == "MUX"} {
+        set eighth [string range $pinname 0 0]
+        return [make_driver_cell $objname "LUT6" "$site/${eighth}6LUT" "O"]
+    } else {
+        return {}
+    }
+}
+
+proc make_site_pin_user {pin net} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFCE_LEAF" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFCE_LEAF" && $pinname == "CE_INT"} {
+        return [make_user_cell $objname "BUFCE_LEAF" "$site/BUFCE" "CE" $net]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFGCE" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFGCE" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_IN"} {
+        set result [make_user_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "I" $net]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I0"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I0" $net]
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I1"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I1" $net]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFG_GT" "$site/BUFG_GT" "I" $net]
+    } elseif {$sitetype == "SLICEM" && $pinname == "LCLK"} {
+        return [make_user_cell $objname "SRL16E" "$site/H6LUT" "CLK" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK1"} {
+        return [make_user_cell $objname "FDRE" "$site/AFF" "C" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK2"} {
+        return [make_user_cell $objname "FDRE" "$site/EFF" "C" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK"} {
+        return [make_user_cell $objname "IDELAYE3" "$site/IDELAY" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK_C"} {
+        return [make_user_cell $objname "ISERDESE3" "$site/ISERDES" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLKDIV"} {
+        return [make_user_cell $objname "ISERDESE3" "$site/ISERDES" "CLKDIV" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "TX_OCLK"} {
+        return [make_user_cell $objname "OSERDESE3" "$site/OSERDES" "CLK" $net]
+    } else {
+        return {}
+    }
+}
+
+proc route_via {net sink_pin pip} {
+    set success 0
+    set src [lindex [get_nodes -of_objects [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]] 0]
+    set sink [get_nodes -of_objects [get_site_pins -filter {DIRECTION == IN} -of_objects $net ]]
+    if {$sink == {}} {
+        set sink $sink_pin
+    }
+    set_property IS_ROUTE_FIXED 0 $net
+    #route_design -unroute -nets $net
+
+    set from_node [get_nodes -uphill -of_objects $pip]
+    set to_node [get_nodes -downhill -of_objects $pip]
+    if {$src == $from_node} {
+        set route_a $from_node
+    } else {
+        if {[catch {set route_a [find_routing_path -max_nodes 15 -from $src -to $from_node]}]} {
+            return 0
+        }
+        if {$route_a == ""} {
+            return 0
+        }
+    }
+    if {$sink == $to_node} {
+        set route_b $to_node
+    } else {
+        if {[catch {set route_b [find_routing_path -max_nodes 15  -from $to_node -to $sink]}]} {
+            return 0
+        }
+        if {$route_b == ""} {
+            return 0
+        }
+    }
+
+    if {[catch {set_property FIXED_ROUTE "$route_a $route_b" $net}]} {
+        return 0
+    }
+    set_property IS_ROUTE_FIXED 1 $net
+    if {[get_property ROUTE_STATUS $net] != "ROUTED"} {
+        set_property IS_ROUTE_FIXED 0 $net
+        route_design -unroute -nets $net
+        return 0
+    }
+    return 1
+}
+
+# https://wiki.tcl-lang.org/page/Shuffle+a+list
+proc shuffle6 { list } {
+     set n [llength $list]
+     for { set i 1 } { $i < $n } { incr i } {
+         set j [expr { int( rand() * $n ) }]
+         set temp [lindex $list $i]
+         lset list $i [lindex $list $j]
+         lset list $j $temp
+     }
+     return $list
+ }
+
+set tile_types [list "CMT_L" "CMT_L" "CMT_L" "RCLK_BRAM_INTF_L" \
+                    "RCLK_CLEL_L_L" "RCLK_CLEM_CLKBUF_L" "RCLK_CLEM_L" \
+                    "RCLK_DSP_INTF_R" "RCLK_DSP_INTF_CLKBUF_L" "RCLK_INT_L"\
+                    "RCLK_INT_R" "RCLK_RCLK_XIPHY_INNER_FT" "RCLK_HDIO" "RCLK_RCLK_URAM_INTF_L_FT"]
+
+set run $::argv
+create_project -force -part xczu7ev-ffvf1517-2-e design${run} design${run}
+
+
+link_design
+create_cell -reference LUT6 "misc_lut"
+place_design
+route_design
+
+#remove_cell [get_cells]
+#remove_net [get_nets]
+
+set count [expr {int(110 + rand()*80)}]
+
+for {set i 0} {$i < $count} {incr i} {
+    puts "$i/$count"
+    for {set j 0} {$j < 10} {incr j} {
+        set tt [lindex $tile_types [expr {int(rand()*[llength $tile_types])}]]
+        set tiles [get_tiles -filter "TILE_TYPE == $tt"]
+        set tile [lindex $tiles [expr {int(rand()*[llength $tiles])}]]
+        set tile_pips [get_pips -of_objects $tile -filter {NAME !~ "*VCC_WIRE*"}]
+        set pip [lindex $tile_pips [expr {int(rand()*[llength $tile_pips])}]]
+        set wavefront_bwd $pip
+        set driver {}
+        for {set k 0} {$k < 10} {incr k} {
+            set wavefront_bwd [get_nodes -uphill -of_objects $wavefront_bwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.5} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_bwd]]
+                foreach pin $wavefront_pins {
+                    set driver [make_site_pin_driver $pin]
+                    if {$driver != {}} {
+                        break
+                    }
+                }
+            }
+            if {$driver != {}} {
+                break
+            }
+            if {[llength $wavefront_bwd] > 20000} {
+                break
+            }
+        }
+
+        if {$driver == {}} {
+            continue
+        }
+
+        set drv_cell [lindex $driver 0]
+        set net [lindex $driver 1]
+
+        set wavefront_fwd $pip
+        set user {}
+        for {set k 0} {$k < 10} {incr k} {
+            set wavefront_fwd [get_nodes -downhill -of_objects $wavefront_fwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.5} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_fwd]]
+                foreach pin $wavefront_pins {
+                    set user [make_site_pin_user $pin $net]
+                    set sink_pin $pin
+                    if {$user != {}} {
+                        break
+                    }
+                }
+            }
+            if {$user != {}} {
+                break
+            }
+            if {[llength $wavefront_fwd] > 20000} {
+                break
+            }
+        }
+
+        if {$user == {}} {
+            remove_cell $drv_cell
+            remove_net $net
+            continue
+        }
+
+        set success [route_via $net $sink_pin $pip]
+        if { $success == 0 } {
+            remove_cell $drv_cell
+            remove_cell [lindex $user 0]
+            remove_net $net
+        } else {
+            break
+        }
+    }
+}
+
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+set_property SEVERITY {Warning} [get_drc_checks]
+write_checkpoint -force ../specimen_clk/rclkroute_b${run}.dcp
+write_edif -force ../specimen_clk/rclkroute_b${run}.edf
+write_bitstream -force ../specimen_clk/rclkroute_b${run}.bit
+
diff --git a/spec/rclk_routing_fuzz_3.tcl b/spec/rclk_routing_fuzz_3.tcl
new file mode 100644
index 0000000..e117311
--- /dev/null
+++ b/spec/rclk_routing_fuzz_3.tcl
@@ -0,0 +1,317 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+proc make_driver_cell {name celltype loc pinname} {
+    set existing_cell [get_cells -of_objects [get_bels $loc]]
+    if {$existing_cell != {}} {
+        set existing_net [get_nets -of_objects [get_pins "${existing_cell}/${pinname}"]]
+        if {$existing_net != {}} {
+           return {}
+        }
+        create_net "${name}_net"
+        set net [get_nets "${name}_net"]
+        connect_net -net $net -objects [get_pins "${existing_cell}/${pinname}"]
+        return [list $existing_cell $net]
+    }
+    create_net "${name}_net"
+    set net [get_nets "${name}_net"]
+    create_cell -reference $celltype "${name}_drv"
+    place_cell "${name}_drv" $loc
+    connect_net -net $net -objects [get_pins "${name}_drv/${pinname}"]
+    return [list [get_cells "${name}_drv"] $net]
+}
+
+proc make_user_cell {name celltype loc pinname net} {
+    set existing_cell [get_cells -of_objects [get_bels $loc]]
+    if {$existing_cell != {}} {
+        set existing_net [get_nets -of_objects [get_pins "${existing_cell}/${pinname}"]]
+        if {$existing_net != {}} {
+           return {}
+        }
+        connect_net -net $net -objects [get_pins "${existing_cell}/${pinname}"]
+        return [list $existing_cell $net]
+    }
+    create_cell -reference $celltype "${name}_usr"
+    place_cell "${name}_usr" $loc
+    connect_net -net $net -objects [get_pins "${name}_usr/${pinname}"]
+    return [list [get_cells "${name}_usr"] $net]
+}
+
+
+proc make_site_pin_driver {pin} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {$sitetype == "BUFCE_ROW" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_ROW_FSR" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_LEAF" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_OUT"} {
+        set result [make_driver_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "O"]
+        if {$result == {}} {
+            return {}
+        }
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "O"]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFG_GT" "$site/BUFG_GT" "O"]
+    } elseif {($sitetype == "SLICEL" || $sitetype == "SLICEM") && [string range $pinname 1 4] == "MUX"} {
+        set eighth [string range $pinname 0 0]
+        return [make_driver_cell $objname "LUT6" "$site/${eighth}6LUT" "O"]
+    } elseif {$sitetype == "MMCM" && [string first "CLK" $pinname] != -1  && [string first "OUT" $pinname] != -1} {
+        return [make_driver_cell $objname "MMCME4_ADV" "$site/MMCM" $pinname]
+    } elseif {$sitetype == "PLL" && [string first "CLK" $pinname] != -1  && [string first "OUT" $pinname] != -1} {
+        return [make_driver_cell $objname "PLLE4_ADV" "$site/PLL" $pinname]
+    } else {
+        return {}
+    }
+}
+
+proc make_site_pin_user {pin net} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_nets -of_objects $pin] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFCE_LEAF" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFCE_LEAF" && $pinname == "CE_INT"} {
+        return [make_user_cell $objname "BUFCE_LEAF" "$site/BUFCE" "CE" $net]
+    } elseif {$sitetype == "BUFCE_ROW_FSR" && $pinname == "CE_PRE_OPTINV"} {
+        return [make_user_cell $objname "BUFCE_ROW" "$site/BUFCE" "CE" $net]
+    } elseif {$sitetype == "BUFCE_ROW" && $pinname == "CE_PRE_OPTINV"} {
+        return [make_user_cell $objname "BUFCE_ROW" "$site/BUFCE" "CE" $net]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFGCE" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFGCE" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_IN"} {
+        set result [make_user_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "I" $net]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I0"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I0" $net]
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I1"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I1" $net]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFG_GT" "$site/BUFG_GT" "I" $net]
+    } elseif {$sitetype == "SLICEM" && $pinname == "LCLK"} {
+        return [make_user_cell $objname "SRL16E" "$site/H6LUT" "CLK" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK1"} {
+        return [make_user_cell $objname "FDRE" "$site/AFF" "C" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK2"} {
+        return [make_user_cell $objname "FDRE" "$site/EFF" "C" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK"} {
+        return [make_user_cell $objname "IDELAYE3" "$site/IDELAY" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK_C"} {
+        return [make_user_cell $objname "ISERDESE3" "$site/ISERDES" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLKDIV"} {
+        return [make_user_cell $objname "ISERDESE3" "$site/ISERDES" "CLKDIV" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "TX_OCLK"} {
+        return [make_user_cell $objname "OSERDESE3" "$site/OSERDES" "CLK" $net]
+    } elseif {$sitetype == "MMCM" && $pinname == "CLKIN1"} {
+        return [make_user_cell $objname "MMCME4_ADV" "$site/MMCM" "CLKIN1" $net]
+    } elseif {$sitetype == "MMCM" && $pinname == "CLKIN2"} {
+        return [make_user_cell $objname "MMCME4_ADV" "$site/MMCM" "CLKIN2" $net]
+    } elseif {$sitetype == "MMCM" && $pinname == "CLKFBIN"} {
+        return [make_user_cell $objname "MMCME4_ADV" "$site/MMCM" "CLKFBIN" $net]
+    } elseif {$sitetype == "PLL" && $pinname == "CLKIN"} {
+        return [make_user_cell $objname "PLLE4_ADV" "$site/PLL" "CLKIN" $net]
+    } elseif {$sitetype == "PLL" && $pinname == "CLKFBIN"} {
+        return [make_user_cell $objname "PLLE4_ADV" "$site/PLL" "CLKFBIN" $net]
+    } else {
+        return {}
+    }
+}
+
+proc route_via {net sink_pin pip} {
+    set success 0
+    set src [lindex [get_nodes -of_objects [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]] 0]
+    set sink [get_nodes -of_objects [get_site_pins -filter {DIRECTION == IN} -of_objects $net ]]
+    if {$sink == {}} {
+        set sink [get_nodes -of_objects $sink_pin]
+    }
+    set_property IS_ROUTE_FIXED 0 $net
+    #route_design -unroute -nets $net
+
+    set from_node [get_nodes -uphill -of_objects $pip]
+    set to_node [get_nodes -downhill -of_objects $pip]
+    if {$src == $from_node} {
+        set route_a $from_node
+    } else {
+        if {[catch {set route_a [find_routing_path -max_nodes 15 -from $src -to $from_node]}]} {
+            return 0
+        }
+        if {$route_a == ""} {
+            return 0
+        }
+    }
+    if {$sink == $to_node} {
+        set route_b $to_node
+    } else {
+        if {[catch {set route_b [find_routing_path -max_nodes 15  -from $to_node -to $sink]}]} {
+            return 0
+        }
+        if {$route_b == ""} {
+            return 0
+        }
+    }
+
+    if {[catch {set_property FIXED_ROUTE "$route_a $route_b" $net}]} {
+        return 0
+    }
+    set_property IS_ROUTE_FIXED 1 $net
+    if {[get_property ROUTE_STATUS $net] != "ROUTED"} {
+        set_property IS_ROUTE_FIXED 0 $net
+        route_design -unroute -nets $net
+        return 0
+    }
+    return 1
+}
+
+# https://wiki.tcl-lang.org/page/Shuffle+a+list
+proc shuffle6 { list } {
+     set n [llength $list]
+     for { set i 1 } { $i < $n } { incr i } {
+         set j [expr { int( rand() * $n ) }]
+         set temp [lindex $list $i]
+         lset list $i [lindex $list $j]
+         lset list $j $temp
+     }
+     return $list
+ }
+
+set tile_types [list "CMT_L" "CMT_L" "CMT_L" "RCLK_INT_L" "RCLK_RCLK_XIPHY_INNER_FT"]
+
+set run $::argv
+create_project -force -part xczu7ev-ffvf1517-2-e design${run} design${run}
+
+
+link_design
+create_cell -reference LUT6 "misc_lut"
+place_design
+route_design
+
+#remove_cell [get_cells]
+#remove_net [get_nets]
+
+set count [expr {int(110 + rand()*80)}]
+
+for {set i 0} {$i < $count} {incr i} {
+    puts "$i/$count"
+    set tt [lindex $tile_types [expr {int(rand()*[llength $tile_types])}]]
+    for {set j 0} {$j < 10} {incr j} {
+        set tiles [get_tiles -filter "TILE_TYPE == $tt"]
+        set tile [lindex $tiles [expr {int(rand()*[llength $tiles])}]]
+        set tile_pips [get_pips -of_objects $tile -filter {NAME !~ "*VCC_WIRE*"}]
+        set pip [lindex $tile_pips [expr {int(rand()*[llength $tile_pips])}] "*CLK_LEAF*"]
+        set wavefront_bwd $pip
+        set driver {}
+        for {set k 0} {$k < 10} {incr k} {
+            set wavefront_bwd [get_nodes -uphill -of_objects $wavefront_bwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.5 || [llength [get_nodes -uphill -of_objects $wavefront_bwd]] == 0} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_bwd]]
+                foreach pin $wavefront_pins {
+                    set driver [make_site_pin_driver $pin]
+                    if {$driver != {}} {
+                        break
+                    }
+                }
+            }
+            if {$driver != {}} {
+                break
+            }
+            if {[llength $wavefront_bwd] > 20000} {
+                break
+            }
+        }
+
+        if {$driver == {}} {
+            continue
+        }
+
+        set drv_cell [lindex $driver 0]
+        set net [lindex $driver 1]
+
+        set wavefront_fwd $pip
+        set user {}
+        for {set k 0} {$k < 10} {incr k} {
+            set wavefront_fwd [get_nodes -downhill -of_objects $wavefront_fwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.5 || [llength [get_nodes -downhill -of_objects $wavefront_fwd]] == 0} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_fwd]]
+                foreach pin $wavefront_pins {
+                    set user [make_site_pin_user $pin $net]
+                    set sink_pin $pin
+                    if {$user != {}} {
+                        break
+                    }
+                }
+            }
+            if {$user != {}} {
+                break
+            }
+            if {[llength $wavefront_fwd] > 20000} {
+                break
+            }
+        }
+
+        if {$user == {}} {
+            if {[llength [get_nets -of_objects $drv_cell]] == 1} {
+                remove_cell $drv_cell
+            }
+            remove_net $net
+            continue
+        }
+
+        set success [route_via $net $sink_pin $pip]
+        if { $success == 0 } {
+            if {[llength [get_nets -of_objects $drv_cell]] == 1} {
+                remove_cell $drv_cell
+            }
+            set user_cell [lindex $user 0]
+            if {[llength [get_nets -of_objects $user_cell]] == 1} {
+                remove_cell $user_cell
+            }
+            remove_net $net
+        } else {
+            break
+        }
+    }
+}
+
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+set_property SEVERITY {Warning} [get_drc_checks]
+write_checkpoint -force ../specimen_clk/rclkroute_pll${run}.dcp
+write_edif -force ../specimen_clk/rclkroute_pll${run}.edf
+write_bitstream -force ../specimen_clk/rclkroute_pll${run}.bit
+
diff --git a/spec/rclk_routing_fuzz_io.tcl b/spec/rclk_routing_fuzz_io.tcl
new file mode 100644
index 0000000..e7ba73e
--- /dev/null
+++ b/spec/rclk_routing_fuzz_io.tcl
@@ -0,0 +1,274 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+proc make_driver_cell {name celltype loc pinname} {
+    create_net "${name}_net"
+    set net [get_nets "${name}_net"]
+    create_cell -reference $celltype "${name}_drv"
+    place_cell "${name}_drv" $loc
+    connect_net -net $net -objects [get_pins "${name}_drv/${pinname}"]
+    return [list [get_cells "${name}_drv"] $net]
+}
+
+proc make_user_cell {name celltype loc pinname net} {
+    create_cell -reference $celltype "${name}_usr"
+    place_cell "${name}_usr" $loc
+    connect_net -net $net -objects [get_pins "${name}_usr/${pinname}"]
+    return [list [get_cells "${name}_usr"] $net]
+}
+
+
+proc make_site_pin_driver {pin} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_ROW" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_ROW_FSR" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_LEAF" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_OUT"} {
+        set result [make_driver_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "O"]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "O"]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFG_GT" "$site/BUFG_GT" "O"]
+    } elseif {($sitetype == "SLICEL" || $sitetype == "SLICEM") && [string range $pinname 1 4] == "MUX"} {
+        set eighth [string range $pinname 0 0]
+        return [make_driver_cell $objname "LUT6" "$site/${eighth}6LUT" "O"]
+    } else {
+        return {}
+    }
+}
+
+proc make_site_pin_user {pin net} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFCE_LEAF" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFCE_LEAF" && $pinname == "CE_INT"} {
+        return [make_user_cell $objname "BUFCE_LEAF" "$site/BUFCE" "CE" $net]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFGCE" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFGCE" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_IN"} {
+        set result [make_user_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "I" $net]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I0"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I0" $net]
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I1"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I1" $net]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFG_GT" "$site/BUFG_GT" "I" $net]
+    } elseif {$sitetype == "SLICEM" && $pinname == "LCLK"} {
+        return [make_user_cell $objname "SRL16E" "$site/H6LUT" "CLK" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK1"} {
+        return [make_user_cell $objname "FDRE" "$site/AFF" "C" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK2"} {
+        return [make_user_cell $objname "FDRE" "$site/EFF" "C" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK"} {
+        return [make_user_cell $objname "IDELAYE3" "$site/IDELAY" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK_C"} {
+        return [make_user_cell $objname "ISERDESE3" "$site/ISERDES" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLKDIV"} {
+        return [make_user_cell $objname "ISERDESE3" "$site/ISERDES" "CLKDIV" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "TX_OCLK"} {
+        return [make_user_cell $objname "OSERDESE3" "$site/OSERDES" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "TX_OCLKDIV"} {
+        return [make_user_cell $objname "OSERDESE3" "$site/OSERDES" "CLKDIV" $net]
+    } else {
+        return {}
+    }
+}
+
+proc route_via {net sink_pin pip} {
+    set success 0
+    set src [lindex [get_nodes -of_objects [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]] 0]
+    set sink [get_nodes -of_objects [get_site_pins -filter {DIRECTION == IN} -of_objects $net ]]
+    if {$sink == {}} {
+        set sink $sink_pin
+    }
+    set_property IS_ROUTE_FIXED 0 $net
+    #route_design -unroute -nets $net
+
+    set from_node [get_nodes -uphill -of_objects $pip]
+    set to_node [get_nodes -downhill -of_objects $pip]
+    if {$src == $from_node} {
+        set route_a $from_node
+    } else {
+        if {[catch {set route_a [find_routing_path -max_nodes 15 -from $src -to $from_node]}]} {
+            return 0
+        }
+        if {$route_a == ""} {
+            return 0
+        }
+    }
+    if {$sink == $to_node} {
+        set route_b $to_node
+    } else {
+        if {[catch {set route_b [find_routing_path -max_nodes 15  -from $to_node -to $sink]}]} {
+            return 0
+        }
+        if {$route_b == ""} {
+            return 0
+        }
+    }
+
+    if {[catch {set_property FIXED_ROUTE "$route_a $route_b" $net}]} {
+        return 0
+    }
+    set_property IS_ROUTE_FIXED 1 $net
+    if {[get_property ROUTE_STATUS $net] != "ROUTED"} {
+        set_property IS_ROUTE_FIXED 0 $net
+        route_design -unroute -nets $net
+        return 0
+    }
+    return 1
+}
+
+# https://wiki.tcl-lang.org/page/Shuffle+a+list
+proc shuffle6 { list } {
+     set n [llength $list]
+     for { set i 1 } { $i < $n } { incr i } {
+         set j [expr { int( rand() * $n ) }]
+         set temp [lindex $list $i]
+         lset list $i [lindex $list $j]
+         lset list $j $temp
+     }
+     return $list
+ }
+
+set tile_types [list "RCLK_RCLK_XIPHY_INNER_FT" "RCLK_HDIO" "RCLK_RCLK_URAM_INTF_L_FT"]
+
+set run $::argv
+create_project -force -part xczu7ev-ffvf1517-2-e design${run} design${run}
+
+
+link_design
+create_cell -reference LUT6 "misc_lut"
+place_design
+route_design
+
+#remove_cell [get_cells]
+#remove_net [get_nets]
+
+set count [expr {int(90 + rand()*80)}]
+
+for {set i 0} {$i < $count} {incr i} {
+    puts "$i/$count"
+    set tt [lindex $tile_types [expr {int(rand()*[llength $tile_types])}]]
+    set tiles [get_tiles -filter "TILE_TYPE == $tt"]
+    for {set j 0} {$j < 10} {incr j} {
+        set tile [lindex $tiles [expr {int(rand()*[llength $tiles])}]]
+        set tile_pips [get_pips -of_objects $tile -filter {NAME !~ "*VCC_WIRE*"}]
+        set pip [lindex $tile_pips [expr {int(rand()*[llength $tile_pips])}]]
+        set wavefront_bwd $pip
+        set driver {}
+        for {set k 0} {$k < 11} {incr k} {
+            set wavefront_bwd [get_nodes -uphill -of_objects $wavefront_bwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.25} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_bwd]]
+                foreach pin $wavefront_pins {
+                    set driver [make_site_pin_driver $pin]
+                    if {$driver != {}} {
+                        break
+                    }
+                }
+            }
+            if {$driver != {}} {
+                break
+            }
+            if {[llength $wavefront_bwd] > 20000} {
+                break
+            }
+        }
+
+        if {$driver == {}} {
+            continue
+        }
+
+        set drv_cell [lindex $driver 0]
+        set net [lindex $driver 1]
+
+        set wavefront_fwd $pip
+        set user {}
+        for {set k 0} {$k < 12} {incr k} {
+            set wavefront_fwd [get_nodes -downhill -of_objects $wavefront_fwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.25} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_fwd]]
+                foreach pin $wavefront_pins {
+                    set user [make_site_pin_user $pin $net]
+                    set sink_pin $pin
+                    if {$user != {}} {
+                        break
+                    }
+                }
+            }
+            if {$user != {}} {
+                break
+            }
+            if {[llength $wavefront_fwd] > 20000} {
+                break
+            }
+        }
+
+        if {$user == {}} {
+            remove_cell $drv_cell
+            remove_net $net
+            continue
+        }
+
+        set success [route_via $net $sink_pin $pip]
+        if { $success == 0 } {
+            remove_cell $drv_cell
+            remove_cell [lindex $user 0]
+            remove_net $net
+        } else {
+            break
+        }
+    }
+}
+
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+set_property SEVERITY {Warning} [get_drc_checks]
+write_checkpoint -force ../specimen_clk/rclkroute_c${run}.dcp
+write_edif -force ../specimen_clk/rclkroute_c${run}.edf
+write_bitstream -force ../specimen_clk/rclkroute_c${run}.bit
+
diff --git a/spec/routing_fuzz.tcl b/spec/routing_fuzz.tcl
new file mode 100644
index 0000000..3d7cf98
--- /dev/null
+++ b/spec/routing_fuzz.tcl
@@ -0,0 +1,74 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+set nets [get_nets fz*]
+set tiles [get_tiles -filter {TILE_TYPE == "RCLK_CLEM_R" || TILE_TYPE == "RCLK_CLEM_L" || TILE_TYPE == "RCLK_CLEL_R" || TILE_TYPE == "RCLK_CLEL_L"}]
+for {set k 0} {$k < 30} {incr k} {
+foreach net $nets {
+	set success 0
+	set src [lindex [get_nodes -of_objects [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]] 0]
+	set sink [get_nodes -of_objects [get_site_pins -filter {DIRECTION == IN} -of_objects $net ]]
+	set_property IS_ROUTE_FIXED 0 $net
+	route_design -unroute -nets $net
+	for {set i 0} {$i < 12} {incr i} {
+		# try and route using an interesting pip
+		set tile [lindex $tiles [expr {int(rand()*[llength $tiles])}]]
+		set tile_pips [get_pips -of_objects $tile -filter {NAME !~ "*VCC_WIRE*"}]
+		set pip [lindex $tile_pips [expr {int(rand()*[llength $tile_pips])}]]
+		puts $pip
+		set from_node [get_nodes -uphill -of_objects $pip]
+		set to_node [get_nodes -downhill -of_objects $pip]
+		puts "$k $i $net $src $from_node $to_node $sink"
+		# pre-reject dud pips
+		if {[llength [get_nodes -uphill -of_objects $from_node]] == 0 ||  [llength [get_nodes -downhill -of_objects $to_node]] == 0} {
+			continue
+		}
+		if {[catch {set route_a [find_routing_path -max_nodes 60 -from $src -to $from_node]}]} {
+			continue
+		}
+		if {$route_a == ""} {
+			continue
+		}
+		if {[catch {set route_b [find_routing_path -max_nodes 60  -from $to_node -to $sink]}]} {
+			continue
+		}
+		if {$route_b == ""} {
+			continue
+		}
+		if {[catch {set_property FIXED_ROUTE "$route_a $route_b" $net}]} {
+			continue
+		}
+		set_property IS_ROUTE_FIXED 1 $net
+		if {[get_property ROUTE_STATUS $net] != "ROUTED"} {
+			set_property IS_ROUTE_FIXED 0 $net
+			route_design -unroute -nets $net
+			continue
+		}
+		set success 1
+		break
+	}
+	if {$success == 0} {
+		# route simply\\\\\\
+		set_property FIXED_ROUTE [find_routing_path -from $src -to $sink] $net
+	}
+}
+set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
+set_property SEVERITY {Warning} [get_drc_checks UCIO-1]
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+
+write_edif -force "./results/specimen_clk/rclkroute$k.edf"
+write_checkpoint -force "./results/specimen_clk/rclkroute$k.dcp"
+write_bitstream -force "./results/specimen_clk/rclkroute$k.bit"
+
+}
\ No newline at end of file
diff --git a/spec/routing_fuzz_simple.tcl b/spec/routing_fuzz_simple.tcl
new file mode 100644
index 0000000..5aaf81d
--- /dev/null
+++ b/spec/routing_fuzz_simple.tcl
@@ -0,0 +1,273 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+proc make_driver_cell {name celltype loc pinname} {
+    create_net "${name}_net"
+    set net [get_nets "${name}_net"]
+    create_cell -reference $celltype "${name}_drv"
+    place_cell "${name}_drv" $loc
+    connect_net -net $net -objects [get_pins "${name}_drv/${pinname}"]
+    return [list [get_cells "${name}_drv"] $net]
+}
+
+proc make_user_cell {name celltype loc pinname net} {
+    create_cell -reference $celltype "${name}_usr"
+    place_cell "${name}_usr" $loc
+    connect_net -net $net -objects [get_pins "${name}_usr/${pinname}"]
+    return [list [get_cells "${name}_usr"] $net]
+}
+
+
+proc make_site_pin_driver {pin} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_ROW" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_ROW_FSR" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_ROW" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFCE_LEAF" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_HDIO" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCE" "$site/BUFCE" "O"]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_OUT"} {
+        set result [make_driver_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "O"]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "O"]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_OUT"} {
+        return [make_driver_cell $objname "BUFG_GT" "$site/BUFG_GT" "O"]
+    } elseif {($sitetype == "SLICEL" || $sitetype == "SLICEM") && [string range $pinname 1 4] == "MUX"} {
+        set eighth [string range $pinname 0 0]
+        return [make_driver_cell $objname "LUT6" "$site/${eighth}6LUT" "O"]
+    } else {
+        return {}
+    }
+}
+
+proc make_site_pin_user {pin net} {
+    set site [get_sites -of_objects $pin]
+    if { $site == {} } {
+        return {}
+    }
+    set sitetype [get_property SITE_TYPE $site]
+    set pinname [lindex [split $pin "/"] 1]
+    set objname "${site}_${pinname}"
+    if {[get_cells -of_objects $site] != {}} {
+        return {}
+    }
+    if {$sitetype == "BUFCE_LEAF" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFCE_LEAF" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFGCE" "$site/BUFCE" "I" $net]
+    } elseif {$sitetype == "BUFGCE_DIV" && $pinname == "CLK_IN"} {
+        set result [make_user_cell $objname "BUFGCE_DIV" "$site/BUFGCE_DIV" "I" $net]
+        set_property BUFGCE_DIVIDE 1 [lindex $result 0]
+        return $result
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I0"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I0" $net]
+    } elseif {$sitetype == "BUFGCTRL" && $pinname == "CLK_I1"} {
+        return [make_user_cell $objname "BUFGCTRL" "$site/BUFGCTRL" "I1" $net]
+    } elseif {$sitetype == "BUFG_GT" && $pinname == "CLK_IN"} {
+        return [make_user_cell $objname "BUFG_GT" "$site/BUFG_GT" "I" $net]
+    } elseif {$sitetype == "SLICEM" && $pinname == "LCLK"} {
+        return [make_user_cell $objname "SRL16E" "$site/H6LUT" "CLK" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK1"} {
+        return [make_user_cell $objname "FDRE" "$site/AFF" "C" $net]
+    } elseif {($sitetype == "SLICEM" || $sitetype == "SLICEL") && $pinname == "CLK2"} {
+        return [make_user_cell $objname "FDRE" "$site/EFF" "C" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK"} {
+        return [make_user_cell $objname "IDELAYE3" "$site/IDELAY" "CLK" $net]
+    } elseif {$sitetype == "BITSLICE_RX_TX" && $pinname == "RX_CLK_C"} {
+        return [make_user_cell $objname "ISERDESE3" "$site/ISERDES" "CLK" $net]
+    } else {
+        return {}
+    }
+}
+
+proc route_via {net sink_pin pip} {
+    set success 0
+    set src [lindex [get_nodes -of_objects [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]] 0]
+    set sink [get_nodes -of_objects [get_site_pins -filter {DIRECTION == IN} -of_objects $net ]]
+    if {$sink == {}} {
+        set sink $sink_pin
+    }
+    set_property IS_ROUTE_FIXED 0 $net
+    #route_design -unroute -nets $net
+
+    set from_node [get_nodes -uphill -of_objects $pip]
+    set to_node [get_nodes -downhill -of_objects $pip]
+    if {$src == $from_node} {
+        set route_a $from_node
+    } else {
+        if {[catch {set route_a [find_routing_path -max_nodes 15 -from $src -to $from_node]}]} {
+            return 0
+        }
+        if {$route_a == ""} {
+            return 0
+        }
+    }
+    if {$sink == $to_node} {
+        set route_b $to_node
+    } else {
+        if {[catch {set route_b [find_routing_path -max_nodes 15  -from $to_node -to $sink]}]} {
+            return 0
+        }
+        if {$route_b == ""} {
+            return 0
+        }
+    }
+
+    if {[catch {set_property FIXED_ROUTE "$route_a $route_b" $net}]} {
+        return 0
+    }
+    set_property IS_ROUTE_FIXED 1 $net
+    if {[get_property ROUTE_STATUS $net] != "ROUTED"} {
+        set_property IS_ROUTE_FIXED 0 $net
+        route_design -unroute -nets $net
+        return 0
+    }
+    return 1
+}
+
+# https://wiki.tcl-lang.org/page/Shuffle+a+list
+proc shuffle6 { list } {
+     set n [llength $list]
+     for { set i 1 } { $i < $n } { incr i } {
+         set j [expr { int( rand() * $n ) }]
+         set temp [lindex $list $i]
+         lset list $i [lindex $list $j]
+         lset list $j $temp
+     }
+     return $list
+ }
+
+set tile_types [list "CMT_L" "CMT_L" "CMT_L" "RCLK_BRAM_INTF_L" "RCLK_BRAM_INTF_TD_L" "RCLK_BRAM_INTF_TD_R"\
+                    "RCLK_CLEL_L_L" "RCLK_CLEL_L_R" "RCLK_CLEM_CLKBUF_L" "RCLK_CLEM_L" "RCLK_CLEM_R"\
+                        "RCLK_DSP_INTF_L" "RCLK_DSP_INTF_CLKBUF_L" "RCLK_INT_L" "RCLK_RCLK_XIPHY_INNER_FT"]
+
+set run $::argv
+create_project -force -part xczu7ev-ffvf1517-2-e design${run} design${run}
+
+
+link_design
+create_cell -reference LUT6 "misc_lut"
+place_design
+route_design
+
+#remove_cell [get_cells]
+#remove_net [get_nets]
+
+set count [expr {int(90 + rand()*70)}]
+
+for {set i 0} {$i < $count} {incr i} {
+    puts "$i/$count"
+    for {set j 0} {$j < 10} {incr j} {
+        set tt [lindex $tile_types [expr {int(rand()*[llength $tile_types])}]]
+        set tiles [get_tiles -filter "TILE_TYPE == $tt"]
+        set tile [lindex $tiles [expr {int(rand()*[llength $tiles])}]]
+        set tile_pips [get_pips -of_objects $tile -filter {NAME !~ "*VCC_WIRE*"}]
+        set pip [lindex $tile_pips [expr {int(rand()*[llength $tile_pips])}]]
+        set wavefront_bwd $pip
+        set driver {}
+        for {set k 0} {$k < 10} {incr k} {
+            set wavefront_bwd [get_nodes -uphill -of_objects $wavefront_bwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.5} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_bwd]]
+                foreach pin $wavefront_pins {
+                    set driver [make_site_pin_driver $pin]
+                    if {$driver != {}} {
+                        break
+                    }
+                }
+            }
+            if {$driver != {}} {
+                break
+            }
+            if {[llength $wavefront_bwd] > 20000} {
+                break
+            }
+        }
+
+        if {$driver == {}} {
+            continue
+        }
+
+        set drv_cell [lindex $driver 0]
+        set net [lindex $driver 1]
+
+        set wavefront_fwd $pip
+        set user {}
+        for {set k 0} {$k < 10} {incr k} {
+            set wavefront_fwd [get_nodes -downhill -of_objects $wavefront_fwd]
+            # Randomly skip to vary depth
+            if {rand() > 0.5} {
+                set wavefront_pins [shuffle6 [get_site_pins -of_objects $wavefront_fwd]]
+                foreach pin $wavefront_pins {
+                    set user [make_site_pin_user $pin $net]
+                    set sink_pin $pin
+                    if {$user != {}} {
+                        break
+                    }
+                }
+            }
+            if {$user != {}} {
+                break
+            }
+            if {[llength $wavefront_fwd] > 20000} {
+                break
+            }
+        }
+
+        if {$user == {}} {
+            remove_cell $drv_cell
+            remove_net $net
+            continue
+        }
+
+        set success [route_via $net $sink_pin $pip]
+        if { $success == 0 } {
+            remove_cell $drv_cell
+            remove_cell [lindex $user 0]
+            remove_net $net
+        } else {
+            break
+        }
+    }
+}
+
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+set_property SEVERITY {Warning} [get_drc_checks PDCN-*]
+set_property SEVERITY {Warning} [get_drc_checks RTRES-*]
+set_property SEVERITY {Warning} [get_drc_checks AVAL-*]
+set_property SEVERITY {Warning} [get_drc_checks REQP-*]
+set_property SEVERITY {Warning} [get_drc_checks BIVR-*]
+set_property SEVERITY {Warning} [get_drc_checks PDRC-203]
+set_property SEVERITY {Warning} [get_drc_checks ADEF-911]
+
+write_checkpoint -force ../specimen_clk/rclkroute${run}.dcp
+write_edif -force ../specimen_clk/rclkroute${run}.edf
+write_bitstream -force ../specimen_clk/rclkroute${run}.bit
+
diff --git a/spec/slice.v b/spec/slice.v
index ce33903..b049083 100644
--- a/spec/slice.v
+++ b/spec/slice.v
@@ -215,4 +215,4 @@
 			"CY":    assign OUT = CY;
 		endcase		
 	endgenerate
-endmodule
\ No newline at end of file
+endmodule
diff --git a/spec/slice_carry.v b/spec/slice_carry.v
index 4eaeaee..853aef8 100644
--- a/spec/slice_carry.v
+++ b/spec/slice_carry.v
@@ -283,4 +283,4 @@
 			"X":  assign OUT = X;
 		endcase		
 	endgenerate
-endmodule
\ No newline at end of file
+endmodule
diff --git a/spec/slice_memory.v b/spec/slice_memory.v
index efe2fe7..f696882 100644
--- a/spec/slice_memory.v
+++ b/spec/slice_memory.v
@@ -381,4 +381,4 @@
 			$error("unsupported mode");
 		end
 	endgenerate
-endmodule
\ No newline at end of file
+endmodule
diff --git a/spec/uram.py b/spec/uram.py
new file mode 100644
index 0000000..af93e64
--- /dev/null
+++ b/spec/uram.py
@@ -0,0 +1,136 @@
+# Copyright 2020 Project U-Ray Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import sys
+
+X = 12
+N = 60
+
+root = sys.argv[2]
+
+def random_vector(size):
+	return "{%s}" % ", ".join(["d[%d]" % np.random.randint(40) for k in range(size)])
+
+for x in range(X):
+	with open(root + "/uram/uram%d.v" % x, "w") as f:
+		print("module top(input [7:0] clk, cen, rst, input [39:0] d, input [7:0] sel, output [63:0] q);", file=f)
+		print("    wire [8:0] r = {1'b0, rst}, e = {1'b1, cen};", file=f)
+		print("    wire [63:0] int_q[0:%d];" % (N-1), file=f)
+		for i in range(N):
+			print("    (* KEEP, DONT_TOUCH *)", file=f)
+			print("    URAM288 #(", file=f)
+			print("        .AUTO_SLEEP_LATENCY(%d)," % np.random.randint(3, 16), file=f)
+			print("        .BWE_MODE_A(\"%s\")," % np.random.choice(["PARITY_INTERLEAVED", "PARITY_INDEPENDENT"]), file=f)
+			print("        .BWE_MODE_B(\"%s\")," % np.random.choice(["PARITY_INTERLEAVED", "PARITY_INDEPENDENT"]), file=f)
+			print("        .EN_AUTO_SLEEP_MODE(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .EN_ECC_RD_A(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .EN_ECC_RD_B(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .EN_ECC_WR_A(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .EN_ECC_WR_B(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .IREG_PRE_A(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .IREG_PRE_B(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .IS_CLK_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_EN_A_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_EN_B_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RDB_WR_A_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RDB_WR_B_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RST_A_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RST_B_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .OREG_A(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .OREG_B(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .OREG_ECC_A(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .OREG_ECC_B(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .REG_CAS_A(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .REG_CAS_B(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .RST_MODE_A(\"%s\")," % np.random.choice(["SYNC", "ASYNC"]), file=f)
+			print("        .RST_MODE_B(\"%s\")," % np.random.choice(["SYNC", "ASYNC"]), file=f)
+			print("        .SELF_ADDR_A(11'd%d)," % np.random.randint(2**11), file=f)
+			print("        .SELF_ADDR_B(11'd%d)," % np.random.randint(2**11), file=f)
+			print("        .SELF_MASK_A(11'd%d)," % np.random.randint(2**11), file=f)
+			print("        .SELF_MASK_B(11'd%d)," % np.random.randint(2**11), file=f)
+			print("        .USE_EXT_CE_A(\"%s\")," % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("        .USE_EXT_CE_B(\"%s\")" % np.random.choice(["FALSE", "TRUE"]), file=f)
+			print("    ) uram_%d (" % i, file=f)
+			print("        .ADDR_A(%s)," % random_vector(23), file=f)
+			print("        .ADDR_B(%s)," % random_vector(23), file=f)
+			print("        .BWE_A(%s)," % random_vector(9), file=f)
+			print("        .BWE_B(%s)," % random_vector(9), file=f)
+			print("        .CLK(clk[%d])," % np.random.randint(8), file=f)
+			print("        .DIN_A(%s)," % random_vector(72), file=f)
+			print("        .DIN_B(%s)," % random_vector(72), file=f)
+			print("        .EN_A(e[%d])," % np.random.randint(9), file=f)
+			print("        .EN_B(e[%d])," % np.random.randint(9), file=f)
+			print("        .INJECT_DBITERR_A(%s)," % random_vector(1), file=f)
+			print("        .INJECT_DBITERR_B(%s)," % random_vector(1), file=f)
+			print("        .INJECT_SBITERR_A(%s)," % random_vector(1), file=f)
+			print("        .INJECT_SBITERR_B(%s)," % random_vector(1), file=f)
+			print("        .OREG_CE_A(e[%d])," % np.random.randint(9), file=f)
+			print("        .OREG_CE_B(e[%d])," % np.random.randint(9), file=f)
+			print("        .OREG_ECC_CE_A(e[%d])," % np.random.randint(9), file=f)
+			print("        .OREG_ECC_CE_B(e[%d])," % np.random.randint(9), file=f)
+			print("        .RDB_WR_A(%s)," % random_vector(1), file=f)
+			print("        .RDB_WR_B(%s)," % random_vector(1), file=f)
+			print("        .RST_A(r[%d])," % np.random.randint(9), file=f)
+			print("        .RST_B(r[%d])," % np.random.randint(9), file=f)
+			o_bit = np.random.randint(-1, 150)
+			if o_bit < 72:
+				print("        .DOUT_A({int_q[%d][0]%s})," % (i, ", {%d{1'bx}}" % o_bit if o_bit > 0 else ""), file=f)
+			elif o_bit < 144:
+				print("        .DOUT_A({int_q[%d][0]%s})," % (i, ", {%d{1'bx}}" % (o_bit-72) if o_bit > 72 else ""), file=f)
+			elif o_bit == 144:
+				print("        .RDACCESS_A(int_q[%d][0])," % i, file=f)
+			elif o_bit == 145:
+				print("        .RDACCESS_B(int_q[%d][0])," % i, file=f)
+			elif o_bit == 146:
+				print("        .DBITERR_A(int_q[%d][0])," % i, file=f)
+			elif o_bit == 147:
+				print("        .DBITERR_B(int_q[%d][0])," % i, file=f)
+			elif o_bit == 148:
+				print("        .SBITERR_A(int_q[%d][0])," % i, file=f)
+			elif o_bit == 149:
+				print("        .SBITERR_B(int_q[%d][0])," % i, file=f)
+			print("        .SLEEP(%s)" % random_vector(1), file=f)
+			print(");", file=f)
+		print("    assign q = int_q[sel];", file=f)
+		print("endmodule", file=f)
+	with open(root + "/uram/uram%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/uram/uram%d.v" % x)), file=f)
+		print("synth_design -top top -part xczu7ev-ffvc1156-2-e", file=f)
+		print("opt_design", file=f)
+		print("place_design", file=f)
+		print("route_design", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks NSTD-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks UCIO-1]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks AVAL-*]", file=f)
+		print("set_property SEVERITY {Warning} [get_drc_checks REQP-*]", file=f)
+		print("set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_bram/uram%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_bram/uram%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_bram/uram%d.bit" % (root, x), file=f)
+with open(root + "/uram/run.sh", "w") as f:
+	print("#/usr/bin/env bash", file=f)
+	print("set -ex", file=f)
+	for x in range(X):
+		print("vivado -mode batch -nolog -nojournal -source uram%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_bram/uram%d.bit %s/frames.txt > %s/specimen_bram/uram%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tilebits.json %s/specimen_bram/uram%d.dump > %s/specimen_bram/uram%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_bram/uram%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_bram/uram%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_bram/uram%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_bram/uram%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_bram/uram%d.features" % (root, x), file=f)
+		print("fi", file=f)
diff --git a/spec/xiphy_clk_mux.tcl b/spec/xiphy_clk_mux.tcl
new file mode 100644
index 0000000..cd1273c
--- /dev/null
+++ b/spec/xiphy_clk_mux.tcl
@@ -0,0 +1,72 @@
+// Copyright 2020 Project U-Ray Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+set run $::argv
+create_project -force -part xczu7ev-ffvf1517-2-e design${run} design${run}
+
+
+link_design
+create_cell -reference LUT6 "misc_lut"
+place_design
+route_design
+
+remove_net xiphy_*
+remove_cell xiphy_*
+
+foreach tile [get_tiles -filter {TILE_TYPE == RCLK_RCLK_XIPHY_INNER_FT}] {
+	set i 0
+	foreach pip [get_pips -uphill -of_objects [get_wires "$tile/CLK_TO_XIPHY_BYTES_TOP0" -of_objects $tile]] {
+		set drv_node [get_nodes -uphill -of_objects $pip]
+		puts $drv_node
+		set pins [get_site_pins -of_objects [get_nodes -uphill -of_objects $drv_node] "BUFCE_ROW*"]
+		if {[llength $pins] != 1} {
+			continue
+		}
+		create_net "xiphy_${tile}_clk${i}"
+		set net [get_nets "xiphy_${tile}_clk${i}"]
+		create_cell -reference BUFCE_ROW "xiphy_${tile}_clk${i}_buf"
+		set site [get_sites -of_objects [lindex $pins 0]]
+		place_cell "xiphy_${tile}_clk${i}_buf" "${site}/BUFCE"
+		connect_net -net $net -objects [get_pins "xiphy_${tile}_clk${i}_buf/O"]
+		incr i
+	}
+	foreach sink_node [get_nodes -downhill -of_objects [get_nodes "$tile/CLK_HDISTR_ONE0"] "*CLK_TO_XIPHY*"] {
+		puts $sink_node
+		foreach pin [get_site_pins -of_objects [get_nodes -downhill -of_objects $sink_node] "BITSLICE_RX_TX*/*TX_OCLK"] {
+			set site [get_sites -of_objects $pin]
+			if {[llength [get_cells -of_objects $site]] > 0 } {
+				continue
+			}
+			create_cell -reference OSERDESE3 "xiphy_sink_${site}"
+			place_cell "xiphy_sink_${site}" "${site}/OSERDES"
+			while {1} {
+				set net_idx [expr int(rand() * $i)]
+				if {[lsearch [get_nets -of_objects [get_tiles -of_objects $site]] "xiphy_${tile}_clk${net_idx}"] == -1} {
+					break
+				}
+			}
+			set net [get_nets "xiphy_${tile}_clk${net_idx}"]
+			connect_net -net $net -objects [get_pins "xiphy_sink_${site}/CLK"]
+			break
+		}
+	}
+}
+
+set_property SEVERITY Warning [get_drc_checks]
+route_design
+set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
+write_checkpoint -force ../specimen_clk/xclkroute_a${run}.dcp
+write_edif -force ../specimen_clk/xclkroute_a${run}.edf
+write_bitstream -force ../specimen_clk/xclkroute_a${run}.bit
+