Initial Project U-Ray code.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e046f6c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+database/*
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..6652bd4
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,13 @@
+# This is the list of Project U-Ray significant contributors.
+#
+# This does not necessarily list everyone who has contributed code,
+# especially since many employees of one corporation may be contributing.
+# To see the full list of contributors, see the revision history in
+# source control.
+
+# Companies
+Google LLC
+Antmicro Sp. z o. o.
+
+# Individuals
+tansell@google.com, me@mith.ro (Tim 'mithro' Ansell)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4337305
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+Project U-Ray
+=============
+
+Project U-Ray is an attempt at documenting the bitstream format for the
+[Xilinx Ultrascale and Ultrascale+ parts](https://www.xilinx.com/products/technology/ultrascale.html)
+including all parts from the following lines;
+ * Kintex Ultrascale
+ * Virtex Ultrascale
+ * Zynq UltraScale MPSoC
+ * Kintex UltraScale+
+ * Virtex UltraScale+
+ * Zynq UltraScale+ MPSoC
+
+It takes a lot of the learning from
+[Project X-Ray](https://github.com/SymbiFlow/prjxray) and
+[Project Trellis](https://github.com/SymbiFlow/prjtrellis).
+
+The initial targets parts and boards are;
+ * [Ultra96-V2 Zynq UltraScale+ ZU3EG Development Board (ULTRA96-V2-G)](https://www.avnet.com/shop/us/products/avnet-engineering-services/aes-ultra96-v2-g-3074457345638646173/) - Xilinx Zynq UltraScale+ MPSoC ZU3EG - $USD249
+ * [Genesys ZU: Zynq Ultrascale+ MPSoC Development Board](https://store.digilentinc.com/genesys-zu-zynq-ultrascale-mpsoc-development-board/) - Xilinx Zynq UltraScale+ MPSoC ZU3EG - $USD1,149
+
+
diff --git a/spec/bram18.py b/spec/bram18.py
new file mode 100644
index 0000000..bdb537b
--- /dev/null
+++ b/spec/bram18.py
@@ -0,0 +1,83 @@
+# 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
+import math
+
+print("module top(input clk, clkb, rst, ena, enb, cea, ceb, input [13:0] ra, wa, input [3:0] we, input [17:0] wdata, input [10:0] sel, output [35:0] rdata);")
+N = 600
+print("    wire [35:0] int_d[0:%d-1];" % N)
+print("    assign rdata = sel[0] ? int_d[sel[10:1]][35:18] : int_d[sel[10:1]][17:0];")
+for i in range(N):
+	write_width_b = np.random.choice([0, 1, 2, 4, 9, 18, 36])
+	read_width_a = np.random.choice([0, 1, 2, 4, 9, 18, 36], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.45, 0.1])
+	write_width_a = 0 if (write_width_b == 36 or read_width_a == 36) else np.random.choice([0, 1, 2, 4, 9, 18])
+	read_width_b = 0 if (read_width_a == 36 or write_width_b == 36) else np.random.choice([0, 1, 2, 4, 9, 18], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.55])
+
+	cd = np.random.choice(["INDEPENDENT", "COMMON"])
+
+	print("    RAMB18E2 #(")
+	print("        .CLOCK_DOMAINS(\"%s\")," % cd)
+	print("        .READ_WIDTH_A(%d)," % read_width_a)
+	print("        .READ_WIDTH_B(%d)," % read_width_b)
+	print("        .WRITE_WIDTH_A(%d)," % write_width_a)
+	print("        .WRITE_WIDTH_B(%d)," % write_width_b)
+	print("        .WRITE_MODE_A(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .WRITE_MODE_B(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .CASCADE_ORDER_A(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .CASCADE_ORDER_B(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .DOA_REG(%d)," % (0 if (write_width_b == 36 or read_width_a == 36) else np.random.randint(2)))
+	print("        .DOB_REG(%d),"% (0 if (write_width_b == 36 or read_width_a == 36) else np.random.randint(2)))
+	print("        .ENADDRENA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .ENADDRENB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RSTREG_PRIORITY_A(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .RSTREG_PRIORITY_B(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .SLEEP_ASYNC(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	for pin in ("CLKARDCLK", "CLKBWRCLK", "ENARDEN", "ENBWREN", "RSTRAMARSTRAM", "RSTRAMB", "RSTREGARSTREG", "RSTREGB"):
+		print("        .IS_%s_INVERTED(%d)," % (pin, np.random.randint(2)))
+	print("        .INIT_A(18'd%d)," % np.random.randint(2**18))
+	print("        .INIT_B(18'd%d)," % np.random.randint(2**18))
+	print("        .SRVAL_A(18'd%d)," % np.random.randint(2**18))
+	print("        .SRVAL_B(18'd%d)" % np.random.randint(2**18))
+	print("   ) ram%d (" % i)
+	print("        .DINADIN(wdata[15:0]),")
+	print("        .DINPADINP(wdata[17:16]),")
+	print("        .DOUTADOUT(int_d[%d][15:0])," % i)
+	print("        .DOUTPADOUTP(int_d[%d][17:16])," % i)
+	print("        .ADDRARDADDR(ra),")
+	print("        .CLKARDCLK(clk),")
+	print("        .ADDRENA(ena),")
+	print("        .ENARDEN(ena),")
+	print("        .REGCEAREGCE(cea),")
+	print("        .RSTRAMARSTRAM(rst),")
+	print("        .RSTREGARSTREG(rst),")
+	print("        .WEA(%s)," % ("2'b00" if write_width_a == 0 else "we[1:0]"))
+	print("        .DINBDIN(wdata[15:0]),")
+	print("        .DINPBDINP(wdata[17:16]),")
+	print("        .DOUTBDOUT(int_d[%d][33:18])," % i)
+	print("        .DOUTPBDOUTP(int_d[%d][35:34])," % i)
+	print("        .ADDRBWRADDR(wa),")
+	print("        .CLKBWRCLK(%s)," % ("clkb" if cd == "INDEPENDENT" else "clk"))
+	print("        .ENBWREN(enb),")
+	print("        .ADDRENB(enb),")
+	print("        .REGCEB(ceb),")
+	print("        .RSTRAMB(rst),")
+	print("        .RSTREGB(rst),")
+	print("        .WEBWE(%s)" % ("2'b00" if write_width_b == 0 else ("we[3:0]" if write_width_b == 36 else "we[3:2]")))
+	print("   );")
+	print()
+print("endmodule")
\ No newline at end of file
diff --git a/spec/bram18_2.py b/spec/bram18_2.py
new file mode 100644
index 0000000..4e7763e
--- /dev/null
+++ b/spec/bram18_2.py
@@ -0,0 +1,89 @@
+# 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
+import math
+
+print("module top(input clk, clkb, rst, ena, enb, cea, ceb, input [13:0] ra, wa, input [3:0] we, input [17:0] wdata, input [10:0] sel, output [35:0] rdata);")
+N = 200
+print("    wire [35:0] int_d[0:%d-1];" % N)
+print("    assign rdata = sel[0] ? int_d[sel[10:1]][35:18] : int_d[sel[10:1]][17:0];")
+for i in range(N):
+	write_width_b = np.random.choice([0, 1, 2, 4, 9, 18, 36])
+	read_width_a = np.random.choice([0, 1, 2, 4, 9, 18, 36], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.45, 0.1])
+	write_width_a = 0 if (write_width_b == 36 or read_width_a == 36) else np.random.choice([0, 1, 2, 4, 9, 18])
+	read_width_b = 0 if (read_width_a == 36 or write_width_b == 36) else np.random.choice([0, 1, 2, 4, 9, 18], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.55])
+
+	cd = np.random.choice(["INDEPENDENT", "COMMON"])
+
+	print("    (* keep, dont_touch *) RAMB18E2 #(")
+	print("        .CLOCK_DOMAINS(\"%s\")," % cd)
+	print("        .READ_WIDTH_A(%d)," % read_width_a)
+	print("        .READ_WIDTH_B(%d)," % read_width_b)
+	print("        .WRITE_WIDTH_A(%d)," % write_width_a)
+	print("        .WRITE_WIDTH_B(%d)," % write_width_b)
+	print("        .WRITE_MODE_A(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .WRITE_MODE_B(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .CASCADE_ORDER_A(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .CASCADE_ORDER_B(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .DOA_REG(%d)," % (0 if (write_width_b == 36 or read_width_a == 36) else np.random.randint(2)))
+	print("        .DOB_REG(%d),"% (0 if (write_width_b == 36 or read_width_a == 36) else np.random.randint(2)))
+	print("        .ENADDRENA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .ENADDRENB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RSTREG_PRIORITY_A(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .RSTREG_PRIORITY_B(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .SLEEP_ASYNC(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	for pin in ("CLKARDCLK", "CLKBWRCLK", "ENARDEN", "ENBWREN", "RSTRAMARSTRAM", "RSTRAMB", "RSTREGARSTREG", "RSTREGB"):
+		print("        .IS_%s_INVERTED(%d)," % (pin, np.random.randint(2)))
+	print("        .INIT_A(18'd%d)," % np.random.randint(2**18))
+	print("        .INIT_B(18'd%d)," % np.random.randint(2**18))
+	print("        .SRVAL_A(18'd%d)," % np.random.randint(2**18))
+	print("        .SRVAL_B(18'd%d)" % np.random.randint(2**18))
+	print("   ) ram%d (" % i)
+	print("        .DINADIN(wdata[15:0]),")
+	print("        .DINPADINP(wdata[17:16]),")
+	douta_len = np.random.randint(0, 19)
+	if douta_len > 0:	
+		print("        .DOUTADOUT(int_d[%d][%d:0])," % (i, min(15, douta_len - 1)))
+	if douta_len > 16:
+		print("        .DOUTPADOUTP(int_d[%d][%d:16])," % (i, douta_len - 1))
+	print("        .ADDRARDADDR(ra),")
+	print("        .CLKARDCLK(clk),")
+	print("        .ADDRENA(ena),")
+	print("        .ENARDEN(ena),")
+	print("        .REGCEAREGCE(cea),")
+	print("        .RSTRAMARSTRAM(rst),")
+	print("        .RSTREGARSTREG(rst),")
+	print("        .WEA(%s)," % ("2'b00" if write_width_a == 0 else "we[1:0]"))
+	print("        .DINBDIN(wdata[15:0]),")
+	print("        .DINPBDINP(wdata[17:16]),")
+	doutb_len = np.random.randint(0, 19)
+	if doutb_len > 0:
+		print("        .DOUTBDOUT(int_d[%d][%d:18])," % (i, min(33, 17 + doutb_len)))
+	if doutb_len > 16:
+		print("        .DOUTPBDOUTP(int_d[%d][%d:34])," % (i, 17 + doutb_len))
+	print("        .ADDRBWRADDR(wa),")
+	print("        .CLKBWRCLK(%s)," % ("clkb" if cd == "INDEPENDENT" else "clk"))
+	print("        .ENBWREN(enb),")
+	print("        .ADDRENB(enb),")
+	print("        .REGCEB(ceb),")
+	print("        .RSTRAMB(rst),")
+	print("        .RSTREGB(rst),")
+	print("        .WEBWE(%s)" % ("2'b00" if write_width_b == 0 else ("we[3:0]" if write_width_b == 36 else "we[3:2]")))
+	print("   );")
+	print()
+print("endmodule")
\ No newline at end of file
diff --git a/spec/bram18_3.py b/spec/bram18_3.py
new file mode 100644
index 0000000..8c26653
--- /dev/null
+++ b/spec/bram18_3.py
@@ -0,0 +1,89 @@
+# 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
+import math
+
+print("module top(input clk, clkb, rst, ena, enb, cea, ceb, input [13:0] ra, wa, input [3:0] we, input [17:0] wdata, input [10:0] sel, output [35:0] rdata);")
+N = 500
+print("    wire [35:0] int_d[0:%d-1];" % N)
+print("    assign rdata = sel[0] ? int_d[sel[10:1]][35:18] : int_d[sel[10:1]][17:0];")
+for i in range(N):
+	write_width_b = np.random.choice([0, 1, 2, 4, 9, 18, 36])
+	read_width_a = np.random.choice([0, 1, 2, 4, 9, 18, 36], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.45, 0.1])
+	write_width_a = 0 if (write_width_b == 36 or read_width_a == 36) else np.random.choice([0, 1, 2, 4, 9, 18])
+	read_width_b = 0 if (read_width_a == 36 or write_width_b == 36) else np.random.choice([0, 1, 2, 4, 9, 18], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.55])
+
+	cd = np.random.choice(["INDEPENDENT", "COMMON"])
+
+	print("    (* keep, dont_touch *) RAMB18E2 #(")
+	print("        .CLOCK_DOMAINS(\"%s\")," % cd)
+	print("        .READ_WIDTH_A(%d)," % read_width_a)
+	print("        .READ_WIDTH_B(%d)," % read_width_b)
+	print("        .WRITE_WIDTH_A(%d)," % write_width_a)
+	print("        .WRITE_WIDTH_B(%d)," % write_width_b)
+	print("        .WRITE_MODE_A(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .WRITE_MODE_B(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .CASCADE_ORDER_A(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .CASCADE_ORDER_B(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .DOA_REG(%d)," % (0 if (write_width_b == 36 or read_width_a == 36) else np.random.randint(2)))
+	print("        .DOB_REG(%d),"% (0 if (write_width_b == 36 or read_width_a == 36) else np.random.randint(2)))
+	print("        .ENADDRENA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .ENADDRENB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RSTREG_PRIORITY_A(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .RSTREG_PRIORITY_B(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .SLEEP_ASYNC(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	for pin in ("CLKARDCLK", "CLKBWRCLK", "ENARDEN", "ENBWREN", "RSTRAMARSTRAM", "RSTRAMB", "RSTREGARSTREG", "RSTREGB"):
+		print("        .IS_%s_INVERTED(%d)," % (pin, np.random.randint(2)))
+	print("        .INIT_A(18'd%d)," % np.random.randint(2**18))
+	print("        .INIT_B(18'd%d)," % np.random.randint(2**18))
+	print("        .SRVAL_A(18'd%d)," % np.random.randint(2**18))
+	print("        .SRVAL_B(18'd%d)" % np.random.randint(2**18))
+	print("   ) ram%d (" % i)
+	print("        .DINADIN(wdata[15:0]),")
+	print("        .DINPADINP(wdata[17:16]),")
+	douta_bit = np.random.randint(-1, 19)
+	if douta_bit >= 0 and douta_bit < 16:	
+		print("        .DOUTADOUT({int_d[%d][0]%s})," % (i, ", {%d{1'bx}}" % douta_bit if douta_bit > 0 else ""))
+	if douta_bit >= 16:
+		print("        .DOUTPADOUTP({int_d[%d][16]%s})," % (i, ", {%d{1'bx}}" % (douta_bit - 16) if douta_bit > 16 else ""))
+	print("        .ADDRARDADDR(ra),")
+	print("        .CLKARDCLK(clk),")
+	print("        .ADDRENA(ena),")
+	print("        .ENARDEN(ena),")
+	print("        .REGCEAREGCE(cea),")
+	print("        .RSTRAMARSTRAM(rst),")
+	print("        .RSTREGARSTREG(rst),")
+	print("        .WEA(%s)," % ("2'b00" if write_width_a == 0 else "we[1:0]"))
+	print("        .DINBDIN(wdata[15:0]),")
+	print("        .DINPBDINP(wdata[17:16]),")
+	doutb_bit = np.random.randint(-1, 19)
+	if doutb_bit >= 0 and doutb_bit < 16:	
+		print("        .DOUTBDOUT({int_d[%d][18]%s})," % (i, ", {%d{1'bx}}" % doutb_bit if doutb_bit > 0 else ""))
+	if doutb_bit >= 16:
+		print("        .DOUTPBDOUTP({int_d[%d][34]%s})," % (i, ", {%d{1'bx}}" % (doutb_bit - 16) if doutb_bit > 16 else ""))
+	print("        .ADDRBWRADDR(wa),")
+	print("        .CLKBWRCLK(%s)," % ("clkb" if cd == "INDEPENDENT" else "clk"))
+	print("        .ENBWREN(enb),")
+	print("        .ADDRENB(enb),")
+	print("        .REGCEB(ceb),")
+	print("        .RSTRAMB(rst),")
+	print("        .RSTREGB(rst),")
+	print("        .WEBWE(%s)" % ("2'b00" if write_width_b == 0 else ("we[3:0]" if write_width_b == 36 else "we[3:2]")))
+	print("   );")
+	print()
+print("endmodule")
\ No newline at end of file
diff --git a/spec/bram36.py b/spec/bram36.py
new file mode 100644
index 0000000..6e6ed40
--- /dev/null
+++ b/spec/bram36.py
@@ -0,0 +1,85 @@
+# 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
+import math
+
+print("module top(input clk, clkb, rst, ena, enb, cea, ceb, input [14:0] ra, wa, input [7:0] we, input [17:0] wdata, input [10:0] sel, output [35:0] rdata);")
+N = 300
+print("    wire [71:0] int_d[0:%d-1];" % N)
+print("    assign rdata = int_d[sel[10:2]][18 * sel[1:0] +: 18];")
+for i in range(N):
+	read_width_a = np.random.choice([0, 1, 2, 4, 9, 18, 36], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.1, 0.45])
+	write_width_a = np.random.choice([0, 1, 2, 4, 9, 18, 36])
+	read_width_b = np.random.choice([0, 1, 2, 4, 9, 18, 36], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.1, 0.45])
+	write_width_b = np.random.choice([0, 1, 2, 4, 9, 18, 36])
+	cd = np.random.choice(["INDEPENDENT", "COMMON"])
+
+	print("    RAMB36E2 #(")
+	print("        .CLOCK_DOMAINS(\"%s\")," % cd)
+	print("        .READ_WIDTH_A(%d)," % read_width_a)
+	print("        .READ_WIDTH_B(%d)," % read_width_b)
+	print("        .WRITE_WIDTH_A(%d)," % write_width_a)
+	print("        .WRITE_WIDTH_B(%d)," % write_width_b)
+	print("        .WRITE_MODE_A(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .WRITE_MODE_B(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .CASCADE_ORDER_A(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .CASCADE_ORDER_B(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .DOA_REG(%d)," % np.random.randint(2))
+	print("        .DOB_REG(%d),"% np.random.randint(2))
+	print("        .ENADDRENA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .ENADDRENB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .EN_ECC_PIPE(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .EN_ECC_READ(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .EN_ECC_WRITE(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .RDADDRCHANGEA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .SLEEP_ASYNC(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RSTREG_PRIORITY_A(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .RSTREG_PRIORITY_B(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	for pin in ("CLKARDCLK", "CLKBWRCLK", "ENARDEN", "ENBWREN", "RSTRAMARSTRAM", "RSTRAMB", "RSTREGARSTREG", "RSTREGB"):
+		print("        .IS_%s_INVERTED(%d)," % (pin, np.random.randint(2)))
+	print("        .INIT_A({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .INIT_B({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .SRVAL_A({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .SRVAL_B({18'd%d, 18'd%d})" % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("   ) ram%d (" % i)
+	print("        .DINADIN({wdata[15:0], wdata[15:0]}),")
+	print("        .DINPADINP({wdata[17:16], wdata[17:16]}),")
+	print("        .DOUTADOUT({int_d[%d][31:0]})," % i)
+	print("        .DOUTPADOUTP({int_d[%d][35:32]})," % i)
+	print("        .ADDRARDADDR(ra),")
+	print("        .CLKARDCLK(clk),")
+	print("        .ADDRENA(ena),")
+	print("        .ENARDEN(ena),")
+	print("        .REGCEAREGCE(cea),")
+	print("        .RSTRAMARSTRAM(rst),")
+	print("        .RSTREGARSTREG(rst),")
+	print("        .WEA(%s)," % ("4'b00" if write_width_a == 0 else "we[3:0]"))
+	print("        .DINBDIN({wdata[15:0], wdata[15:0]}),")
+	print("        .DINPBDINP({wdata[17:16], wdata[17:16]}),")
+	print("        .DOUTBDOUT(int_d[%d][67:36])," % i)
+	print("        .DOUTPBDOUTP(int_d[%d][71:68])," % i)
+	print("        .ADDRBWRADDR(wa),")
+	print("        .CLKBWRCLK(%s)," % ("clkb" if cd == "INDEPENDENT" else "clk"))
+	print("        .ENBWREN(enb),")
+	print("        .ADDRENB(enb),")
+	print("        .REGCEB(ceb),")
+	print("        .RSTRAMB(rst),")
+	print("        .RSTREGB(rst),")
+	print("        .WEBWE(%s)" % ("4'b00" if write_width_b == 0 else "we[7:4]"))
+	print("   );")
+	print()
+print("endmodule")
\ No newline at end of file
diff --git a/spec/bram36_sdp.py b/spec/bram36_sdp.py
new file mode 100644
index 0000000..def70ef
--- /dev/null
+++ b/spec/bram36_sdp.py
@@ -0,0 +1,85 @@
+# 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
+import math
+
+print("module top(input clk, clkb, rst, ena, enb, cea, ceb, input [14:0] ra, wa, input [7:0] we, input [17:0] wdata, input [10:0] sel, output [35:0] rdata);")
+N = 300
+print("    wire [71:0] int_d[0:%d-1];" % N)
+print("    assign rdata = int_d[sel[10:2]][18 * sel[1:0] +: 18];")
+for i in range(N):
+	write_width_b = np.random.choice([0, 1, 2, 4, 9, 18, 36, 72])
+	read_width_a = np.random.choice([0, 1, 2, 4, 9, 18, 36, 72], p=[0.05, 0.1, 0.1, 0.1, 0.05, 0.05, 0.45, 0.1])
+	write_width_a = 0 if (write_width_b == 72 or read_width_a == 72) else np.random.choice([0, 1, 2, 4, 9, 18, 36])
+	read_width_b = 0 if (write_width_b == 72 or read_width_a == 72) else np.random.choice([0, 1, 2, 4, 9, 18, 36], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.1, 0.45])
+	cd = np.random.choice(["INDEPENDENT", "COMMON"])
+
+	print("    RAMB36E2 #(")
+	print("        .CLOCK_DOMAINS(\"%s\")," % cd)
+	print("        .READ_WIDTH_A(%d)," % read_width_a)
+	print("        .READ_WIDTH_B(%d)," % read_width_b)
+	print("        .WRITE_WIDTH_A(%d)," % write_width_a)
+	print("        .WRITE_WIDTH_B(%d)," % write_width_b)
+	print("        .WRITE_MODE_A(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .WRITE_MODE_B(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .CASCADE_ORDER_A(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .CASCADE_ORDER_B(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .DOA_REG(%d)," % (0 if (write_width_b == 72 or read_width_a == 72) else np.random.randint(2)))
+	print("        .DOB_REG(%d)," % (0 if (write_width_b == 72 or read_width_a == 72) else np.random.randint(2)))
+	print("        .ENADDRENA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .ENADDRENB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .EN_ECC_PIPE(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .EN_ECC_READ(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .EN_ECC_WRITE(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .RDADDRCHANGEA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .SLEEP_ASYNC(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RSTREG_PRIORITY_A(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .RSTREG_PRIORITY_B(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	for pin in ("CLKARDCLK", "CLKBWRCLK", "ENARDEN", "ENBWREN", "RSTRAMARSTRAM", "RSTRAMB", "RSTREGARSTREG", "RSTREGB"):
+		print("        .IS_%s_INVERTED(%d)," % (pin, np.random.randint(2)))
+	print("        .INIT_A({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .INIT_B({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .SRVAL_A({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .SRVAL_B({18'd%d, 18'd%d})" % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("   ) ram%d (" % i)
+	print("        .DINADIN({wdata[15:0], wdata[15:0]}),")
+	print("        .DINPADINP({wdata[17:16], wdata[17:16]}),")
+	print("        .DOUTADOUT({int_d[%d][31:0]})," % i)
+	print("        .DOUTPADOUTP({int_d[%d][35:32]})," % i)
+	print("        .ADDRARDADDR(ra),")
+	print("        .CLKARDCLK(clk),")
+	print("        .ADDRENA(ena),")
+	print("        .ENARDEN(ena),")
+	print("        .REGCEAREGCE(cea),")
+	print("        .RSTRAMARSTRAM(rst),")
+	print("        .RSTREGARSTREG(rst),")
+	print("        .WEA(%s)," % ("4'b00" if write_width_a == 0 else "we[3:0]"))
+	print("        .DINBDIN({wdata[15:0], wdata[15:0]}),")
+	print("        .DINPBDINP({wdata[17:16], wdata[17:16]}),")
+	print("        .DOUTBDOUT(int_d[%d][67:36])," % i)
+	print("        .DOUTPBDOUTP(int_d[%d][71:68])," % i)
+	print("        .ADDRBWRADDR(wa),")
+	print("        .CLKBWRCLK(%s)," % ("clkb" if cd == "INDEPENDENT" else "clk"))
+	print("        .ENBWREN(enb),")
+	print("        .ADDRENB(enb),")
+	print("        .REGCEB(ceb),")
+	print("        .RSTRAMB(rst),")
+	print("        .RSTREGB(rst),")
+	print("        .WEBWE(%s)" % ("4'b00" if write_width_b == 0 else ("we[7:0]" if write_width_b == 72 else "we[7:4]")))
+	print("   );")
+	print()
+print("endmodule")
\ No newline at end of file
diff --git a/spec/bram36_sdp_2.py b/spec/bram36_sdp_2.py
new file mode 100644
index 0000000..b415899
--- /dev/null
+++ b/spec/bram36_sdp_2.py
@@ -0,0 +1,91 @@
+# 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
+import math
+
+print("module top(input clk, clkb, rst, ena, enb, cea, ceb, input [14:0] ra, wa, input [7:0] we, input [17:0] wdata, input [10:0] sel, output [35:0] rdata);")
+N = 300
+print("    wire [71:0] int_d[0:%d-1];" % N)
+print("    assign rdata = int_d[sel[10:2]][18 * sel[1:0] +: 18];")
+for i in range(N):
+	write_width_b = np.random.choice([0, 1, 2, 4, 9, 18, 36, 72])
+	read_width_a = np.random.choice([0, 1, 2, 4, 9, 18, 36, 72], p=[0.05, 0.1, 0.1, 0.1, 0.05, 0.05, 0.45, 0.1])
+	write_width_a = 0 if (write_width_b == 72 or read_width_a == 72) else np.random.choice([0, 1, 2, 4, 9, 18, 36])
+	read_width_b = 0 if (write_width_b == 72 or read_width_a == 72) else np.random.choice([0, 1, 2, 4, 9, 18, 36], p=[0.05, 0.1, 0.1, 0.1, 0.1, 0.1, 0.45])
+	cd = np.random.choice(["INDEPENDENT", "COMMON"])
+
+	print("    RAMB36E2 #(")
+	print("        .CLOCK_DOMAINS(\"%s\")," % cd)
+	print("        .READ_WIDTH_A(%d)," % read_width_a)
+	print("        .READ_WIDTH_B(%d)," % read_width_b)
+	print("        .WRITE_WIDTH_A(%d)," % write_width_a)
+	print("        .WRITE_WIDTH_B(%d)," % write_width_b)
+	print("        .WRITE_MODE_A(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .WRITE_MODE_B(\"%s\")," % np.random.choice(["NO_CHANGE", "READ_FIRST", "WRITE_FIRST"]))
+	print("        .CASCADE_ORDER_A(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .CASCADE_ORDER_B(\"%s\")," % np.random.choice(["NONE", "FIRST", "MIDDLE", "LAST"]))
+	print("        .DOA_REG(%d)," % (0 if (write_width_b == 72 or read_width_a == 72) else np.random.randint(2)))
+	print("        .DOB_REG(%d)," % (0 if (write_width_b == 72 or read_width_a == 72) else np.random.randint(2)))
+	print("        .ENADDRENA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .ENADDRENB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .EN_ECC_PIPE(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .EN_ECC_READ(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .EN_ECC_WRITE(\"%s\")," % np.random.choice(["FALSE", "TRUE"], p=[0.9, 0.1]))
+	print("        .RDADDRCHANGEA(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RDADDRCHANGEB(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .SLEEP_ASYNC(\"%s\")," % np.random.choice(["FALSE", "TRUE"]))
+	print("        .RSTREG_PRIORITY_A(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	print("        .RSTREG_PRIORITY_B(\"%s\")," % np.random.choice(["RSTREG", "REGCE"]))
+	for pin in ("CLKARDCLK", "CLKBWRCLK", "ENARDEN", "ENBWREN", "RSTRAMARSTRAM", "RSTRAMB", "RSTREGARSTREG", "RSTREGB"):
+		print("        .IS_%s_INVERTED(%d)," % (pin, np.random.randint(2)))
+	print("        .INIT_A({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .INIT_B({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .SRVAL_A({18'd%d, 18'd%d})," % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("        .SRVAL_B({18'd%d, 18'd%d})" % (np.random.randint(2**18), np.random.randint(2**18)))
+	print("   ) ram%d (" % i)
+	print("        .DINADIN({wdata[15:0], wdata[15:0]}),")
+	print("        .DINPADINP({wdata[17:16], wdata[17:16]}),")
+	douta_bit = np.random.randint(-1, 37)
+	if douta_bit >= 0 and douta_bit < 32:	
+		print("        .DOUTADOUT({int_d[%d][0]%s})," % (i, ", {%d{1'bx}}" % douta_bit if douta_bit > 0 else ""))
+	if douta_bit >= 32:
+		print("        .DOUTPADOUTP({int_d[%d][32]%s})," % (i, ", {%d{1'bx}}" % (douta_bit - 32) if douta_bit > 32 else ""))
+	print("        .ADDRARDADDR(ra),")
+	print("        .CLKARDCLK(clk),")
+	print("        .ADDRENA(ena),")
+	print("        .ENARDEN(ena),")
+	print("        .REGCEAREGCE(cea),")
+	print("        .RSTRAMARSTRAM(rst),")
+	print("        .RSTREGARSTREG(rst),")
+	print("        .WEA(%s)," % ("4'b00" if write_width_a == 0 else "we[3:0]"))
+	print("        .DINBDIN({wdata[15:0], wdata[15:0]}),")
+	print("        .DINPBDINP({wdata[17:16], wdata[17:16]}),")
+	doutb_bit = np.random.randint(-1, 37)
+	if doutb_bit >= 0 and doutb_bit < 32:	
+		print("        .DOUTBDOUT({int_d[%d][36]%s})," % (i, ", {%d{1'bx}}" % doutb_bit if doutb_bit > 0 else ""))
+	if doutb_bit >= 32:
+		print("        .DOUTPBDOUTP({int_d[%d][68]%s})," % (i, ", {%d{1'bx}}" % (doutb_bit - 32) if doutb_bit > 32 else ""))
+	print("        .ADDRBWRADDR(wa),")
+	print("        .CLKBWRCLK(%s)," % ("clkb" if cd == "INDEPENDENT" else "clk"))
+	print("        .ENBWREN(enb),")
+	print("        .ADDRENB(enb),")
+	print("        .REGCEB(ceb),")
+	print("        .RSTRAMB(rst),")
+	print("        .RSTREGB(rst),")
+	print("        .WEBWE(%s)" % ("4'b00" if write_width_b == 0 else ("we[7:0]" if write_width_b == 72 else "we[7:4]")))
+	print("   );")
+	print()
+print("endmodule")
\ No newline at end of file
diff --git a/spec/dsp.py b/spec/dsp.py
new file mode 100644
index 0000000..d550444
--- /dev/null
+++ b/spec/dsp.py
@@ -0,0 +1,152 @@
+# 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 = 8
+N = 120
+
+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 + "/dsp1/dsp%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)
+		print("    assign q = int_q[sel];", file=f)
+		for i in range(N):
+			use_preadd = np.random.choice([False, True])
+			bmultsel = (np.random.choice(["AD", "B"]) if use_preadd else "B")
+			amultsel = (np.random.choice(["AD", "A"]) if use_preadd and bmultsel == "AD" else ("AD" if use_preadd else "A"))
+			use_patdet = np.random.choice([False, True])
+			print("    DSP48E2 #(", file=f)
+			print("        .ADREG(%d)," % (np.random.randint(2) if use_preadd else 0), file=f)
+			print("        .ALUMODEREG(%d)," % np.random.randint(2), file=f)
+			print("        .AMULTSEL(\"%s\")," % amultsel, file=f)
+			print("        .AREG(%d)," % np.random.randint(3), file=f)
+			print("        .AUTORESET_PATDET(\"%s\")," % np.random.choice(["NO_RESET", "RESET_MATCH", "RESET_NOT_MATCH"]), file=f)
+			print("        .AUTORESET_PRIORITY(\"%s\")," % np.random.choice(["RESET", "CEP"]), file=f)
+			print("        .A_INPUT(\"DIRECT\"),", file=f)
+			print("        .BMULTSEL(\"%s\")," % bmultsel,file=f)
+			print("        .BREG(%d)," % np.random.randint(3), file=f)
+			print("        .B_INPUT(\"DIRECT\"),", file=f)
+			print("        .CARRYINREG(%d)," % np.random.randint(2), file=f)
+			print("        .CARRYINSELREG(%d)," % np.random.randint(2), file=f)
+			print("        .CREG(%d)," % np.random.randint(2), file=f)
+			print("        .DREG(%d)," % np.random.randint(2), file=f)
+			print("        .INMODEREG(%d)," % np.random.randint(2), file=f)
+			print("        .IS_ALUMODE_INVERTED(%d)," % np.random.randint(16), file=f)
+			print("        .IS_CARRYIN_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_CLK_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_INMODE_INVERTED(%d)," % np.random.randint(32), file=f)
+			print("        .IS_OPMODE_INVERTED(%d)," % np.random.randint(512), file=f)
+			print("        .IS_RSTALLCARRYIN_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTALUMODE_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTA_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTB_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTCTRL_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTC_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTD_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTINMODE_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTM_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTP_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .MASK({24'd%d, 24'd%d})," % (np.random.randint(2**24), np.random.randint(2**24)), file=f)
+			print("        .MREG(%d)," % np.random.randint(2), file=f)
+			print("        .OPMODEREG(%d)," % np.random.randint(2), file=f)
+			print("        .PATTERN({24'd%d, 24'd%d})," % (np.random.randint(2**24), np.random.randint(2**24)), file=f)
+			print("        .PREADDINSEL(\"%s\")," % np.random.choice(["A", "B"]), file=f)
+			print("        .PREG(%d)," % np.random.randint(2), file=f)
+			print("        .RND({24'd%d, 24'd%d})," % (np.random.randint(2**24), np.random.randint(2**24)), file=f)
+			print("        .SEL_MASK(\"%s\")," % np.random.choice(["MASK", "C", "ROUNDING_MODE1", "ROUNDING_MODE2"]), file=f)
+			print("        .SEL_PATTERN(\"%s\")," % np.random.choice(["PATTERN", "C"]), file=f)
+			print("        .USE_PATTERN_DETECT(\"%s\")," % ("PATDET" if use_patdet else "NO_PATDET"), file=f)
+			print("        .USE_SIMD(\"%s\")," %  np.random.choice(["ONE48", "TWO24", "FOUR12"]), file=f)
+			print("        .USE_WIDEXOR(\"%s\")," %  np.random.choice(["TRUE", "FALSE"]), file=f)
+			print("        .XORSIMD(\"%s\")" %  np.random.choice(["XOR12", "XOR24_48_96"]), file=f)
+			print("    ) dsp_%d (" % i, file=f)
+			print("        .A(%s)," % random_vector(30), file=f)
+			print("        .ALUMODE(%s)," % random_vector(4), file=f)
+			print("        .B(%s)," % random_vector(18), file=f)
+			print("        .C(%s)," % random_vector(48), file=f)
+			print("        .CARRYIN(%s)," % random_vector(1), file=f)
+			print("        .CEA1(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEA2(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEAD(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEALUMODE(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEC(e[%d])," % np.random.randint(9), file=f)
+			print("        .CECARRYIN(e[%d])," % np.random.randint(9), file=f)
+			print("        .CECTRL(e[%d])," % np.random.randint(9), file=f)
+			print("        .CED(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEINMODE(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEM(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEP(e[%d])," % np.random.randint(9), file=f)
+			print("        .CLK(clk[%d])," % np.random.randint(8), file=f)
+			print("        .D(%s)," % random_vector(27), file=f)
+			print("        .INMODE(%s)," % random_vector(5), file=f)
+			print("        .OPMODE(%s)," % random_vector(9), file=f)
+			print("        .RSTA(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTALLCARRYIN(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTALUMODE(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTB(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTC(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTCTRL(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTD(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTINMODE(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTM(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTP(r[%d])," % np.random.randint(9), file=f)
+			print("        .P(int_q[%d][47:0])," % i, file=f)
+			print("        .CARRYOUT(int_q[%d][51:48])," % i, file=f)
+			print("        .XOROUT(int_q[%d][59:52])," % i, file=f)
+			print("        .PATTERNDETECT(int_q[%d][60])," % i, file=f)
+			print("        .PATTERNBDETECT(int_q[%d][61])," % i, file=f)
+			print("        .OVERFLOW(int_q[%d][62])," % i, file=f)
+			print("        .UNDERFLOW(int_q[%d][63])" % i, file=f)
+			print("    );", file=f)
+			print("", file=f)
+		print("endmodule", file=f)
+	with open(root + "/dsp1/dsp%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/dsp1/dsp%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/dsp%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_bram/dsp%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_bram/dsp%d.bit" % (root, x), file=f)
+with open(root + "/dsp1/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 dsp%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_bram/dsp%d.bit %s/frames.txt > %s/specimen_bram/dsp%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tile.json %s/specimen_bram/dsp%d.dump > %s/specimen_bram/dsp%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_bram/dsp%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/dsp_2.py b/spec/dsp_2.py
new file mode 100644
index 0000000..2392de2
--- /dev/null
+++ b/spec/dsp_2.py
@@ -0,0 +1,160 @@
+# 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 = 8
+N = 200
+
+root = sys.argv[2]
+
+def random_vector(size):
+	return "{%s}" % ", ".join(["d[%d]" % np.random.randint(12) for k in range(size)])
+
+for x in range(X):
+
+
+	with open(root + "/dsp2/dsp_b%d.v" % x, "w") as f:
+		print("module top(input [7:0] clk, cen, rst, input [11: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)
+		print("    assign q = int_q[sel];", file=f)
+		for i in range(N):
+			use_preadd = np.random.choice([False, True])
+			bmultsel = (np.random.choice(["AD", "B"]) if use_preadd else "B")
+			amultsel = (np.random.choice(["AD", "A"]) if use_preadd and bmultsel == "AD" else ("AD" if use_preadd else "A"))
+			use_patdet = np.random.choice([False, True])
+			print("    (* keep, dont_touch *) DSP48E2 #(", file=f)
+			print("        .ADREG(%d)," % (np.random.randint(2) if use_preadd else 0), file=f)
+			print("        .ALUMODEREG(%d)," % np.random.randint(2), file=f)
+			print("        .AMULTSEL(\"%s\")," % amultsel, file=f)
+			print("        .AREG(%d)," % np.random.randint(3), file=f)
+			print("        .AUTORESET_PATDET(\"%s\")," % np.random.choice(["NO_RESET", "RESET_MATCH", "RESET_NOT_MATCH"]), file=f)
+			print("        .AUTORESET_PRIORITY(\"%s\")," % np.random.choice(["RESET", "CEP"]), file=f)
+			print("        .A_INPUT(\"DIRECT\"),", file=f)
+			print("        .BMULTSEL(\"%s\")," % bmultsel,file=f)
+			print("        .BREG(%d)," % np.random.randint(3), file=f)
+			print("        .B_INPUT(\"DIRECT\"),", file=f)
+			print("        .CARRYINREG(%d)," % np.random.randint(2), file=f)
+			print("        .CARRYINSELREG(%d)," % np.random.randint(2), file=f)
+			print("        .CREG(%d)," % np.random.randint(2), file=f)
+			print("        .DREG(%d)," % np.random.randint(2), file=f)
+			print("        .INMODEREG(%d)," % np.random.randint(2), file=f)
+			print("        .IS_ALUMODE_INVERTED(%d)," % np.random.randint(16), file=f)
+			print("        .IS_CARRYIN_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_CLK_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_INMODE_INVERTED(%d)," % np.random.randint(32), file=f)
+			print("        .IS_OPMODE_INVERTED(%d)," % np.random.randint(512), file=f)
+			print("        .IS_RSTALLCARRYIN_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTALUMODE_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTA_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTB_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTCTRL_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTC_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTD_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTINMODE_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTM_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .IS_RSTP_INVERTED(%d)," % np.random.randint(2), file=f)
+			print("        .MASK({24'd%d, 24'd%d})," % (np.random.randint(2**24), np.random.randint(2**24)), file=f)
+			print("        .MREG(%d)," % np.random.randint(2), file=f)
+			print("        .OPMODEREG(%d)," % np.random.randint(2), file=f)
+			print("        .PATTERN({24'd%d, 24'd%d})," % (np.random.randint(2**24), np.random.randint(2**24)), file=f)
+			print("        .PREADDINSEL(\"%s\")," % np.random.choice(["A", "B"]), file=f)
+			print("        .PREG(%d)," % np.random.randint(2), file=f)
+			print("        .RND({24'd%d, 24'd%d})," % (np.random.randint(2**24), np.random.randint(2**24)), file=f)
+			print("        .SEL_MASK(\"%s\")," % np.random.choice(["MASK", "C", "ROUNDING_MODE1", "ROUNDING_MODE2"]), file=f)
+			print("        .SEL_PATTERN(\"%s\")," % np.random.choice(["PATTERN", "C"]), file=f)
+			print("        .USE_PATTERN_DETECT(\"%s\")," % ("PATDET" if use_patdet else "NO_PATDET"), file=f)
+			print("        .USE_SIMD(\"%s\")," %  np.random.choice(["ONE48", "TWO24", "FOUR12"]), file=f)
+			print("        .USE_WIDEXOR(\"%s\")," %  np.random.choice(["TRUE", "FALSE"]), file=f)
+			print("        .XORSIMD(\"%s\")" %  np.random.choice(["XOR12", "XOR24_48_96"]), file=f)
+			print("    ) dsp_%d (" % i, file=f)
+			print("        .A(%s)," % random_vector(30), file=f)
+			print("        .ALUMODE(%s)," % random_vector(4), file=f)
+			print("        .B(%s)," % random_vector(18), file=f)
+			#print("        .C(%s)," % random_vector(48), file=f)
+			print("        .CARRYIN(%s)," % random_vector(1), file=f)
+			print("        .CEA1(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEA2(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEAD(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEALUMODE(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEC(e[%d])," % np.random.randint(9), file=f)
+			print("        .CECARRYIN(e[%d])," % np.random.randint(9), file=f)
+			print("        .CECTRL(e[%d])," % np.random.randint(9), file=f)
+			print("        .CED(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEINMODE(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEM(e[%d])," % np.random.randint(9), file=f)
+			print("        .CEP(e[%d])," % np.random.randint(9), file=f)
+			#print("        .D(%s)," % random_vector(27), file=f)
+			print("        .INMODE(%s)," % random_vector(5), file=f)
+			print("        .OPMODE(%s)," % random_vector(9), file=f)
+			print("        .RSTA(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTALLCARRYIN(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTALUMODE(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTB(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTC(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTCTRL(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTD(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTINMODE(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTM(r[%d])," % np.random.randint(9), file=f)
+			print("        .RSTP(r[%d])," % np.random.randint(9), file=f)
+			o_bit = np.random.randint(-1, 64)
+			if o_bit >= 0 and o_bit < 48:
+				print("        .P({int_q[%d][0]%s})," % (i, ", {%d{1'bx}}" % o_bit if o_bit > 0 else ""), file=f)
+			if o_bit >= 48 and o_bit < 52:
+				print("        .CARRYOUT({int_q[%d][48]%s})," % (i, ", {%d{1'bx}}" % (o_bit-48) if o_bit > 48 else ""), file=f)
+			if o_bit >= 52 and o_bit < 60:
+				print("        .XOROUT({int_q[%d][52]%s})," % (i, ", {%d{1'bx}}" % (o_bit-52) if o_bit > 52 else ""), file=f)
+			if o_bit == 60:
+				print("        .PATTERNDETECT(int_q[%d][60])," % i, file=f)
+			if o_bit == 61:
+				print("        .PATTERNBDETECT(int_q[%d][61])," % i, file=f)
+			if o_bit == 62:
+				print("        .OVERFLOW(int_q[%d][62])," % i, file=f)
+			if o_bit == 63:
+				print("        .UNDERFLOW(int_q[%d][63])," % i, file=f)
+			print("        .CLK(clk[%d])" % np.random.randint(8), file=f)
+			print("    );", file=f)
+			print("", file=f)
+		print("endmodule", file=f)
+	with open(root + "/dsp2/dsp_b%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/dsp2/dsp_b%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/dsp_b%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_bram/dsp_b%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_bram/dsp_b%d.bit" % (root, x), file=f)
+with open(root + "/dsp2/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 dsp_b%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_bram/dsp_b%d.bit %s/frames.txt > %s/specimen_bram/dsp_b%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tile.json %s/specimen_bram/dsp_b%d.dump > %s/specimen_bram/dsp_b%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_bram/dsp_b%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp_b%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp_b%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp_b%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_bram/dsp_b%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/flipflops.py b/spec/flipflops.py
new file mode 100644
index 0000000..f9ca1dd
--- /dev/null
+++ b/spec/flipflops.py
@@ -0,0 +1,64 @@
+# 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
+
+print("module top(input [15:0] clk, rst, cen, input [6:0] d, output q);")
+N = 10000
+print("    wire [%d:0] int_d;" % N)
+print("    assign int_d[6:0] = d;")
+print("    assign q = int_d[%d];" % N)
+
+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 "SLICEM" in site or "SLICEL" in site:
+				slices.append(site.split(":")[0])
+for i in range(6, N):
+	fftype, srsig = np.random.choice(["FDPE,PRE", "FDCE,CLR", "FDSE,S", "FDRE,R"]).split(",")
+	sl = slices.pop()
+	if np.random.ranf() < 0.5:
+		data = "int_d[%d]" % i
+	else:
+		lutsize = np.random.randint(2, 10)
+		if lutsize > 6:
+			lutsize = 6
+		print ("    wire lut_q_%d;" % i)
+
+		print("     (* BEL=\"%s%dLUT\" *) (* LOC=\"%s\" *) LUT%d #(" % (np.random.choice(list("ABCDEFGH")), np.random.randint(6 if lutsize == 6 else 5, 7), sl, lutsize))
+		print("         .INIT(%d'h%016x)" % (2 ** lutsize, np.random.randint(1, (2 ** ((2 ** lutsize) - 1) - 1))))
+		print("     ) lut_%d (" % i)
+		for j in range(lutsize):
+			print("         .I%d(int_d[%d])," % (j, i - j))
+		print("         .O(lut_q_%d)" % i)
+		print("     );")
+		data = "lut_q_%d" % i
+
+	print("    (* BEL=\"%sFF%s\" *) (* LOC=\"%s\" *) %s #(" % (np.random.choice(list("ABCDEFGH")), np.random.choice(["", "2"]),  sl, fftype))
+	print("          .IS_C_INVERTED(%s)," % np.random.choice(["0", "1"]))
+	print("          .IS_%s_INVERTED(%s)," % (srsig, np.random.choice(["0", "1"])))
+	print("          .INIT(%s)" % np.random.choice(["1'b0", "1'b1"]))
+	print("     ) ff_%d (" % i)
+	print("          .C(clk[%d])," % np.random.randint(0, 16))
+	print("          .CE(cen[%d])," % np.random.randint(0, 16))
+	print("          .%s(rst[%d])," % (srsig, np.random.randint(0, 16)))
+	print("          .D(%s)," % (data))
+	print("          .Q(int_d[%d])" % (i + 1))
+	print("     );")
+print("endmodule")
diff --git a/spec/gclk.py b/spec/gclk.py
new file mode 100644
index 0000000..e723fbe
--- /dev/null
+++ b/spec/gclk.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 numpy as np
+import sys
+
+bufgces = []
+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 ("BUFGCE" in site) and "HDIO" not in site and "DIV" not in site:
+				bufgces.append((site.split(":")[0], sl[2]))
+
+np.random.shuffle(bufgces)
+
+print("module top(input i, output o);")
+N = int(len(bufgces) * 0.08)
+print("    wire [%d:0] r;" % N)
+print("    assign r[0] = i;")
+print("    assign o = r[%d];" % N)
+for i in range(N):
+	bg, tile = bufgces[i]
+	print("(* LOC=\"%s\" *)" % bg)
+	if "BUFGCTRL" in bg:
+		print("    BUFGCTRL bufg_%d (.I0(r[%d]), .I1(r[%d]), .S0(1'b1), .S1(1'b0), .CE0(1'b1), .O(r[%d]));" % (i, i, i, i+1))
+	elif "DIV" in bg:
+		print("    BUFGCE_DIV #(.BUFGCE_DIVIDE(3)) bufg_%d (.I(r[%d]), .CLR(0), .CE(1'b1), .O(r[%d]));" % (i, i, i + 1))
+	else:
+		print("    BUFGCE bufg_%d (.I(r[%d]), .CE(1'b1), .O(r[%d]));" % (i, i, i+1))
+print("endmodule")
\ No newline at end of file
diff --git a/spec/gclk_2.py b/spec/gclk_2.py
new file mode 100644
index 0000000..0cfb058
--- /dev/null
+++ b/spec/gclk_2.py
@@ -0,0 +1,142 @@
+# 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 = {}
+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 ("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(":"))
+
+X = 64
+
+root = sys.argv[2]
+
+for x in range(X):
+	buffers = []
+
+	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)
+		target_type = np.random.choice(["BUFGCE", "BUFGCE_DIV", "BUFGCTRL"] if len(buffers) > 0 else ["BUFGCE", "BUFGCE_DIV"])
+		tile_buffers = np.random.randint(8)
+		found_buffers = 0
+		for buf, buftype in shuffled_bufs:
+			if found_buffers >= tile_buffers:
+				break
+			if buftype != target_type:
+				continue
+			buffers.append((buf, buftype))
+			print("%s %s" % (tile, buf))
+			found_buffers += 1
+	print()
+	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 + "/clkroute3/gclk%d.v" % x, "w") as f:
+		print("module top(input i, input [9:0] aux, d, output o, q);", file=f)
+		N = len(buffers)
+		print("    wire [%d:0] r;" % N, file=f)
+		print("    assign r[0] = i;", file=f)
+		print("    assign o = r[%d];" % N, file=f)
+		print("    wire [%d:0] r2;" % (2*N), file=f)
+		print("    assign r2[0] = d;", file=f)
+		print("    assign q = r2[%d];" % (2*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)
+			elif "DIV" in buftype:
+				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)
+				print("        .I(r[%d])," % i, file=f)
+				print("        %s," % random_control(["CE", "CLR"]), file=f)
+				print("        .O(r[%d])" % (i+1), file=f)
+				print("    );", file=f)
+			else:
+				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_div_%d (" % i, file=f)
+				print("        .I(r[%d])," % i, file=f)
+				print("        %s," % random_control(["CE"]), file=f)
+				print("        .O(r[%d])" % (i+1), file=f)
+				print("    );", file=f)
+
+			if np.random.randint(2) == 1:
+				print("FDCE ff_%d(.C(r[%d]), .CE(1'b1), .CLR(1'b0), .D(r2[%d]), .Q(r2[%d]));" % (i, i, 2*i, 2*i+1), file=f)
+			else:
+				print("assign r2[%d] = r2[%d];" % (2*i+1, 2*i), file=f)
+
+			if np.random.randint(2) == 1:
+				print("SRLC32E srl_%d(.CLK(r[%d]), .CE(1'b1), .A(aux[4:0]), .D(r2[%d]), .Q(r2[%d]));" % (i, i, 2*i+1, 2*i+2), file=f)
+			else:
+				print("assign r2[%d] = r2[%d];" % (2*i+2, 2*i+1), file=f)
+
+			print("", file=f)
+		print("endmodule", file=f)
+	with open(root + "/clkroute3/gclk%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/clkroute3/gclk%d.v" % x)), file=f)
+		#print("read_xdc %s" % (root + ("/clkroute3/gclk%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%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_clk/gclk%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_clk/gclk%d.bit" % (root, x), file=f)
+with open(root + "/clkroute3/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%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/gclk%d.bit %s/frames.txt > %s/specimen_clk/gclk%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tile.json %s/specimen_clk/gclk%d.dump > %s/specimen_clk/gclk%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_clk/gclk%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclk%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/gclk_3.py b/spec/gclk_3.py
new file mode 100644
index 0000000..dfc290a
--- /dev/null
+++ b/spec/gclk_3.py
@@ -0,0 +1,181 @@
+# 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 = 32
+
+root = sys.argv[2]
+
+for x in range(X):
+	buffers = []
+
+	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)
+		target_type = np.random.choice(["BUFGCE", "BUFGCE_DIV", "BUFGCTRL"] if len(buffers) > 0 else ["BUFGCE", "BUFGCE_DIV"])
+		tile_buffers = np.random.randint(6)
+		found_buffers = 0
+		for buf, buftype in shuffled_bufs:
+			if found_buffers >= tile_buffers:
+				break
+			if buftype != target_type:
+				continue
+			buffers.append((buf, buftype))
+			print("%s %s" % (tile, buf))
+			found_buffers += 1
+	
+	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 + "/clkroute4/gclkb_%d.v" % x, "w") as f:
+		print("module top(input [23:0] i, input [9:0] aux, input d, output o, q);", file=f)
+		N = 24
+		print("    wire [71: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)
+		print("    assign r[23:0] = i;", file=f)
+		for i in range(12):
+			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)
+			print("        .I(i[%d])," % i, file=f)
+			print("        %s," % random_control(["CE", "CLR"]), file=f)
+			print("        .O(r[%d])" % (24+i), file=f)
+			print("    );", file=f)
+		for i in range(36):
+			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])," %  np.random.randint(24), file=f)
+			print("        %s," % random_control(["CE"]), file=f)
+			print("        .O(r[%d])" % (i+36), file=f)
+			print("    );", file=f)
+
+		R2=0
+		NS=16
+		ffs=""
+		for row, hcs in sorted(halfcolumn_slices_by_row.items()):
+			row_clks = np.random.randint(16, 25)
+			clks = [np.random.randint(72) for k in range(row_clks)]
+			halfs = [hcs[np.random.randint(len(hcs))] for k in range(NS)]
+			for h in halfs:
+				half_clks = np.random.randint(8, 17)
+				rclks = [np.random.choice(clks) for k in range(half_clks)]
+				for sl in h:
+					ffs += "(* LOC=\"%s\" *) FDCE ff_%d (.C(r[%d]), .CE(aux[%d]), .CLR(1'b0), .D(r2[%d]), .Q(r2[%d]));\n" % (sl, R2, np.random.choice(rclks), 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 + "/clkroute4/gclkb_%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/clkroute4/gclkb_%d.v" % x)), file=f)
+		#print("read_xdc %s" % (root + ("/clkroute4/gclkb_%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/gclkb_%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_clk/gclkb_%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_clk/gclkb_%d.bit" % (root, x), file=f)
+with open(root + "/clkroute4/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 gclkb_%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/gclkb_%d.bit %s/frames.txt > %s/specimen_clk/gclkb_%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tile.json %s/specimen_clk/gclkb_%d.dump > %s/specimen_clk/gclkb_%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_clk/gclkb_%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkb_%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkb_%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkb_%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkb_%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/gclk_4.py b/spec/gclk_4.py
new file mode 100644
index 0000000..7f1f314
--- /dev/null
+++ b/spec/gclk_4.py
@@ -0,0 +1,188 @@
+# 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 = 32
+
+root = sys.argv[2]
+
+for x in range(X):
+	buffers = []
+
+	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)
+		target_type = np.random.choice(["BUFGCE", "BUFGCE_DIV", "BUFGCTRL"] if len(buffers) > 0 else ["BUFGCE", "BUFGCE_DIV"])
+		tile_buffers = np.random.randint(6)
+		found_buffers = 0
+		for buf, buftype in shuffled_bufs:
+			if found_buffers >= tile_buffers:
+				break
+			if buftype != target_type:
+				continue
+			buffers.append((buf, buftype))
+			print("%s %s" % (tile, buf))
+			found_buffers += 1
+	
+	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 + "/clkroute5/gclkc_%d.v" % x, "w") as f:
+		print("module top(input [23:0] i, input [9:0] aux, input d, output o, q);", file=f)
+		N = 24
+		print("    wire [71: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)
+		print("    assign r[23:0] = i;", file=f)
+		for i in range(12):
+			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)
+			print("        .I(i[%d])," % i, file=f)
+			print("        %s," % random_control(["CE", "CLR"]), file=f)
+			print("        .O(r[%d])" % (24+i), file=f)
+			print("    );", file=f)
+		for i in range(12):
+			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])," %  np.random.randint(24), file=f)
+			print("        %s," % random_control(["CE"]), file=f)
+			print("        .O(r[%d])" % (i+36), file=f)
+			print("    );", file=f)
+
+		R2=0
+		NS=64
+		ffs=""
+		for row, hcs in sorted(halfcolumn_slices_by_row.items()):
+			row_clks = np.random.randint(20, 25)
+			clks = [np.random.randint(48) for k in range(row_clks)]
+			hi = []
+			while len(hi) < NS:
+				next_i = np.random.randint(len(hcs))
+				while next_i in hi:
+					next_i = np.random.randint(len(hcs))
+				hi.append(next_i)
+
+			halfs = [hcs[hi[k]] for k in range(NS)]
+			for h in halfs:
+				half_clks = np.random.randint(13, 17)
+				rclks = [np.random.choice(clks) for k in range(half_clks)]
+				for sl in h:
+					ffs += "(* LOC=\"%s\" *) FDCE ff_%d (.C(r[%d]), .CE(aux[%d]), .CLR(~aux[%d]), .D(r2[%d]), .Q(r2[%d]));\n" % (sl, R2, rclks[R2 % len(rclks)], 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 + "/clkroute5/gclkc_%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/clkroute5/gclkc_%d.v" % x)), file=f)
+		#print("read_xdc %s" % (root + ("/clkroute5/gclkc_%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/gclkc_%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_clk/gclkc_%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_clk/gclkc_%d.bit" % (root, x), file=f)
+with open(root + "/clkroute5/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 gclkc_%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/gclkc_%d.bit %s/frames.txt > %s/specimen_clk/gclkc_%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tile.json %s/specimen_clk/gclkc_%d.dump > %s/specimen_clk/gclkc_%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_clk/gclkc_%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkc_%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkc_%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkc_%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_clk/gclkc_%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/memory.py b/spec/memory.py
new file mode 100644
index 0000000..a8e3b36
--- /dev/null
+++ b/spec/memory.py
@@ -0,0 +1,115 @@
+# 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
+import math
+
+print("module top(input [15:0] clk, cen, input [8:0] wa, input [7:0] ra, input [7:0] d, input [11:0] sel, output [3:0] q);")
+N = 4096
+print("    wire [3:0] int_d[0:%d-1];" % N)
+print("    assign q = int_d[sel];")
+
+def connect(sig, dst, len):
+	if sig is None:
+		return []
+	bit = 0
+	conns = []
+	for x in sig:
+		if type(x) is tuple:
+			name, width = x
+			conns.append(".%s(%s[%d +: %d])" % (name, dst, bit, width))
+			bit += width
+		else:
+			conns.append(".%s(%s[%d])" % (x, dst, bit))
+			bit += 1
+	return conns
+
+for i in range(N):
+	ctype = np.random.choice(["SRL16E", "SRLC32E", "RAM32X1D", "RAM32X1S", "RAM64X1D", "RAM64X1S", "RAM128X1S", "RAM128X1D", "RAM256X1D", "RAM256X1S", "RAM512X1S"])
+	clockport = None
+	ceport = None
+	wraddr = None
+	rdaddr = None
+	wdata = None
+	rdata = None
+	init_len = None
+	if ctype in ("SRL16E", "SRLC32E"):
+		clockport = "CLK"
+		ceport = "CE"
+		wdata = ["D"]
+		rdata = ["Q"]
+		if ctype == "SRLC32E":
+			rdata.append("Q31")
+			rdaddr = [("A", 5)]
+			init_len = 32
+		else:
+			rdaddr = ["A0", "A1", "A2", "A3"]
+			init_len = 16
+	elif ctype in ("RAM32X1D", "RAM32X1S"):
+		clockport = "WCLK"
+		ceport = "WE"
+		wdata = ["D"]
+		wraddr = ["A0", "A1", "A2", "A3", "A4"]
+		init_len = 32
+		if ctype == "RAM32X1D":
+			rdaddr = ["DPRA0", "DPRA1", "DPRA2", "DPRA3", "DPRA4"]
+			rdata = ["SPO", "DPO"]
+		else:
+			rdata = ["O"]
+	elif ctype in ("RAM64X1D", "RAM64X1S"):
+		clockport = "WCLK"
+		ceport = "WE"
+		wdata = ["D"]
+		wraddr = ["A0", "A1", "A2", "A3", "A4", "A5"]
+		init_len = 64
+		if ctype == "RAM64X1D":
+			rdaddr = ["DPRA0", "DPRA1", "DPRA2", "DPRA3", "DPRA4", "DPRA5"]
+			rdata = ["SPO", "DPO"]
+		else:
+			rdata = ["O"]
+	elif ctype in ("RAM128X1S", "RAM128X1D", "RAM256X1D", "RAM256X1S", "RAM512X1S"):
+		init_len = int(ctype[3:6])
+		abits = math.log2(init_len)
+		clockport = "WCLK"
+		ceport = "WE"
+		wdata = ["D"]
+		if ctype == "RAM128X1S":
+			wraddr = ["A0", "A1", "A2", "A3", "A4", "A5", "A6"]
+		else:
+			wraddr = [("A", abits)]
+		if ctype[-1] == "D":
+			rdaddr = [("DPRA", abits)]
+			rdata = ["SPO", "DPO"]
+		else:
+			rdata = ["O"]
+	else:
+		assert False, ctype
+	conns = []
+	if clockport is not None:
+		conns.append(".%s(clk[%d])" % (clockport, np.random.randint(0, 16)))
+	if ceport is not None:
+		conns.append(".%s(cen[%d])" % (ceport, np.random.randint(0, 16)))
+	conns += connect(wraddr, "wa", 9)
+	conns += connect(rdaddr, "ra", 8)
+	conns += connect(wdata, "d", 8)
+	conns += connect(rdata, "int_d[%d]" % i, 4)
+	print("    %s #(" % ctype)
+	if init_len is not None:
+		initdata = np.random.randint(2, size=init_len)
+		print("        .INIT(%d'b%s)" % (init_len, "".join([str(_) for _ in initdata])))
+	print("    ) %s_%d (" % (ctype, i))
+	print("    %s" % ",\n    ".join(conns))
+	print("    );")
+print("endmodule")
diff --git a/spec/rclk_int.py b/spec/rclk_int.py
new file mode 100644
index 0000000..816924c
--- /dev/null
+++ b/spec/rclk_int.py
@@ -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.
+
+import numpy as np
+import sys
+
+N = 5000
+
+bufgces = []
+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 "BUFGCE" in site and "HDIO" not in site:
+				bufgces.append(site.split(":")[0])
+
+print("module layer_1(input [31:0] clk, input [71:0] cen, input d, output q);")
+print("    wire [%d:0] r;" % N)
+print("    assign r[0] = d;")
+print("    assign q = r[%d];" % N)
+print()
+for i in range(N):
+	print("    FDCE ff_%d (.C(clk[%d]), .CLR(1'b0), .CE(cen[%d]), .D(r[%d]), .Q(r[%d]));" % (i, (i * 32) // N, np.random.randint(72), i, i+1))
+	print()
+print("endmodule")
+
+
+M = 16
+print("module top(input [15:0] clk, cen, input d, output q);")
+print("    wire [511:0] clk_int;")
+print("    wire [71:0] cen_int;")
+print("    assign clk_int[15:0] = clk;")
+print("    assign cen_int[15:0] = cen;")
+print("    assign cen_int[71:64] = 8'hFF;")
+for i in range(16, 512):
+	a = np.random.randint(16)
+	b = None
+	while b is None or b == a:
+		b = np.random.randint(16)
+	c = None
+	while c is None or c == a or c == b:
+		c = np.random.randint(16)
+	bg = None
+	if len(bufgces) > 0:
+		bg = bufgces.pop()
+	if bg is not None and np.random.randint(3) > 0:
+		if "DIV" in bg:
+			print("    BUFGCE_DIV #(.BUFGCE_DIVIDE(3)) bufg_%d (.I(clk[%d] ^ clk[%d] ^ clk[%d]), .CLR(0), .CE(1'b1), .O(clk_int[%d]));" % (i, a, b, c, i))
+		else:
+			print("    BUFGCE bufg_%d (.I(clk[%d] ^ clk[%d] ^ clk[%d]), .CE(1'b1), .O(clk_int[%d]));" % (i, a, b, c, i))
+	else:
+		print("    assign clk_int[%d] = clk[%d] ^ clk[%d] ^ clk[%d];" % (i, a, b, c))
+	if i < 64:
+		print("    assign cen_int[%d] = cen[%d] ^ cen[%d];" % (i, a, b))
+print()
+print("    wire [%d:0] r;" % M)
+print("    assign r[0] = d;")
+print("    assign q = r[%d];" % M)
+for i in range(M):
+	print("    layer_1 submod_%d(.clk(clk_int[%d +: 32]), .cen(cen_int), .d(r[%d]), .q(r[%d]));" % (i, 32 * i, i, i+1))
+print("endmodule")
diff --git a/spec/rclk_int_2.py b/spec/rclk_int_2.py
new file mode 100644
index 0000000..059d99b
--- /dev/null
+++ b/spec/rclk_int_2.py
@@ -0,0 +1,122 @@
+# 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 = []
+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 "BUFGCE" in site and "HDIO" not in site:
+				bufgces.append(site.split(":")[0])
+			if "SLICEM" in site or "SLICEL" in site:
+				slices.append(site.split(":")[0])
+orig_bufgces = list(bufgces)
+np.random.shuffle(bufgces)
+np.random.shuffle(slices)
+X = 30
+
+root = sys.argv[2]
+
+bufg_prob = np.random.randint(15, 20)
+
+for x in range(X):
+	with open(root + "/clkroute/clkr%d.v" % x, "w") as f:
+		N = 256
+		M = 16
+		for m in range(M):
+			print("module layer_1_%d(input [31:0] clk, input [71:0] cen, input d, output q);" % m, file=f)
+			print("    wire [%d:0] r;" % N, file=f)
+			print("    assign r[0] = d;", file=f)
+			print("    assign q = r[%d];" % N, file=f)
+			print("", file=f)
+			for i in range(N):
+				if N == 0 and len(slices) > 0:
+					loc = slices.pop()
+					print("(* LOC=\"%s\" *)" % loc, file=f)
+				print("    FDCE ff_%d (.C(clk[%d]), .CLR(1'b0), .CE(cen[%d]), .D(r[%d]), .Q(r[%d]));" % (i, (i * 32) // N, np.random.randint(72), i, i+1), file=f)
+			print("endmodule", file=f)
+
+
+		print("module top(input [39:0] clk, cen, input d, output q);", file=f)
+		num_inputs = np.random.randint(8, 40)
+		print("    wire [511:0] clk_int;", file=f)
+		print("    wire [71:0] cen_int;", file=f)
+		print("    assign clk_int[%d:0] = clk;" % (num_inputs - 1), file=f)
+		print("    assign cen_int[%d:0] = cen;" % (num_inputs - 1), file=f)
+		print("    assign cen_int[71:64] = 8'hFF;", file=f)
+
+		bufgces = list(orig_bufgces)
+		np.random.shuffle(bufgces)
+
+		for i in range(num_inputs, 512):
+			a = np.random.randint(num_inputs)
+			b = None
+			while b is None or b == a:
+				b = np.random.randint(num_inputs)
+			c = None
+			while c is None or c == a or c == b:
+				c = np.random.randint(num_inputs)
+			bg = None
+			if len(bufgces) > 0:
+				bg = bufgces.pop()
+			if bg is not None and np.random.randint(20) >= bufg_prob or (bg is not None and "DIV" in bg and np.random.randint(19) >= bufg_prob):
+				if "DIV" in bg:
+					print("    BUFGCE_DIV #(.BUFGCE_DIVIDE(%d), .IS_I_INVERTED(%d), .IS_CE_INVERTED(%d), .IS_CLR_INVERTED(%d)) bufg_%d (.I(clk[%d] ^ clk[%d] ^ clk[%d]), .CLR(!cen[%d]), .CE(cen[%d]), .O(clk_int[%d]));" %
+						(np.random.randint(1, 9), np.random.randint(2), np.random.randint(2), np.random.randint(2), i, a, b, c, np.random.randint(40), np.random.randint(40), i), file=f)
+				else:
+					ctype = np.random.choice(["", "_1"])
+					params = " #(.IS_I_INVERTED(%d), .IS_CE_INVERTED(%d), .CE_TYPE(\"%s\")) " % (np.random.randint(2), np.random.randint(2), np.random.choice(["SYNC", "ASYNC", "HARD_SYNC"])) if ctype == "" else ""
+					print("    BUFGCE%s %s bufg_%d (.I(clk[%d] ^ clk[%d] ^ clk[%d]), .CE(cen[%d]), .O(clk_int[%d]));" %
+						(ctype, params, i, a, b, c, np.random.randint(40), i), file=f)
+			else:
+				print("    assign clk_int[%d] = clk[%d] ^ clk[%d] ^ clk[%d];" % (i, a, b, c), file=f)
+			if i < 64:
+				print("    assign cen_int[%d] = cen[%d] ^ cen[%d];" % (i, a, b), file=f)
+		print("", file=f)
+		print("    wire [%d:0] r;" % M, file=f)
+		print("    assign r[0] = d;", file=f)
+		print("    assign q = r[%d];" % M, file=f)
+		for i in range(M):
+			print("    layer_1_%d submod_%d(.clk(clk_int[%d +: 32]), .cen(cen_int), .d(r[%d]), .q(r[%d]));" % (i, i, 32 * i, i, i+1), file=f)
+		print("endmodule", file=f)
+	with open(root + "/clkroute/clkr%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/clkroute/clkr%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 BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_clk/clkr%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_clk/clkr%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_clk/clkr%d.bit" % (root, x), file=f)
+with open(root + "/clkroute/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 clkr%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/clkr%d.bit %s/frames.txt > %s/specimen_clk/clkr%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tile.json %s/specimen_clk/clkr%d.dump > %s/specimen_clk/clkr%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_clk/clkr%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_clk/clkr%d.tbits" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/rclk_int_3.py b/spec/rclk_int_3.py
new file mode 100644
index 0000000..c057ded
--- /dev/null
+++ b/spec/rclk_int_3.py
@@ -0,0 +1,140 @@
+# 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 = []
+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 "BUFGCE" in site and "HDIO" not in site:
+				bufgces.append(site.split(":")[0])
+			if "SLICEM" in site or "SLICEL" in site:
+				slices.append(site.split(":")[0])
+orig_bufgces = list(bufgces)
+np.random.shuffle(bufgces)
+np.random.shuffle(slices)
+X = 30
+
+root = sys.argv[2]
+
+bufg_prob = np.random.randint(15, 20)
+
+for x in range(X):
+	with open(root + "/clkroute2/clkrb%d.v" % x, "w") as f:
+		N = 256
+		M = np.random.randint(24)
+		for m in range(M):
+			print("module layer_1_%d(input [31:0] clk, input [71:0] cen, input d, output q);" % m, file=f)
+			print("    wire [%d:0] r;" % N, file=f)
+			print("    assign r[0] = d;", file=f)
+			print("    assign q = r[%d];" % N, file=f)
+			print("", file=f)
+			for i in range(N):
+				if N == 0 and len(slices) > 0:
+					loc = slices.pop()
+					print("(* LOC=\"%s\" *)" % loc, file=f)
+				if N != 0 and np.random.randint(16) == 0:
+					print("    wire srl_tmp_%d;" % i, file=f)
+					print("    SRLC32E srl_%d (.CLK(clk[%d]), .CE(cen[%d]), .A(5'b11111), .D(r[%d]), .Q(srl_tmp_%d));" % (i, (i * 32) // N, np.random.randint(72), i, i), file=f)
+					print("    FDCE ff_%d (.C(clk[%d]), .CLR(1'b0), .CE(cen[%d]), .D(srl_tmp_%d), .Q(r[%d]));" % (i, (i * 32) // N, np.random.randint(72), i, i+1), file=f)
+				else:
+					print("    FDCE ff_%d (.C(clk[%d]), .CLR(1'b0), .CE(cen[%d]), .D(r[%d]), .Q(r[%d]));" % (i, (i * 32) // N, np.random.randint(72), i, i+1), file=f)
+			print("endmodule", file=f)
+
+
+		print("module top(input [39:0] clk, cen, input d, output q);", file=f)
+		num_inputs = np.random.randint(8, 40)
+		print("    wire [511:0] clk_int;", file=f)
+		print("    wire [71:0] cen_int;", file=f)
+		print("    assign clk_int[%d:0] = clk;" % (num_inputs - 1), file=f)
+		print("    assign cen_int[%d:0] = cen;" % (num_inputs - 1), file=f)
+		print("    assign cen_int[71:64] = 8'hFF;", file=f)
+
+		bufgces = list(orig_bufgces)
+		np.random.shuffle(bufgces)
+
+		for i in range(num_inputs, 512):
+			a = np.random.randint(num_inputs)
+			b = None
+			while b is None or b == a:
+				b = np.random.randint(num_inputs)
+			c = None
+			while c is None or c == a or c == b:
+				c = np.random.randint(num_inputs)
+			bg = None
+			if len(bufgces) > 0:
+				bg = bufgces.pop()
+			if bg is not None and np.random.randint(20) >= bufg_prob or (bg is not None and "DIV" in bg and np.random.randint(19) >= bufg_prob):
+				if "DIV" in bg:
+					print("    BUFGCE_DIV #(.BUFGCE_DIVIDE(%d), .IS_I_INVERTED(%d), .IS_CE_INVERTED(%d), .IS_CLR_INVERTED(%d)) bufg_%d (.I(clk[%d] ^ clk[%d] ^ clk[%d]), .CLR(!cen[%d]), .CE(cen[%d]), .O(clk_int[%d]));" %
+						(np.random.randint(1, 9), np.random.randint(2), np.random.randint(2), np.random.randint(2), i, a, b, c, np.random.randint(40), np.random.randint(40), i), file=f)
+				else:
+					ctype = np.random.choice(["", "_1"])
+					params = " #(.IS_I_INVERTED(%d), .IS_CE_INVERTED(%d), .CE_TYPE(\"%s\")) " % (np.random.randint(2), np.random.randint(2), np.random.choice(["SYNC", "ASYNC"])) if ctype == "" else ""
+					print("    BUFGCE%s %s bufg_%d (.I(clk[%d] ^ clk[%d] ^ clk[%d]), .CE(cen[%d]), .O(clk_int[%d]));" %
+						(ctype, params, i, a, b, c, np.random.randint(40), i), file=f)
+			else:
+				print("    assign clk_int[%d] = clk[%d] ^ clk[%d] ^ clk[%d];" % (i, a, b, c), file=f)
+			if i < 64:
+				print("    assign cen_int[%d] = cen[%d] ^ cen[%d];" % (i, a, b), file=f)
+		print("", file=f)
+		print("    wire [%d:0] r;" % M, file=f)
+		print("    assign r[0] = d;", file=f)
+		print("    assign q = r[%d];" % M, file=f)
+		for i in range(M):
+			print("    layer_1_%d submod_%d(.clk(clk_int[%d +: 32]), .cen(cen_int), .d(r[%d]), .q(r[%d]));" % (i, i, 32 * i, i, i+1), file=f)
+		print("endmodule", file=f)
+	ccio = ["G23", "F23", "G21", "F22", "F11", "H11", "G10", "H9", "G15", "F17", "E15", "D15", "AH11", "AH12", "AJ9", "AJ10", "AG21", "AH22", "AJ21", "AJ20", "AF18", "AH18", "AJ16", "AJ17"]
+	np.random.shuffle(ccio)
+	with open(root + "/clkroute2/clkrb%d.xdc" % x, "w") as f:
+		for i in range(num_inputs):
+			if i >= len(ccio):
+				break
+			print("set_property PACKAGE_PIN %s [get_ports {clk[%d]}]" % (ccio[i], i), file=f)
+			print("set_property IOSTANDARD LVCMOS18 [get_ports {clk[%d]}]" % (i), file=f)
+
+	with open(root + "/clkroute2/clkrb%d.tcl" % x, "w") as f:
+		print("add_files %s" % (root + ("/clkroute2/clkrb%d.v" % x)), file=f)
+		print("read_xdc %s" % (root + ("/clkroute2/clkrb%d.xdc" % 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 BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]", file=f)
+		print("write_checkpoint -force %s/specimen_clk/clkrb%d.dcp" % (root, x), file=f)
+		print("write_edif -force %s/specimen_clk/clkrb%d.edf" % (root, x), file=f)
+		print("write_bitstream -force %s/specimen_clk/clkrb%d.bit" % (root, x), file=f)
+with open(root + "/clkroute2/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 clkrb%d.tcl" % x, file=f)
+		print("if [ $? -eq 0 ]; then", file=f)
+		print("    ../../ultra/tools/dump_bitstream %s/specimen_clk/clkrb%d.bit %s/frames.txt > %s/specimen_clk/clkrb%d.dump" % (root, x, root, root, x), file=f)
+		print("    python3 ../../ultra/tools/bits_to_tiles.py %s/tile.json %s/specimen_clk/clkrb%d.dump > %s/specimen_clk/clkrb%d.tbits" % (root, root, x, root, x), file=f)
+		print("else", file=f)
+		print("   rm %s/specimen_clk/clkrb%d.dump" % (root, x), file=f)
+		print("   rm %s/specimen_clk/clkrb%d.tbits" % (root, x), file=f)
+		print("   rm %s/specimen_clk/clkrb%d.dcp" % (root, x), file=f)
+		print("   rm %s/specimen_clk/clkrb%d.bit" % (root, x), file=f)
+		print("   rm %s/specimen_clk/clkrb%d.features" % (root, x), file=f)
+		print("fi", file=f)
\ No newline at end of file
diff --git a/spec/slice.v b/spec/slice.v
new file mode 100644
index 0000000..ce33903
--- /dev/null
+++ b/spec/slice.v
@@ -0,0 +1,218 @@
+// 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.
+
+module ultra_slice_logic #(
+	parameter [1023:0] LOC = "",
+	parameter [63:0]   ALUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   BLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   CLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   DLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   ELUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   FLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   GLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   HLUT_INIT = 64'h00C0FFEE,
+
+	parameter [1023:0] AFF_TYPE = "NONE",
+	parameter [1023:0] AFF2_TYPE = "NONE",
+	parameter [1023:0] BFF_TYPE = "NONE",
+	parameter [1023:0] BFF2_TYPE = "NONE",
+	parameter [1023:0] CFF_TYPE = "NONE",
+	parameter [1023:0] CFF2_TYPE = "NONE",
+	parameter [1023:0] DFF_TYPE = "NONE",
+	parameter [1023:0] DFF2_TYPE = "NONE",
+	parameter [1023:0] EFF_TYPE = "NONE",
+	parameter [1023:0] EFF2_TYPE = "NONE",
+	parameter [1023:0] FFF_TYPE = "NONE",
+	parameter [1023:0] FFF2_TYPE = "NONE",
+	parameter [1023:0] GFF_TYPE = "NONE",
+	parameter [1023:0] GFF2_TYPE = "NONE",
+	parameter [1023:0] HFF_TYPE = "NONE",
+	parameter [1023:0] HFF2_TYPE = "NONE",
+
+	parameter [15:0]   FF_INIT = 16'h0000,
+
+	parameter [1023:0] FFMUXA1 = "BYP",
+	parameter [1023:0] FFMUXA2 = "BYP",
+	parameter [1023:0] FFMUXB1 = "BYP",
+	parameter [1023:0] FFMUXB2 = "BYP",
+	parameter [1023:0] FFMUXC1 = "BYP",
+	parameter [1023:0] FFMUXC2 = "BYP",
+	parameter [1023:0] FFMUXD1 = "BYP",
+	parameter [1023:0] FFMUXD2 = "BYP",
+	parameter [1023:0] FFMUXE1 = "BYP",
+	parameter [1023:0] FFMUXE2 = "BYP",
+	parameter [1023:0] FFMUXF1 = "BYP",
+	parameter [1023:0] FFMUXF2 = "BYP",
+	parameter [1023:0] FFMUXG1 = "BYP",
+	parameter [1023:0] FFMUXG2 = "BYP",
+	parameter [1023:0] FFMUXH1 = "BYP",
+	parameter [1023:0] FFMUXH2 = "BYP",
+
+	parameter [1023:0] OUTMUXA = "D5",
+	parameter [1023:0] OUTMUXB = "D5",
+	parameter [1023:0] OUTMUXC = "D5",
+	parameter [1023:0] OUTMUXD = "D5",
+	parameter [1023:0] OUTMUXE = "D5",
+	parameter [1023:0] OUTMUXF = "D5",
+	parameter [1023:0] OUTMUXG = "D5",
+	parameter [1023:0] OUTMUXH = "D5",
+
+	parameter [1:0]    CLKINV = 2'b00, SRINV = 2'b00
+
+) (
+	input [7:0] A1, A2, A3, A4, A5, A6, I, X,
+	input [1:0] CLK, SR,
+	input [3:0] CE,
+	output [7:0] O, Q, Q2, MUX
+);
+	wire [7:0] out5;
+	(* BEL="A5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(ALUT_INIT[31:0])) a5lut (.I0(A1[0]), .I1(A2[0]), .I2(A3[0]), .I3(A4[0]), .I4(A5[0]), .O(out5[0]));
+	(* BEL="A6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(ALUT_INIT[63:32])) a6lut (.I0(A1[0]), .I1(A2[0]), .I2(A3[0]), .I3(A4[0]), .I4(A5[0]), .O(O[0]));
+	(* BEL="B5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(BLUT_INIT[31:0])) b5lut (.I0(A1[1]), .I1(A2[1]), .I2(A3[1]), .I3(A4[1]), .I4(A5[1]), .O(out5[1]));
+	(* BEL="B6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(BLUT_INIT[63:32])) b6lut (.I0(A1[1]), .I1(A2[1]), .I2(A3[1]), .I3(A4[1]), .I4(A5[1]), .O(O[1]));
+	(* BEL="C5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(CLUT_INIT[31:0])) c5lut (.I0(A1[2]), .I1(A2[2]), .I2(A3[2]), .I3(A4[2]), .I4(A5[2]), .O(out5[2]));
+	(* BEL="C6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(CLUT_INIT[63:32])) c6lut (.I0(A1[2]), .I1(A2[2]), .I2(A3[2]), .I3(A4[2]), .I4(A5[2]), .O(O[2]));
+	(* BEL="D5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(DLUT_INIT[31:0])) d5lut (.I0(A1[3]), .I1(A2[3]), .I2(A3[3]), .I3(A4[3]), .I4(A5[3]), .O(out5[3]));
+	(* BEL="D6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(DLUT_INIT[63:32])) d6lut (.I0(A1[3]), .I1(A2[3]), .I2(A3[3]), .I3(A4[3]), .I4(A5[3]), .O(O[3]));
+
+	(* BEL="E5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(ELUT_INIT[31:0])) e5lut (.I0(A1[4]), .I1(A2[4]), .I2(A3[4]), .I3(A4[4]), .I4(A5[4]), .O(out5[4]));
+	(* BEL="E6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(ELUT_INIT[63:32])) e6lut (.I0(A1[4]), .I1(A2[4]), .I2(A3[4]), .I3(A4[4]), .I4(A5[4]), .O(O[4]));
+	(* BEL="F5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(FLUT_INIT[31:0])) f5lut (.I0(A1[5]), .I1(A2[5]), .I2(A3[5]), .I3(A4[5]), .I4(A5[5]), .O(out5[5]));
+	(* BEL="F6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(FLUT_INIT[63:32])) f6lut (.I0(A1[5]), .I1(A2[5]), .I2(A3[5]), .I3(A4[5]), .I4(A5[5]), .O(O[5]));
+	(* BEL="G5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(GLUT_INIT[31:0])) g5lut (.I0(A1[6]), .I1(A2[6]), .I2(A3[6]), .I3(A4[6]), .I4(A5[6]), .O(out5[6]));
+	(* BEL="G6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(GLUT_INIT[63:32])) g6lut (.I0(A1[6]), .I1(A2[6]), .I2(A3[6]), .I3(A4[6]), .I4(A5[6]), .O(O[6]));
+	(* BEL="H5LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(HLUT_INIT[31:0])) h5lut (.I0(A1[7]), .I1(A2[7]), .I2(A3[7]), .I3(A4[7]), .I4(A5[7]), .O(out5[7]));
+	(* BEL="H6LUT", LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(HLUT_INIT[63:32])) h6lut (.I0(A1[7]), .I1(A2[7]), .I2(A3[7]), .I3(A4[7]), .I4(A5[7]), .O(O[7]));
+
+	wire [7:0] f7f8;
+	assign f7f8[0] = 1'b0;
+	(* BEL="F7MUX_AB",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxab_i (.I0(O[1]), .I1(O[0]), .S(X[0]), .O(f7f8[1]));
+	(* BEL="F7MUX_CD",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxcd_i (.I0(O[3]), .I1(O[2]), .S(X[2]), .O(f7f8[3]));
+	(* BEL="F7MUX_EF",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxef_i (.I0(O[5]), .I1(O[4]), .S(X[4]), .O(f7f8[5]));
+	(* BEL="F7MUX_GH",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxgh_i (.I0(O[7]), .I1(O[6]), .S(X[6]), .O(f7f8[7]));
+
+	(* BEL="F8MUX_BOT", LOC=LOC, keep, dont_touch *) MUXF8 f8muxabcd_i (.I0(f7f8[3]), .I1(f7f8[1]), .S(X[1]), .O(f7f8[2]));
+	(* BEL="F8MUX_TOP", LOC=LOC, keep, dont_touch *) MUXF8 f8muxefgh_i (.I0(f7f8[7]), .I1(f7f8[5]), .S(X[5]), .O(f7f8[6]));
+
+	(* BEL="F9MUX",     LOC=LOC, keep, dont_touch *) MUXF9 f9_i (.I0(f7f8[6]), .I1(f7f8[2]), .S(X[3]), .O(f7f8[4]));
+
+	wire [15:0] ffin;
+	ultra_slice_logic_ffmux #(.SEL(FFMUXA1)) ffmuxa1_i (.XORIN(), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(), .BYP(X[0]), .OUT(ffin[0]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXA2)) ffmuxa2_i (.XORIN(), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(), .BYP(I[0]), .OUT(ffin[1]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXB1)) ffmuxb1_i (.XORIN(), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(), .BYP(X[1]), .OUT(ffin[2]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXB2)) ffmuxb2_i (.XORIN(), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(), .BYP(I[1]), .OUT(ffin[3]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXC1)) ffmuxc1_i (.XORIN(), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(), .BYP(X[2]), .OUT(ffin[4]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXC2)) ffmuxc2_i (.XORIN(), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(), .BYP(I[2]), .OUT(ffin[5]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXD1)) ffmuxd1_i (.XORIN(), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(), .BYP(X[3]), .OUT(ffin[6]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXD2)) ffmuxd2_i (.XORIN(), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(), .BYP(I[3]), .OUT(ffin[7]));
+
+	ultra_slice_logic_ffmux #(.SEL(FFMUXE1)) ffmuxe1_i (.XORIN(), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(), .BYP(X[4]), .OUT(ffin[8]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXE2)) ffmuxe2_i (.XORIN(), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(), .BYP(I[4]), .OUT(ffin[9]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXF1)) ffmuxf1_i (.XORIN(), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(), .BYP(X[5]), .OUT(ffin[10]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXF2)) ffmuxf2_i (.XORIN(), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(), .BYP(I[5]), .OUT(ffin[11]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXG1)) ffmuxg1_i (.XORIN(), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(), .BYP(X[6]), .OUT(ffin[12]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXG2)) ffmuxg2_i (.XORIN(), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(), .BYP(I[6]), .OUT(ffin[13]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXH1)) ffmuxh1_i (.XORIN(), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(), .BYP(X[7]), .OUT(ffin[14]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXH2)) ffmuxh2_i (.XORIN(), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(), .BYP(I[7]), .OUT(ffin[15]));
+
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("AFF"),  .TYPE(AFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[0])) aff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[0]), .Q(Q[0]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("AFF2"), .TYPE(AFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[1])) aff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[1]), .Q(Q2[0]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("BFF"),  .TYPE(BFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[2])) bff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[2]), .Q(Q[1]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("BFF2"), .TYPE(BFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[3])) bff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[3]), .Q(Q2[1]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("CFF"),  .TYPE(CFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[4])) cff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[4]), .Q(Q[2]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("CFF2"), .TYPE(CFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[5])) cff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[5]), .Q(Q2[2]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("DFF"),  .TYPE(DFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[6])) dff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[6]), .Q(Q[3]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("DFF2"), .TYPE(DFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[7])) dff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[7]), .Q(Q2[3]));
+
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("EFF"),  .TYPE(EFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[8])) eff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[8]), .Q(Q[4]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("EFF2"), .TYPE(EFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[9])) eff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[9]), .Q(Q2[4]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("FFF"),  .TYPE(FFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[10])) fff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[10]), .Q(Q[5]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("FFF2"), .TYPE(FFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[11])) fff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[11]), .Q(Q2[5]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("GFF"),  .TYPE(GFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[12])) gff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[12]), .Q(Q[6]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("GFF2"), .TYPE(GFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[13])) gff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[13]), .Q(Q2[6]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("HFF"),  .TYPE(HFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[14])) hff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[14]), .Q(Q[7]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("HFF2"), .TYPE(HFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[15])) hff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[15]), .Q(Q2[7]));
+
+	ultra_slice_logic_outmux #(.SEL(OUTMUXA)) outmuxa_i (.XORIN(), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(), .OUT(MUX[0]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXB)) outmuxb_i (.XORIN(), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(), .OUT(MUX[1]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXC)) outmuxc_i (.XORIN(), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(), .OUT(MUX[2]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXD)) outmuxd_i (.XORIN(), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(), .OUT(MUX[3]));
+
+	ultra_slice_logic_outmux #(.SEL(OUTMUXE)) outmuxe_i (.XORIN(), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(), .OUT(MUX[4]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXF)) outmuxf_i (.XORIN(), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(), .OUT(MUX[5]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXG)) outmuxg_i (.XORIN(), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(), .OUT(MUX[6]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXH)) outmuxh_i (.XORIN(), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(), .OUT(MUX[7]));
+
+
+endmodule
+
+module ultra_slice_logic_ffmux #(
+	parameter [1023:0] SEL = "BYP"
+) (
+	input XORIN, F7F8, D6, D5, CY, BYP,
+	output OUT
+);
+	generate
+		case(SEL)
+			"XORIN": assign OUT = XORIN;
+			"F7F8":  assign OUT = F7F8;
+			"D6":    assign OUT = D6;
+			"D5":    assign OUT = D5;
+			"CY":    assign OUT = CY;
+			"BYP":   assign OUT = BYP;
+		endcase		
+	endgenerate
+endmodule
+
+module ultra_slice_logic_ffx #(
+	parameter [1023:0] LOC = "",
+	parameter [1023:0] BEL = "",
+	parameter [1023:0] TYPE = "",
+	parameter CLKINV = 1'b0,
+	parameter SRINV = 1'b0,
+	parameter INIT = 1'b0
+) (
+	input C, CE, SR, D,
+	output Q
+);
+	generate
+		case (TYPE)
+			"FDPE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDPE #(.IS_C_INVERTED(CLKINV), .IS_PRE_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .PRE(SR), .D(D), .Q(Q));
+			"FDCE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDCE #(.IS_C_INVERTED(CLKINV), .IS_CLR_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .CLR(SR), .D(D), .Q(Q));
+			"FDSE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDSE #(.IS_C_INVERTED(CLKINV), .IS_S_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .S(SR), .D(D), .Q(Q));
+			"FDRE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDRE #(.IS_C_INVERTED(CLKINV), .IS_R_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .R(SR), .D(D), .Q(Q));
+			
+			"LDPE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) LDPE #(.IS_G_INVERTED(CLKINV), .IS_PRE_INVERTED(SRINV), .INIT(INIT)) ff_i (.G(C), .GE(CE), .PRE(SR), .D(D), .Q(Q));
+			"LDCE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) LDCE #(.IS_G_INVERTED(CLKINV), .IS_CLR_INVERTED(SRINV), .INIT(INIT)) ff_i (.G(C), .GE(CE), .CLR(SR), .D(D), .Q(Q));
+			"NONE": assign Q = INIT;
+		endcase
+	endgenerate
+endmodule
+
+module ultra_slice_logic_outmux #(
+	parameter SEL = "D5"
+) (
+	input XORIN, F7F8, D6, D5, CY,
+	output OUT
+);
+	generate
+		case(SEL)
+			"XORIN": assign OUT = XORIN;
+			"F7F8":  assign OUT = F7F8;
+			"D6":    assign OUT = D6;
+			"D5":    assign OUT = D5;
+			"CY":    assign OUT = CY;
+		endcase		
+	endgenerate
+endmodule
\ No newline at end of file
diff --git a/spec/slice_carry.py b/spec/slice_carry.py
new file mode 100644
index 0000000..d095b75
--- /dev/null
+++ b/spec/slice_carry.py
@@ -0,0 +1,111 @@
+# 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
+
+print("module top(input [15:0] clk, sr, ce, input [15:0] d, output [31:0] q);")
+N = 350
+D = ["d[%d]" % i for i in range(16)]
+
+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 "SLICEM" in site or "SLICEL" in site:
+				slices.append(site.split(":")[0])
+
+np.random.shuffle(slices)
+
+for i in range(N):
+	sl = slices.pop()
+	ffmode = np.random.randint(3, size=2)
+	clk = tuple(np.random.randint(16, size=2))
+	sr = tuple(["1'b1" if y >= 16 else "sr[%d]" % y for y in np.random.randint(25, size=2)])
+	ce = tuple(["1'b1" if y >= 16 else "ce[%d]" % y for y in np.random.randint(25, size=4)])
+
+	def random_fftype(mode):
+		if mode == 0:
+			return np.random.choice(["FDSE", "FDRE"])
+		elif mode == 1:
+			return np.random.choice(["FDPE", "FDCE"])
+		elif mode == 2:
+			return np.random.choice(["LDPE", "LDCE"])
+
+	def random_bit():
+		return np.random.choice(D)
+
+	def random_data(width):
+		return "{%s}" % (", ".join([random_bit() for k in range(width)]))
+
+	fftypes = [random_fftype(ffmode[j // 8]) for j in range(16)]
+	used_outroute = [np.random.choice(["FF", "FF2", "MUX"]) for k in range(8)]
+	for j in range(8):
+		if used_outroute[j] == "FF":
+			fftypes[2*j + 1] = "NONE"
+		elif used_outroute[j] == "FF2":
+			fftypes[2*j] = "NONE"		
+		else:
+			fftypes[2*j] = "NONE"
+			fftypes[2*j + 1] = "NONE"
+	cmode = np.random.choice(["NONE", "SINGLE_CY8", "DUAL_CY4"], p=[0.1, 0.7, 0.2])
+
+	print('   wire [31:0] d%d;' % i)
+	print('   ultra_slice_carry #(')
+	print('      .LOC("%s"),' % sl)
+	for lut in "ABCDEFGH":
+		print("      .%sLUT_INIT(64'b%s)," % (lut, "".join(str(_) for _ in np.random.randint(2, size=64))))
+	for j in range(16):
+		print('      .%sFF%s_TYPE("%s"),' % ("ABCDEFGH"[j//2], "2" if (j % 2) == 1 else "", fftypes[j]))
+	print("      .FF_INIT(16'b%s)," % "".join(str(_) for _ in np.random.randint(2, size=16)))
+	for j1 in "ABCDEFGH":
+		for j2 in ("1", "2"):
+			print('        .FFMUX%s%s("%s"),' % (j1, j2, np.random.choice(["XORIN", "CY"] if cmode != "NONE" else ["D6"])))
+	for j in range(8):
+		print('        .OUTMUX%s("%s"),' % ("ABCDEFGH"[j], ("D6" if used_outroute[j] != "MUX" else np.random.choice(["D6", "XORIN", "CY"] if cmode != "NONE" else ["D6"]))))
+	print("      .CLKINV(2'd%d)," % np.random.randint(4))
+	print("      .SRINV(2'd%d)," % np.random.randint(4))
+	print('      .CARRY_TYPE("%s"),' % cmode)
+	for j in range (8):
+		print('      .DI%dMUX("%s"),' % (j, np.random.choice(["DI", "X"])))
+	print('      .CIMUX("%s"),' % np.random.choice(["1", "0", "X"]))
+	print('      .CITOPMUX("%s")' % (np.random.choice(["1", "0", "X"]) if cmode == "DUAL_CY4" else "CI"))
+	print('   ) slice%d (' % i)
+	for j in range(1, 7):
+		print("      .A%d(%s)," % (j, random_data(8)))
+	print("      .I(%s)," % random_data(8))
+	print("      .X(%s)," % random_data(8))
+	print("      .CLK({clk[%d], clk[%d]})," % clk)
+	print("      .SR({%s, %s})," % sr)
+	print("      .CE({%s, %s, %s, %s})," % ce)
+	print("      .O(d%d[7:0])," % i)
+	print("      .Q(d%d[15:8])," % i)
+	print("      .Q2(d%d[23:16])," % i)
+	print("      .MUX(d%d[31:24])" % i)
+	print('   );')
+	print()
+	
+	D.clear()
+	for j in range(8):
+		D.append("d%d[%d]" % (i, j))
+		D.append("d%d[%d]" % (i, 24 + j))
+		if fftypes[2 * j] != "NONE":
+			D.append("d%d[%d]" % (i, 8 + j))
+		if fftypes[2 * j + 1] != "NONE":
+			D.append("d%d[%d]" % (i, 16 + j))
+print("    assign q = d%d;" % (N-1))
+print("endmodule")
\ No newline at end of file
diff --git a/spec/slice_carry.v b/spec/slice_carry.v
new file mode 100644
index 0000000..4eaeaee
--- /dev/null
+++ b/spec/slice_carry.v
@@ -0,0 +1,286 @@
+// 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.
+
+module ultra_slice_carry #(
+	parameter [1023:0] LOC = "",
+	parameter [63:0]   ALUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   BLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   CLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   DLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   ELUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   FLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   GLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   HLUT_INIT = 64'h00C0FFEE,
+
+	parameter [1023:0] AFF_TYPE = "NONE",
+	parameter [1023:0] AFF2_TYPE = "NONE",
+	parameter [1023:0] BFF_TYPE = "NONE",
+	parameter [1023:0] BFF2_TYPE = "NONE",
+	parameter [1023:0] CFF_TYPE = "NONE",
+	parameter [1023:0] CFF2_TYPE = "NONE",
+	parameter [1023:0] DFF_TYPE = "NONE",
+	parameter [1023:0] DFF2_TYPE = "NONE",
+	parameter [1023:0] EFF_TYPE = "NONE",
+	parameter [1023:0] EFF2_TYPE = "NONE",
+	parameter [1023:0] FFF_TYPE = "NONE",
+	parameter [1023:0] FFF2_TYPE = "NONE",
+	parameter [1023:0] GFF_TYPE = "NONE",
+	parameter [1023:0] GFF2_TYPE = "NONE",
+	parameter [1023:0] HFF_TYPE = "NONE",
+	parameter [1023:0] HFF2_TYPE = "NONE",
+
+	parameter [15:0]   FF_INIT = 16'h0000,
+
+	parameter [1023:0] FFMUXA1 = "BYP",
+	parameter [1023:0] FFMUXA2 = "BYP",
+	parameter [1023:0] FFMUXB1 = "BYP",
+	parameter [1023:0] FFMUXB2 = "BYP",
+	parameter [1023:0] FFMUXC1 = "BYP",
+	parameter [1023:0] FFMUXC2 = "BYP",
+	parameter [1023:0] FFMUXD1 = "BYP",
+	parameter [1023:0] FFMUXD2 = "BYP",
+	parameter [1023:0] FFMUXE1 = "BYP",
+	parameter [1023:0] FFMUXE2 = "BYP",
+	parameter [1023:0] FFMUXF1 = "BYP",
+	parameter [1023:0] FFMUXF2 = "BYP",
+	parameter [1023:0] FFMUXG1 = "BYP",
+	parameter [1023:0] FFMUXG2 = "BYP",
+	parameter [1023:0] FFMUXH1 = "BYP",
+	parameter [1023:0] FFMUXH2 = "BYP",
+
+	parameter [1023:0] OUTMUXA = "D5",
+	parameter [1023:0] OUTMUXB = "D5",
+	parameter [1023:0] OUTMUXC = "D5",
+	parameter [1023:0] OUTMUXD = "D5",
+	parameter [1023:0] OUTMUXE = "D5",
+	parameter [1023:0] OUTMUXF = "D5",
+	parameter [1023:0] OUTMUXG = "D5",
+	parameter [1023:0] OUTMUXH = "D5",
+
+	parameter [1023:0] DI0MUX  = "DI",
+	parameter [1023:0] DI1MUX  = "DI",
+	parameter [1023:0] DI2MUX  = "DI",
+	parameter [1023:0] DI3MUX  = "DI",
+	parameter [1023:0] DI4MUX  = "DI",
+	parameter [1023:0] DI5MUX  = "DI",
+	parameter [1023:0] DI6MUX  = "DI",
+	parameter [1023:0] DI7MUX  = "DI",
+
+	parameter [1023:0] CARRY_TYPE  = "NONE",
+
+	parameter [1023:0] CIMUX  = "CI",
+	parameter [1023:0] CITOPMUX  = "CI",
+
+	parameter [1:0]    CLKINV = 2'b00, SRINV = 2'b00
+
+) (
+	input [7:0] A1, A2, A3, A4, A5, A6, I, X,
+	input [1:0] CLK, SR,
+	input [3:0] CE,
+	output [7:0] O, Q, Q2, MUX
+);
+	wire [7:0] out5;
+	generate
+	if (DI0MUX == "DI") (* BEL="A5LUT", LOC=LOC *) LUT4 #(.INIT(ALUT_INIT[15:0])) a5lut (.I0(A1[0]), .I1(A2[0]), .I2(A3[0]), .I3(A4[0]), .O(out5[0]));
+	(* BEL="A6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(ALUT_INIT[47:32])) a6lut (.I0(A1[0]), .I1(A2[0]), .I2(A3[0]), .I3(A4[0]), .O(O[0]));
+	if (DI1MUX == "DI") (* BEL="B5LUT", LOC=LOC *) LUT4 #(.INIT(BLUT_INIT[15:0])) b5lut (.I0(A1[1]), .I1(A2[1]), .I2(A3[1]), .I3(A4[1]), .O(out5[1]));
+	(* BEL="B6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(BLUT_INIT[47:32])) b6lut (.I0(A1[1]), .I1(A2[1]), .I2(A3[1]), .I3(A4[1]), .O(O[1]));
+	if (DI2MUX == "DI") (* BEL="C5LUT", LOC=LOC *) LUT4 #(.INIT(CLUT_INIT[15:0])) c5lut (.I0(A1[2]), .I1(A2[2]), .I2(A3[2]), .I3(A4[2]),.O(out5[2]));
+	(* BEL="C6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(CLUT_INIT[47:32])) c6lut (.I0(A1[2]), .I1(A2[2]), .I2(A3[2]), .I3(A4[2]), .O(O[2]));
+	if (DI3MUX == "DI") (* BEL="D5LUT", LOC=LOC *) LUT4 #(.INIT(DLUT_INIT[15:0])) d5lut (.I0(A1[3]), .I1(A2[3]), .I2(A3[3]), .I3(A4[3]),  .O(out5[3]));
+	(* BEL="D6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(DLUT_INIT[47:32])) d6lut (.I0(A1[3]), .I1(A2[3]), .I2(A3[3]), .I3(A4[3]), .O(O[3]));
+
+	if (DI4MUX == "DI") (* BEL="E5LUT", LOC=LOC *) LUT4 #(.INIT(ELUT_INIT[15:0])) e5lut (.I0(A1[4]), .I1(A2[4]), .I2(A3[4]), .I3(A4[4]), .O(out5[4]));
+	(* BEL="E6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(ELUT_INIT[47:32])) e6lut (.I0(A1[4]), .I1(A2[4]), .I2(A3[4]), .I3(A4[4]), .O(O[4]));
+	if (DI5MUX == "DI") (* BEL="F5LUT", LOC=LOC *) LUT4 #(.INIT(FLUT_INIT[15:0])) f5lut (.I0(A1[5]), .I1(A2[5]), .I2(A3[5]), .I3(A4[5]),  .O(out5[5]));
+	(* BEL="F6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(FLUT_INIT[47:32])) f6lut (.I0(A1[5]), .I1(A2[5]), .I2(A3[5]), .I3(A4[5]), .O(O[5]));
+	if (DI6MUX == "DI") (* BEL="G5LUT", LOC=LOC *) LUT4 #(.INIT(GLUT_INIT[15:0])) g5lut (.I0(A1[6]), .I1(A2[6]), .I2(A3[6]), .I3(A4[6]), .O(out5[6]));
+	(* BEL="G6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(GLUT_INIT[47:32])) g6lut (.I0(A1[6]), .I1(A2[6]), .I2(A3[6]), .I3(A4[6]), .O(O[6]));
+	if (DI7MUX == "DI") (* BEL="H5LUT", LOC=LOC *) LUT4 #(.INIT(HLUT_INIT[15:0])) h5lut (.I0(A1[7]), .I1(A2[7]), .I2(A3[7]), .I3(A4[7]), .O(out5[7]));
+	(* BEL="H6LUT", LOC=LOC, keep, dont_touch *) LUT4 #(.INIT(HLUT_INIT[47:32])) h6lut (.I0(A1[7]), .I1(A2[7]), .I2(A3[7]), .I3(A4[7]), .O(O[7]));
+	endgenerate
+
+	wire [7:0] f7f8;
+	assign f7f8 = 8'h55;
+/*
+	(* BEL="F7MUX_AB",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxab_i (.I0(O[1]), .I1(O[0]), .S(X[0]), .O(f7f8[1]));
+	(* BEL="F7MUX_CD",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxcd_i (.I0(O[3]), .I1(O[2]), .S(X[2]), .O(f7f8[3]));
+	(* BEL="F7MUX_EF",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxef_i (.I0(O[5]), .I1(O[4]), .S(X[4]), .O(f7f8[5]));
+	(* BEL="F7MUX_GH",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxgh_i (.I0(O[7]), .I1(O[6]), .S(X[6]), .O(f7f8[7]));
+
+	(* BEL="F8MUX_BOT", LOC=LOC, keep, dont_touch *) MUXF8 f8muxabcd_i (.I0(f7f8[3]), .I1(f7f8[1]), .S(X[1]), .O(f7f8[2]));
+	(* BEL="F8MUX_TOP", LOC=LOC, keep, dont_touch *) MUXF8 f8muxefgh_i (.I0(f7f8[7]), .I1(f7f8[5]), .S(X[5]), .O(f7f8[6]));
+
+	(* BEL="F9MUX",     LOC=LOC, keep, dont_touch *) MUXF9 f9_i (.I0(f7f8[6]), .I1(f7f8[2]), .S(X[3]), .O(f7f8[4]));
+*/
+	wire [7:0] di;
+	wire ci, ci_top;
+	ultra_slice_carry_dimux #(.SEL(DI0MUX)) di0mux_i (.DI(out5[0]), .X(X[0]), .OUT(di[0]));
+	ultra_slice_carry_dimux #(.SEL(DI1MUX)) di1mux_i (.DI(out5[1]), .X(X[1]), .OUT(di[1]));
+	ultra_slice_carry_dimux #(.SEL(DI2MUX)) di2mux_i (.DI(out5[2]), .X(X[2]), .OUT(di[2]));
+	ultra_slice_carry_dimux #(.SEL(DI3MUX)) di3mux_i (.DI(out5[3]), .X(X[3]), .OUT(di[3]));
+	ultra_slice_carry_dimux #(.SEL(DI4MUX)) di4mux_i (.DI(out5[4]), .X(X[4]), .OUT(di[4]));
+	ultra_slice_carry_dimux #(.SEL(DI5MUX)) di5mux_i (.DI(out5[5]), .X(X[5]), .OUT(di[5]));
+	ultra_slice_carry_dimux #(.SEL(DI6MUX)) di6mux_i (.DI(out5[6]), .X(X[6]), .OUT(di[6]));
+	ultra_slice_carry_dimux #(.SEL(DI7MUX)) di7mux_i (.DI(out5[7]), .X(X[7]), .OUT(di[7]));
+
+	ultra_slice_carry_cimux #(.SEL(CIMUX)) cimux_i (.CI(), .X(X[0]), .OUT(ci));
+	ultra_slice_carry_cimux #(.SEL(CITOPMUX)) citopmux_i (.CI(), .X(X[4]), .OUT(ci_top));
+
+	wire [7:0] xoro, cout;
+
+	generate
+		if (CARRY_TYPE != "NONE")
+			(* BEL="CARRY8", LOC=LOC, keep, dont_touch *) CARRY8 #(.CARRY_TYPE(CARRY_TYPE)) c8_i(.CI(ci), .CI_TOP(ci_top), .DI(di), .S(O), .O(xoro), .CO(cout));
+	endgenerate
+
+	wire [15:0] ffin;
+	ultra_slice_logic_ffmux #(.SEL(FFMUXA1)) ffmuxa1_i (.XORIN(xoro[0]), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(cout[0]), .BYP(), .OUT(ffin[0]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXA2)) ffmuxa2_i (.XORIN(xoro[0]), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(cout[0]), .BYP(I[0]), .OUT(ffin[1]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXB1)) ffmuxb1_i (.XORIN(xoro[1]), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(cout[1]), .BYP(), .OUT(ffin[2]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXB2)) ffmuxb2_i (.XORIN(xoro[1]), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(cout[1]), .BYP(I[1]), .OUT(ffin[3]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXC1)) ffmuxc1_i (.XORIN(xoro[2]), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(cout[2]), .BYP(), .OUT(ffin[4]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXC2)) ffmuxc2_i (.XORIN(xoro[2]), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(cout[2]), .BYP(I[2]), .OUT(ffin[5]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXD1)) ffmuxd1_i (.XORIN(xoro[3]), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(cout[3]), .BYP(), .OUT(ffin[6]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXD2)) ffmuxd2_i (.XORIN(xoro[3]), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(cout[3]), .BYP(I[3]), .OUT(ffin[7]));
+
+	ultra_slice_logic_ffmux #(.SEL(FFMUXE1)) ffmuxe1_i (.XORIN(xoro[4]), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(cout[4]), .BYP(), .OUT(ffin[8]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXE2)) ffmuxe2_i (.XORIN(xoro[4]), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(cout[4]), .BYP(I[4]), .OUT(ffin[9]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXF1)) ffmuxf1_i (.XORIN(xoro[5]), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(cout[5]), .BYP(), .OUT(ffin[10]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXF2)) ffmuxf2_i (.XORIN(xoro[5]), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(cout[5]), .BYP(I[5]), .OUT(ffin[11]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXG1)) ffmuxg1_i (.XORIN(xoro[6]), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(cout[6]), .BYP(), .OUT(ffin[12]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXG2)) ffmuxg2_i (.XORIN(xoro[6]), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(cout[6]), .BYP(I[6]), .OUT(ffin[13]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXH1)) ffmuxh1_i (.XORIN(xoro[7]), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(cout[7]), .BYP(), .OUT(ffin[14]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXH2)) ffmuxh2_i (.XORIN(xoro[7]), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(cout[7]), .BYP(I[7]), .OUT(ffin[15]));
+
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("AFF"),  .TYPE(AFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[0])) aff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[0]), .Q(Q[0]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("AFF2"), .TYPE(AFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[1])) aff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[1]), .Q(Q2[0]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("BFF"),  .TYPE(BFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[2])) bff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[2]), .Q(Q[1]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("BFF2"), .TYPE(BFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[3])) bff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[3]), .Q(Q2[1]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("CFF"),  .TYPE(CFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[4])) cff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[4]), .Q(Q[2]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("CFF2"), .TYPE(CFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[5])) cff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[5]), .Q(Q2[2]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("DFF"),  .TYPE(DFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[6])) dff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[6]), .Q(Q[3]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("DFF2"), .TYPE(DFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[7])) dff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[7]), .Q(Q2[3]));
+
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("EFF"),  .TYPE(EFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[8])) eff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[8]), .Q(Q[4]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("EFF2"), .TYPE(EFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[9])) eff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[9]), .Q(Q2[4]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("FFF"),  .TYPE(FFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[10])) fff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[10]), .Q(Q[5]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("FFF2"), .TYPE(FFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[11])) fff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[11]), .Q(Q2[5]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("GFF"),  .TYPE(GFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[12])) gff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[12]), .Q(Q[6]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("GFF2"), .TYPE(GFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[13])) gff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[13]), .Q(Q2[6]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("HFF"),  .TYPE(HFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[14])) hff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[14]), .Q(Q[7]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("HFF2"), .TYPE(HFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[15])) hff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[15]), .Q(Q2[7]));
+
+	ultra_slice_logic_outmux #(.SEL(OUTMUXA)) outmuxa_i (.XORIN(xoro[0]), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(cout[0]), .OUT(MUX[0]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXB)) outmuxb_i (.XORIN(xoro[1]), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(cout[1]), .OUT(MUX[1]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXC)) outmuxc_i (.XORIN(xoro[2]), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(cout[2]), .OUT(MUX[2]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXD)) outmuxd_i (.XORIN(xoro[3]), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(cout[3]), .OUT(MUX[3]));
+
+	ultra_slice_logic_outmux #(.SEL(OUTMUXE)) outmuxe_i (.XORIN(xoro[4]), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(cout[4]), .OUT(MUX[4]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXF)) outmuxf_i (.XORIN(xoro[5]), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(cout[5]), .OUT(MUX[5]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXG)) outmuxg_i (.XORIN(xoro[6]), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(cout[6]), .OUT(MUX[6]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXH)) outmuxh_i (.XORIN(xoro[7]), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(cout[7]), .OUT(MUX[7]));
+
+
+endmodule
+
+module ultra_slice_logic_ffmux #(
+	parameter [1023:0] SEL = "BYP"
+) (
+	input XORIN, F7F8, D6, D5, CY, BYP,
+	output OUT
+);
+	generate
+		case(SEL)
+			"XORIN": assign OUT = XORIN;
+			"F7F8":  assign OUT = F7F8;
+			"D6":    assign OUT = D6;
+			"D5":    assign OUT = D5;
+			"CY":    assign OUT = CY;
+			"BYP":   assign OUT = BYP;
+		endcase		
+	endgenerate
+endmodule
+
+module ultra_slice_logic_ffx #(
+	parameter [1023:0] LOC = "",
+	parameter [1023:0] BEL = "",
+	parameter [1023:0] TYPE = "",
+	parameter CLKINV = 1'b0,
+	parameter SRINV = 1'b0,
+	parameter INIT = 1'b0
+) (
+	input C, CE, SR, D,
+	output Q
+);
+	generate
+		case (TYPE)
+			"FDPE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDPE #(.IS_C_INVERTED(CLKINV), .IS_PRE_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .PRE(SR), .D(D), .Q(Q));
+			"FDCE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDCE #(.IS_C_INVERTED(CLKINV), .IS_CLR_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .CLR(SR), .D(D), .Q(Q));
+			"FDSE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDSE #(.IS_C_INVERTED(CLKINV), .IS_S_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .S(SR), .D(D), .Q(Q));
+			"FDRE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDRE #(.IS_C_INVERTED(CLKINV), .IS_R_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .R(SR), .D(D), .Q(Q));
+			
+			"LDPE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) LDPE #(.IS_G_INVERTED(CLKINV), .IS_PRE_INVERTED(SRINV), .INIT(INIT)) ff_i (.G(C), .GE(CE), .PRE(SR), .D(D), .Q(Q));
+			"LDCE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) LDCE #(.IS_G_INVERTED(CLKINV), .IS_CLR_INVERTED(SRINV), .INIT(INIT)) ff_i (.G(C), .GE(CE), .CLR(SR), .D(D), .Q(Q));
+			"NONE": assign Q = INIT;
+		endcase
+	endgenerate
+endmodule
+
+module ultra_slice_logic_outmux #(
+	parameter SEL = "D5"
+) (
+	input XORIN, F7F8, D6, D5, CY,
+	output OUT
+);
+	generate
+		case(SEL)
+			"XORIN": assign OUT = XORIN;
+			"F7F8":  assign OUT = F7F8;
+			"D6":    assign OUT = D6;
+			"D5":    assign OUT = D5;
+			"CY":    assign OUT = CY;
+		endcase		
+	endgenerate
+endmodule
+
+module ultra_slice_carry_dimux #(
+	parameter SEL = "DI"
+) (
+	input DI, X,
+	output OUT
+);
+	generate
+		case(SEL)
+			"DI": assign OUT = DI;
+			"X":  assign OUT = X;
+		endcase		
+	endgenerate
+endmodule
+
+module ultra_slice_carry_cimux #(
+	parameter SEL = "CI"
+) (
+	input CI, X,
+	output OUT
+);
+	generate
+		case(SEL)
+			"CI": assign OUT = CI;
+			"0":  assign OUT = 1'b0;
+			"1":  assign OUT = 1'b1;
+			"X":  assign OUT = X;
+		endcase		
+	endgenerate
+endmodule
\ No newline at end of file
diff --git a/spec/slice_logic.py b/spec/slice_logic.py
new file mode 100644
index 0000000..ce6f46d
--- /dev/null
+++ b/spec/slice_logic.py
@@ -0,0 +1,95 @@
+# 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
+
+print("module top(input [15:0] clk, sr, ce, input [7:0] d, output [31:0] q);")
+N = 500
+D = ["d[%d]" % i for i in range(8)]
+
+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 "SLICEM" in site or "SLICEL" in site:
+				slices.append(site.split(":")[0])
+
+np.random.shuffle(slices)
+
+for i in range(N):
+	sl = slices.pop()
+	ffmode = np.random.randint(3, size=2)
+	clk = tuple(np.random.randint(16, size=2))
+	sr = tuple(["1'b1" if y >= 16 else "sr[%d]" % y for y in np.random.randint(25, size=2)])
+	ce = tuple(["1'b1" if y >= 16 else "ce[%d]" % y for y in np.random.randint(25, size=4)])
+
+	def random_fftype(mode):
+		if mode == 0:
+			return np.random.choice(["NONE", "FDSE", "FDRE"])
+		elif mode == 1:
+			return np.random.choice(["NONE", "FDPE", "FDCE"])
+		elif mode == 2:
+			return np.random.choice(["NONE", "LDPE", "LDCE"])
+
+	def random_bit():
+		return np.random.choice(D)
+
+	def random_data(width):
+		return "{%s}" % (", ".join([random_bit() for k in range(width)]))
+
+	fftypes = [random_fftype(ffmode[j // 8]) for j in range(16)]
+
+	print('   wire [31:0] d%d;' % i)
+	print('   ultra_slice_logic #(')
+	print('      .LOC("%s"),' % sl)
+	for lut in "ABCDEFGH":
+		print("      .%sLUT_INIT(64'b%s)," % (lut, "".join(str(_) for _ in np.random.randint(2, size=64))))
+	for j in range(16):
+		print('      .%sFF%s_TYPE("%s"),' % ("ABCDEFGH"[j//2], "2" if (j % 2) == 1 else "", fftypes[j]))
+	print("      .FF_INIT(16'b%s)," % "".join(str(_) for _ in np.random.randint(2, size=16)))
+	for j1 in "ABCDEFGH":
+		for j2 in ("1", "2"):
+			print('        .FFMUX%s%s("%s"),' % (j1, j2, np.random.choice(["F7F8", "D6", "D5", "BYP"])))
+	for j in "ABCDEFGH":
+		print('        .OUTMUX%s("%s"),' % (j, np.random.choice(["F7F8", "D6", "D5"])))
+	print("      .CLKINV(2'd%d)," % np.random.randint(4))
+	print("      .SRINV(2'd%d)" % np.random.randint(4))
+	print('   ) slice%d (' % i)
+	for j in range(1, 7):
+		print("      .A%d(%s)," % (j, random_data(8)))
+	print("      .I(%s)," % random_data(8))
+	print("      .X(%s)," % random_data(8))
+	print("      .CLK({clk[%d], clk[%d]})," % clk)
+	print("      .SR({%s, %s})," % sr)
+	print("      .CE({%s, %s, %s, %s})," % ce)
+	print("      .O(d%d[7:0])," % i)
+	print("      .Q(d%d[15:8])," % i)
+	print("      .Q2(d%d[23:16])," % i)
+	print("      .MUX(d%d[31:24])" % i)
+	print('   );')
+	print()
+	D.clear()
+	for j in range(8):
+		D.append("d%d[%d]" % (i, j))
+		D.append("d%d[%d]" % (i, 24 + j))
+		if fftypes[2 * j] != "NONE":
+			D.append("d%d[%d]" % (i, 8 + j))
+		if fftypes[2 * j + 1] != "NONE":
+			D.append("d%d[%d]" % (i, 16 + j))
+print("    assign q = d%d;" % (N-1))
+print("endmodule")
\ No newline at end of file
diff --git a/spec/slice_memory.py b/spec/slice_memory.py
new file mode 100644
index 0000000..06130cb
--- /dev/null
+++ b/spec/slice_memory.py
@@ -0,0 +1,143 @@
+# 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
+
+print("module top(input [15:0] clk, sr, ce, input [7:0] d, output [31:0] q);")
+N = 350
+D = ["d[%d]" % i for i in range(8)]
+
+slices = []
+with open(sys.argv[1], "r") as tf:
+	for line in tf:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		if "CLEM" not in sl[3]:
+			continue
+		for site in sl[4:]:
+			if "SLICEM" in site or "SLICEL" in site:
+				slices.append(site.split(":")[0])
+
+np.random.shuffle(slices)
+
+for i in range(N):
+	sl = slices.pop()
+	ffmode = np.random.randint(3, size=2)
+	clk = tuple(np.random.randint(16, size=2))
+	wclk = None
+	while wclk is None or wclk in clk:
+		wclk = np.random.randint(16)
+	sr = tuple(["1'b1" if y >= 16 else "sr[%d]" % y for y in np.random.randint(25, size=2)])
+	ce = tuple(["1'b1" if y >= 16 else "ce[%d]" % y for y in np.random.randint(25, size=4)])
+	we = np.random.randint(16)
+	def random_fftype(mode):
+		if mode == 0:
+			return np.random.choice(["NONE", "FDSE", "FDRE"])
+		elif mode == 1:
+			return np.random.choice(["NONE", "FDPE", "FDCE"])
+		elif mode == 2:
+			return np.random.choice(["NONE", "LDPE", "LDCE"])
+
+	def random_bit():
+		return np.random.choice(D)
+
+	def random_data(width):
+		return "{%s}" % (", ".join([random_bit() for k in range(width)]))
+
+	#fftypes = [random_fftype(ffmode[j // 8]) for j in range(16)]
+	fftypes = ["NONE" for j in range(16)]
+
+	dimux = []
+	mode = []
+	ram_legal = True
+	for lut in "HGFEDCBA":
+		choices = ["LOGIC"]
+		if lut == "H":
+			choices += ["RAMD64", "RAMS64", "RAMD32", "SRL16", "SRL32"]
+		else:
+			if mode[0][0:3] != "RAM":
+				choices += ["SRL16", "SRL32"]
+			if ram_legal:
+				choices.append(mode[0])
+		p = [0.1]
+		for j in range(1, len(choices)):
+			p.append(0.9 / (len(choices) - 1))
+		if len(choices) == 1:
+			p[0] = 1
+		next_mode = np.random.choice(choices, p=p)
+		if len(mode) > 0 and mode[-1] == "SRL32" and next_mode == "SRL32":
+			dimux.append(np.random.choice(["DI", "SIN"], p=[0.2, 0.8]))
+		else:
+			dimux.append("DI")
+		if next_mode[0:3] != "RAM":
+			ram_legal = False
+		mode.append(next_mode)
+
+	dimux = list(reversed(dimux))
+	mode = list(reversed(mode))
+
+	print('   wire [31:0] d%d;' % i)
+	print('   ultra_slice_memory #(')
+	print('      .LOC("%s"),' % sl)
+	for j in range(8):
+		print('      .%s_MODE("%s"),' % ("ABCDEFGH"[j], mode[j]))
+	for lut in "ABCDEFGH":
+		print("      .%sLUT_INIT(64'b%s)," % (lut, "".join(str(_) for _ in np.random.randint(2, size=64))))
+	for j in range(16):
+		print('      .%sFF%s_TYPE("%s"),' % ("ABCDEFGH"[j//2], "2" if (j % 2) == 1 else "", fftypes[j]))
+	print("      .FF_INIT(16'b%s)," % "".join(str(_) for _ in np.random.randint(2, size=16)))
+	for j1 in "ABCDEFGH":
+		for j2 in ("1", "2"):
+			print('        .FFMUX%s%s("%s"),' % (j1, j2, np.random.choice(["F7F8", "D6", "D5"])))
+	for j in "ABCDEFGH":
+		print('        .OUTMUX%s("%s"),' % (j, np.random.choice(["F7F8", "D6", "D5"])))
+	for j in range(7):
+		print('      .DIMUX%s("%s"),' % ("ABCDEFG"[j], dimux[j]))
+	print("      .WCLKINV(1'd%d)," % np.random.randint(2))
+
+	waused = np.random.randint(4)
+
+	print("      .WA6USED(1'd%d)," % (1 if waused > 0 else 0))
+	print("      .WA7USED(1'd%d)," % (1 if waused > 1 else 0))
+	print("      .WA8USED(1'd%d)," % (1 if waused > 2 else 0))
+	print("      .CLKINV(2'd%d)," % np.random.randint(4))
+	print("      .SRINV(2'd%d)" % np.random.randint(4))
+	print('   ) slice%d (' % i)
+	for j in range(1, 7):
+		print("      .A%d(%s)," % (j, random_data(8)))
+	print("      .I(%s)," % random_data(8))
+	print("      .X(%s)," % random_data(8))
+	print("      .CLK({clk[%d], clk[%d]})," % clk[0:2])
+	print("      .WCLK(clk[%d])," % wclk)
+	print("      .SR({%s, %s})," % sr)
+	print("      .CE({%s, %s, %s, %s})," % ce[0:4])
+	print("      .WE(ce[%d])," % we)
+	print("      .O(d%d[7:0])," % i)
+	print("      .Q(d%d[15:8])," % i)
+	print("      .Q2(d%d[23:16])," % i)
+	print("      .MUX(d%d[31:24])" % i)
+	print('   );')
+	print()
+	D.clear()
+	for j in range(8):
+		D.append("d%d[%d]" % (i, j))
+		D.append("d%d[%d]" % (i, 24 + j))
+		if fftypes[2 * j] != "NONE":
+			D.append("d%d[%d]" % (i, 8 + j))
+		if fftypes[2 * j + 1] != "NONE":
+			D.append("d%d[%d]" % (i, 16 + j))
+print("    assign q = d%d;" % (N-1))
+print("endmodule")
\ No newline at end of file
diff --git a/spec/slice_memory.v b/spec/slice_memory.v
new file mode 100644
index 0000000..efe2fe7
--- /dev/null
+++ b/spec/slice_memory.v
@@ -0,0 +1,384 @@
+// 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.
+
+module ultra_slice_memory #(
+	parameter [1023:0] LOC = "",
+	parameter [63:0]   ALUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   BLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   CLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   DLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   ELUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   FLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   GLUT_INIT = 64'h00C0FFEE,
+	parameter [63:0]   HLUT_INIT = 64'h00C0FFEE,
+
+	parameter [1023:0] A_MODE = "LOGIC",
+	parameter [1023:0] B_MODE = "LOGIC",
+	parameter [1023:0] C_MODE = "LOGIC",
+	parameter [1023:0] D_MODE = "LOGIC",
+	parameter [1023:0] E_MODE = "LOGIC",
+	parameter [1023:0] F_MODE = "LOGIC",
+	parameter [1023:0] G_MODE = "LOGIC",
+	parameter [1023:0] H_MODE = "LOGIC",
+
+
+	parameter [1023:0] AFF_TYPE = "NONE",
+	parameter [1023:0] AFF2_TYPE = "NONE",
+	parameter [1023:0] BFF_TYPE = "NONE",
+	parameter [1023:0] BFF2_TYPE = "NONE",
+	parameter [1023:0] CFF_TYPE = "NONE",
+	parameter [1023:0] CFF2_TYPE = "NONE",
+	parameter [1023:0] DFF_TYPE = "NONE",
+	parameter [1023:0] DFF2_TYPE = "NONE",
+	parameter [1023:0] EFF_TYPE = "NONE",
+	parameter [1023:0] EFF2_TYPE = "NONE",
+	parameter [1023:0] FFF_TYPE = "NONE",
+	parameter [1023:0] FFF2_TYPE = "NONE",
+	parameter [1023:0] GFF_TYPE = "NONE",
+	parameter [1023:0] GFF2_TYPE = "NONE",
+	parameter [1023:0] HFF_TYPE = "NONE",
+	parameter [1023:0] HFF2_TYPE = "NONE",
+
+	parameter [15:0]   FF_INIT = 16'h0000,
+
+	parameter [1023:0] FFMUXA1 = "BYP",
+	parameter [1023:0] FFMUXA2 = "BYP",
+	parameter [1023:0] FFMUXB1 = "BYP",
+	parameter [1023:0] FFMUXB2 = "BYP",
+	parameter [1023:0] FFMUXC1 = "BYP",
+	parameter [1023:0] FFMUXC2 = "BYP",
+	parameter [1023:0] FFMUXD1 = "BYP",
+	parameter [1023:0] FFMUXD2 = "BYP",
+	parameter [1023:0] FFMUXE1 = "BYP",
+	parameter [1023:0] FFMUXE2 = "BYP",
+	parameter [1023:0] FFMUXF1 = "BYP",
+	parameter [1023:0] FFMUXF2 = "BYP",
+	parameter [1023:0] FFMUXG1 = "BYP",
+	parameter [1023:0] FFMUXG2 = "BYP",
+	parameter [1023:0] FFMUXH1 = "BYP",
+	parameter [1023:0] FFMUXH2 = "BYP",
+
+	parameter [1023:0] OUTMUXA = "D5",
+	parameter [1023:0] OUTMUXB = "D5",
+	parameter [1023:0] OUTMUXC = "D5",
+	parameter [1023:0] OUTMUXD = "D5",
+	parameter [1023:0] OUTMUXE = "D5",
+	parameter [1023:0] OUTMUXF = "D5",
+	parameter [1023:0] OUTMUXG = "D5",
+	parameter [1023:0] OUTMUXH = "D5",
+
+	parameter [1023:0] DIMUXA = "DI",
+	parameter [1023:0] DIMUXB = "DI",
+	parameter [1023:0] DIMUXC = "DI",
+	parameter [1023:0] DIMUXD = "DI",
+	parameter [1023:0] DIMUXE = "DI",
+	parameter [1023:0] DIMUXF = "DI",
+	parameter [1023:0] DIMUXG = "DI",
+
+	parameter WA6USED = 0, WA7USED = 0, WA8USED = 0, WCLKINV = 0,
+
+	parameter [1:0]    CLKINV = 2'b00, SRINV = 2'b00
+
+) (
+	input [7:0] A1, A2, A3, A4, A5, A6, I, X,
+	input [1:0] CLK, SR,
+	input WCLK, WE,
+	input [3:0] CE,
+	output [7:0] O, Q, Q2, MUX
+);
+
+	wire [8:0] wa;
+	assign wa[5:0] = {A6[7], A5[7], A4[7], A3[7], A2[7], A1[7]};
+	generate
+		if (WA6USED) assign wa[6] = X[6];
+		if (WA7USED) assign wa[7] = X[5];
+		if (WA8USED) assign wa[8] = X[3];
+	endgenerate
+
+	wire [7:0] di0;
+	wire [7:0] mc31;
+
+	assign di0[7] = I[7];
+
+	ultra_slice_logic_dimux #(.SEL(DIMUXA)) dimuxa_i (.DI(I[0]), .SIN(mc31[1]), .OUT(di0[0]));
+	ultra_slice_logic_dimux #(.SEL(DIMUXB)) dimuxb_i (.DI(I[1]), .SIN(mc31[2]), .OUT(di0[1]));
+	ultra_slice_logic_dimux #(.SEL(DIMUXC)) dimuxc_i (.DI(I[2]), .SIN(mc31[3]), .OUT(di0[2]));
+	ultra_slice_logic_dimux #(.SEL(DIMUXD)) dimuxd_i (.DI(I[3]), .SIN(mc31[4]), .OUT(di0[3]));
+	ultra_slice_logic_dimux #(.SEL(DIMUXE)) dimuxe_i (.DI(I[4]), .SIN(mc31[5]), .OUT(di0[4]));
+	ultra_slice_logic_dimux #(.SEL(DIMUXF)) dimuxf_i (.DI(I[5]), .SIN(mc31[6]), .OUT(di0[5]));
+	ultra_slice_logic_dimux #(.SEL(DIMUXG)) dimuxg_i (.DI(I[6]), .SIN(mc31[7]), .OUT(di0[6]));
+
+	wire [7:0] out5;
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("H5LUT"), .BEL6("H6LUT"), .MODE(H_MODE), .INIT(HLUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) hlut_i (.CLK(WCLK), .CE(WE), .A({A6[7], A5[7], A4[7], A3[7], A2[7], A1[7]}), .WA(wa), .DI({X[7], di0[7]}), .DO({O[7], out5[7]}), .MC31(mc31[7]));
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("A5LUT"), .BEL6("A6LUT"), .MODE(A_MODE), .INIT(ALUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) alut_i (.CLK(WCLK), .CE(WE), .A({A6[0], A5[0], A4[0], A3[0], A2[0], A1[0]}), .WA(wa), .DI({X[0], di0[0]}), .DO({O[0], out5[0]}), .MC31(mc31[0]));
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("B5LUT"), .BEL6("B6LUT"), .MODE(B_MODE), .INIT(BLUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) blut_i (.CLK(WCLK), .CE(WE), .A({A6[1], A5[1], A4[1], A3[1], A2[1], A1[1]}), .WA(wa), .DI({X[1], di0[1]}), .DO({O[1], out5[1]}), .MC31(mc31[1]));
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("C5LUT"), .BEL6("C6LUT"), .MODE(C_MODE), .INIT(CLUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) clut_i (.CLK(WCLK), .CE(WE), .A({A6[2], A5[2], A4[2], A3[2], A2[2], A1[2]}), .WA(wa), .DI({X[2], di0[2]}), .DO({O[2], out5[2]}), .MC31(mc31[2]));
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("D5LUT"), .BEL6("D6LUT"), .MODE(D_MODE), .INIT(DLUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) dlut_i (.CLK(WCLK), .CE(WE), .A({A6[3], A5[3], A4[3], A3[3], A2[3], A1[3]}), .WA(wa), .DI({X[3], di0[3]}), .DO({O[3], out5[3]}), .MC31(mc31[3]));
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("E5LUT"), .BEL6("E6LUT"), .MODE(E_MODE), .INIT(ELUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) elut_i (.CLK(WCLK), .CE(WE), .A({A6[4], A5[4], A4[4], A3[4], A2[4], A1[4]}), .WA(wa), .DI({X[4], di0[4]}), .DO({O[4], out5[4]}), .MC31(mc31[4]));
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("F5LUT"), .BEL6("F6LUT"), .MODE(F_MODE), .INIT(FLUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) flut_i (.CLK(WCLK), .CE(WE), .A({A6[5], A5[5], A4[5], A3[5], A2[5], A1[5]}), .WA(wa), .DI({X[5], di0[5]}), .DO({O[5], out5[5]}), .MC31(mc31[5]));
+	ultra_slice_memory_lut #(.LOC(LOC), .BEL5("G5LUT"), .BEL6("G6LUT"), .MODE(G_MODE), .INIT(GLUT_INIT), .CLKINV(WCLKINV), .WA6USED(WA6USED), .WA7USED(WA7USED), .WA8USED(WA8USED)) glut_i (.CLK(WCLK), .CE(WE), .A({A6[6], A5[6], A4[6], A3[6], A2[6], A1[6]}), .WA(wa), .DI({X[6], di0[6]}), .DO({O[6], out5[6]}), .MC31(mc31[6]));
+
+	wire [7:0] f7f8;
+	assign f7f8[0] = mc31[0];
+/*
+	(* BEL="F7MUX_AB",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxab_i (.I0(O[1]), .I1(O[0]), .S(X[0]), .O(f7f8[1]));
+	(* BEL="F7MUX_CD",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxcd_i (.I0(O[3]), .I1(O[2]), .S(X[2]), .O(f7f8[3]));
+	(* BEL="F7MUX_EF",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxef_i (.I0(O[5]), .I1(O[4]), .S(X[4]), .O(f7f8[5]));
+	(* BEL="F7MUX_GH",  LOC=LOC, keep, dont_touch *) MUXF7 f7muxgh_i (.I0(O[7]), .I1(O[6]), .S(X[6]), .O(f7f8[7]));
+
+	(* BEL="F8MUX_BOT", LOC=LOC, keep, dont_touch *) MUXF8 f8muxabcd_i (.I0(f7f8[3]), .I1(f7f8[1]), .S(X[1]), .O(f7f8[2]));
+	(* BEL="F8MUX_TOP", LOC=LOC, keep, dont_touch *) MUXF8 f8muxefgh_i (.I0(f7f8[7]), .I1(f7f8[5]), .S(X[5]), .O(f7f8[6]));
+
+	(* BEL="F9MUX",     LOC=LOC, keep, dont_touch *) MUXF9 f9_i (.I0(f7f8[6]), .I1(f7f8[2]), .S(X[3]), .O(f7f8[4]));
+*/
+	assign f7f8[7:1] = O[7:1];
+	wire [15:0] ffin;
+	ultra_slice_logic_ffmux #(.SEL(FFMUXA1)) ffmuxa1_i (.XORIN(), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(), .BYP(X[0]), .OUT(ffin[0]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXA2)) ffmuxa2_i (.XORIN(), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(), .BYP(I[0]), .OUT(ffin[1]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXB1)) ffmuxb1_i (.XORIN(), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(), .BYP(X[1]), .OUT(ffin[2]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXB2)) ffmuxb2_i (.XORIN(), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(), .BYP(I[1]), .OUT(ffin[3]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXC1)) ffmuxc1_i (.XORIN(), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(), .BYP(X[2]), .OUT(ffin[4]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXC2)) ffmuxc2_i (.XORIN(), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(), .BYP(I[2]), .OUT(ffin[5]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXD1)) ffmuxd1_i (.XORIN(), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(), .BYP(X[3]), .OUT(ffin[6]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXD2)) ffmuxd2_i (.XORIN(), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(), .BYP(I[3]), .OUT(ffin[7]));
+
+	ultra_slice_logic_ffmux #(.SEL(FFMUXE1)) ffmuxe1_i (.XORIN(), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(), .BYP(X[4]), .OUT(ffin[8]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXE2)) ffmuxe2_i (.XORIN(), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(), .BYP(I[4]), .OUT(ffin[9]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXF1)) ffmuxf1_i (.XORIN(), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(), .BYP(X[5]), .OUT(ffin[10]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXF2)) ffmuxf2_i (.XORIN(), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(), .BYP(I[5]), .OUT(ffin[11]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXG1)) ffmuxg1_i (.XORIN(), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(), .BYP(X[6]), .OUT(ffin[12]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXG2)) ffmuxg2_i (.XORIN(), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(), .BYP(I[6]), .OUT(ffin[13]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXH1)) ffmuxh1_i (.XORIN(), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(), .BYP(X[7]), .OUT(ffin[14]));
+	ultra_slice_logic_ffmux #(.SEL(FFMUXH2)) ffmuxh2_i (.XORIN(), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(), .BYP(I[7]), .OUT(ffin[15]));
+
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("AFF"),  .TYPE(AFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[0])) aff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[0]), .Q(Q[0]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("AFF2"), .TYPE(AFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[1])) aff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[1]), .Q(Q2[0]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("BFF"),  .TYPE(BFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[2])) bff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[2]), .Q(Q[1]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("BFF2"), .TYPE(BFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[3])) bff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[3]), .Q(Q2[1]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("CFF"),  .TYPE(CFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[4])) cff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[4]), .Q(Q[2]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("CFF2"), .TYPE(CFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[5])) cff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[5]), .Q(Q2[2]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("DFF"),  .TYPE(DFF_TYPE),  .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[6])) dff_i  (.C(CLK[0]), .SR(SR[0]), .CE(CE[0]), .D(ffin[6]), .Q(Q[3]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("DFF2"), .TYPE(DFF2_TYPE), .CLKINV(CLKINV[0]), .SRINV(SRINV[0]), .INIT(FF_INIT[7])) dff2_i (.C(CLK[0]), .SR(SR[0]), .CE(CE[1]), .D(ffin[7]), .Q(Q2[3]));
+
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("EFF"),  .TYPE(EFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[8])) eff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[8]), .Q(Q[4]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("EFF2"), .TYPE(EFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[9])) eff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[9]), .Q(Q2[4]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("FFF"),  .TYPE(FFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[10])) fff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[10]), .Q(Q[5]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("FFF2"), .TYPE(FFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[11])) fff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[11]), .Q(Q2[5]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("GFF"),  .TYPE(GFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[12])) gff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[12]), .Q(Q[6]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("GFF2"), .TYPE(GFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[13])) gff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[13]), .Q(Q2[6]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("HFF"),  .TYPE(HFF_TYPE),  .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[14])) hff_i  (.C(CLK[1]), .SR(SR[1]), .CE(CE[2]), .D(ffin[14]), .Q(Q[7]));
+	ultra_slice_logic_ffx #(.LOC(LOC), .BEL("HFF2"), .TYPE(HFF2_TYPE), .CLKINV(CLKINV[1]), .SRINV(SRINV[1]), .INIT(FF_INIT[15])) hff2_i (.C(CLK[1]), .SR(SR[1]), .CE(CE[3]), .D(ffin[15]), .Q(Q2[7]));
+
+	ultra_slice_logic_outmux #(.SEL(OUTMUXA)) outmuxa_i (.XORIN(), .F7F8(f7f8[0]), .D6(O[0]), .D5(out5[0]), .CY(), .OUT(MUX[0]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXB)) outmuxb_i (.XORIN(), .F7F8(f7f8[1]), .D6(O[1]), .D5(out5[1]), .CY(), .OUT(MUX[1]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXC)) outmuxc_i (.XORIN(), .F7F8(f7f8[2]), .D6(O[2]), .D5(out5[2]), .CY(), .OUT(MUX[2]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXD)) outmuxd_i (.XORIN(), .F7F8(f7f8[3]), .D6(O[3]), .D5(out5[3]), .CY(), .OUT(MUX[3]));
+
+	ultra_slice_logic_outmux #(.SEL(OUTMUXE)) outmuxe_i (.XORIN(), .F7F8(f7f8[4]), .D6(O[4]), .D5(out5[4]), .CY(), .OUT(MUX[4]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXF)) outmuxf_i (.XORIN(), .F7F8(f7f8[5]), .D6(O[5]), .D5(out5[5]), .CY(), .OUT(MUX[5]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXG)) outmuxg_i (.XORIN(), .F7F8(f7f8[6]), .D6(O[6]), .D5(out5[6]), .CY(), .OUT(MUX[6]));
+	ultra_slice_logic_outmux #(.SEL(OUTMUXH)) outmuxh_i (.XORIN(), .F7F8(f7f8[7]), .D6(O[7]), .D5(out5[7]), .CY(), .OUT(MUX[7]));
+
+
+endmodule
+
+module ultra_slice_logic_ffmux #(
+	parameter [1023:0] SEL = "BYP"
+) (
+	input XORIN, F7F8, D6, D5, CY, BYP,
+	output OUT
+);
+	generate
+		case(SEL)
+			"XORIN": assign OUT = XORIN;
+			"F7F8":  assign OUT = F7F8;
+			"D6":    assign OUT = D6;
+			"D5":    assign OUT = D5;
+			"CY":    assign OUT = CY;
+			"BYP":   assign OUT = BYP;
+		endcase		
+	endgenerate
+endmodule
+
+module ultra_slice_logic_ffx #(
+	parameter [1023:0] LOC = "",
+	parameter [1023:0] BEL = "",
+	parameter [1023:0] TYPE = "",
+	parameter CLKINV = 1'b0,
+	parameter SRINV = 1'b0,
+	parameter INIT = 1'b0
+) (
+	input C, CE, SR, D,
+	output Q
+);
+	generate
+		case (TYPE)
+			"FDPE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDPE #(.IS_C_INVERTED(CLKINV), .IS_PRE_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .PRE(SR), .D(D), .Q(Q));
+			"FDCE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDCE #(.IS_C_INVERTED(CLKINV), .IS_CLR_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .CLR(SR), .D(D), .Q(Q));
+			"FDSE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDSE #(.IS_C_INVERTED(CLKINV), .IS_S_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .S(SR), .D(D), .Q(Q));
+			"FDRE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) FDRE #(.IS_C_INVERTED(CLKINV), .IS_R_INVERTED(SRINV), .INIT(INIT)) ff_i (.C(C), .CE(CE), .R(SR), .D(D), .Q(Q));
+			
+			"LDPE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) LDPE #(.IS_G_INVERTED(CLKINV), .IS_PRE_INVERTED(SRINV), .INIT(INIT)) ff_i (.G(C), .GE(CE), .PRE(SR), .D(D), .Q(Q));
+			"LDCE": (* LOC=LOC, BEL=BEL, keep, dont_touch *) LDCE #(.IS_G_INVERTED(CLKINV), .IS_CLR_INVERTED(SRINV), .INIT(INIT)) ff_i (.G(C), .GE(CE), .CLR(SR), .D(D), .Q(Q));
+			"NONE": assign Q = INIT;
+		endcase
+	endgenerate
+endmodule
+
+module ultra_slice_logic_outmux #(
+	parameter SEL = "D5"
+) (
+	input XORIN, F7F8, D6, D5, CY,
+	output OUT
+);
+	generate
+		case(SEL)
+			"XORIN": assign OUT = XORIN;
+			"F7F8":  assign OUT = F7F8;
+			"D6":    assign OUT = D6;
+			"D5":    assign OUT = D5;
+			"CY":    assign OUT = CY;
+		endcase		
+	endgenerate
+endmodule
+
+module ultra_slice_logic_dimux #(
+	parameter [1023:0] SEL = "DI"
+) (
+	input DI, SIN,
+	output OUT
+);
+	generate
+		case(SEL)
+			"DI": assign OUT = DI;
+			"SIN":  assign OUT = SIN;
+		endcase		
+	endgenerate
+endmodule
+
+module ultra_slice_memory_lut #(
+	parameter [1023:0] LOC = "",
+	parameter [1023:0] BEL5 = "",
+	parameter [1023:0] BEL6 = "",
+	parameter [1023:0] MODE = "LOGIC",
+	parameter [63:0] INIT = 64'h0,
+	parameter CLKINV = 0, WA6USED = 0, WA7USED = 0, WA8USED = 0
+) (
+	input CLK, CE,
+	input [5:0] A,
+	input [8:0] WA,
+	input [1:0] DI,
+	output [1:0] DO,
+	output MC31
+);
+	generate
+		if (MODE == "LOGIC") begin
+			(* BEL=BEL6, LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(INIT[63:32])) lut6 (.I0(A[0]), .I1(A[1]), .I2(A[2]), .I3(A[3]), .I4(A[4]), .O(DO[1]));
+			(* BEL=BEL5, LOC=LOC, keep, dont_touch *) LUT5 #(.INIT(INIT[31:0])) lut5 (.I0(A[0]), .I1(A[1]), .I2(A[2]), .I3(A[3]), .I4(A[4]), .O(DO[0]));
+			assign MC31 = DO[1];
+		end else if (MODE == "SRL16") begin
+			(* BEL=BEL6, LOC=LOC, keep, dont_touch *) SRL16E #(.INIT(INIT[63:32]), .IS_CLK_INVERTED(CLKINV)) srl6 (.A0(A[0]), .A1(A[1]), .A2(A[2]), .A3(A[3]), .D(DI[1]), .CLK(CLK), .CE(CE), .Q(DO[1]));
+			(* BEL=BEL5, LOC=LOC, keep, dont_touch *) SRL16E #(.INIT(INIT[31:0]), .IS_CLK_INVERTED(CLKINV)) srl5 (.A0(A[0]), .A1(A[1]), .A2(A[2]), .A3(A[3]), .D(DI[0]), .CLK(CLK), .CE(CE), .Q(DO[0]));
+			assign MC31 = DO[1];
+		end else if (MODE == "SRL32") begin
+			(* BEL=BEL6, keep, dont_touch *) SRLC32E #(.INIT(INIT[31:0]), .IS_CLK_INVERTED(CLKINV)) srl6(.A(A[4:0]), .D(DI[0]), .CLK(CLK), .CE(CE), .Q(DO[1]), .Q31(MC31));
+			assign DO[0] = DO[1];
+		end else if (MODE == "RAMD64") begin
+			if (WA6USED && WA7USED) begin
+				(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMD64E #(.INIT(INIT), .IS_CLK_INVERTED(CLKINV)) ram_i (
+					.RADR0(A[0]), .RADR1(A[1]), .RADR2(A[2]), .RADR3(A[3]), .RADR4(A[4]), .RADR5(A[5]),
+					.WADR0(WA[0]), .WADR1(WA[1]), .WADR2(WA[2]), .WADR3(WA[3]), .WADR4(WA[4]), .WADR5(WA[5]), .WADR6(WA[6]), .WADR7(WA[7]),
+					.CLK(CLK), .WE(CE),
+					.I(DI[0]), .O(DO[1])
+				);
+			
+			end else if (WA6USED) begin
+				(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMD64E #(.INIT(INIT), .IS_CLK_INVERTED(CLKINV)) ram_i (
+					.RADR0(A[0]), .RADR1(A[1]), .RADR2(A[2]), .RADR3(A[3]), .RADR4(A[4]), .RADR5(A[5]),
+					.WADR0(WA[0]), .WADR1(WA[1]), .WADR2(WA[2]), .WADR3(WA[3]), .WADR4(WA[4]), .WADR5(WA[5]), .WADR6(WA[6]),
+					.CLK(CLK), .WE(CE),
+					.I(DI[0]), .O(DO[1])
+				);
+			end else begin
+				(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMD64E #(.INIT(INIT), .IS_CLK_INVERTED(CLKINV)) ram_i (
+					.RADR0(A[0]), .RADR1(A[1]), .RADR2(A[2]), .RADR3(A[3]), .RADR4(A[4]), .RADR5(A[5]),
+					.WADR0(WA[0]), .WADR1(WA[1]), .WADR2(WA[2]), .WADR3(WA[3]), .WADR4(WA[4]), .WADR5(WA[5]),
+					.CLK(CLK), .WE(CE),
+					.I(DI[0]), .O(DO[1])
+				);
+			end
+			assign DO[0] = DO[1];
+			assign MC31 = DO[1];
+		end else if (MODE == "RAMS64") begin
+			if (WA6USED && WA7USED && WA8USED) begin
+				(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMS64E1 #(.INIT(INIT), .IS_CLK_INVERTED(CLKINV)) ram_i (
+					.ADR0(WA[0]), .ADR1(WA[1]), .ADR2(WA[2]), .ADR3(WA[3]), .ADR4(WA[4]), .ADR5(WA[5]),
+					.WADR6(WA[6]), .WADR7(WA[7]), .WADR8(WA[8]), 
+					.CLK(CLK), .WE(CE),
+					.I(DI[0]), .O(DO[1])
+				);
+			end else if (WA6USED && WA7USED) begin
+				(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMS64E1 #(.INIT(INIT), .IS_CLK_INVERTED(CLKINV)) ram_i (
+					.ADR0(WA[0]), .ADR1(WA[1]), .ADR2(WA[2]), .ADR3(WA[3]), .ADR4(WA[4]), .ADR5(WA[5]),
+					.WADR6(WA[6]), .WADR7(WA[7]),
+					.CLK(CLK), .WE(CE),
+					.I(DI[0]), .O(DO[1])
+				);
+			end else if (WA6USED) begin
+				(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMS64E1 #(.INIT(INIT), .IS_CLK_INVERTED(CLKINV)) ram_i (
+					.ADR0(WA[0]), .ADR1(WA[1]), .ADR2(WA[2]), .ADR3(WA[3]), .ADR4(WA[4]), .ADR5(WA[5]),
+					.WADR6(WA[6]),
+					.CLK(CLK), .WE(CE),
+					.I(DI[0]), .O(DO[1])
+				);
+			end else begin
+				(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMS64E1 #(.INIT(INIT), .IS_CLK_INVERTED(CLKINV)) ram_i (
+					.ADR0(WA[0]), .ADR1(WA[1]), .ADR2(WA[2]), .ADR3(WA[3]), .ADR4(WA[4]), .ADR5(WA[5]),
+					.CLK(CLK), .WE(CE),
+					.I(DI[0]), .O(DO[1])
+				);
+			end
+			assign DO[0] = DO[1];
+			assign MC31 = DO[1];
+		end else if (MODE == "RAMS32") begin
+			(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMS32 #(.INIT(INIT[63:32]), .IS_CLK_INVERTED(CLKINV)) ram1_i (
+				.ADR0(WA[0]), .ADR1(WA[1]), .ADR2(WA[2]), .ADR3(WA[3]), .ADR4(WA[4]),
+				.CLK(CLK), .WE(CE),
+				.I(DI[1]), .O(DO[1])
+			);
+			(* BEL=BEL5, LOC=LOC, keep, dont_touch *) RAMS32 #(.INIT(INIT[31:0]), .IS_CLK_INVERTED(CLKINV)) ram0_i (
+				.ADR0(WA[0]), .ADR1(WA[1]), .ADR2(WA[2]), .ADR3(WA[3]), .ADR4(WA[4]),
+				.CLK(CLK), .WE(CE),
+				.I(DI[0]), .O(DO[0])
+			);
+			assign MC31 = DO[1];
+		end else if (MODE == "RAMD32") begin
+			(* BEL=BEL6, LOC=LOC, keep, dont_touch *) RAMD32 #(.INIT(INIT[63:32]), .IS_CLK_INVERTED(CLKINV)) ram1_i (
+				.WADR0(WA[0]), .WADR1(WA[1]), .WADR2(WA[2]), .WADR3(WA[3]), .WADR4(WA[4]),
+				.RADR0(A[0]), .RADR1(A[1]), .RADR2(A[2]), .RADR3(A[3]), .RADR4(A[4]), 
+				.CLK(CLK), .WE(CE),
+				.I(DI[1]), .O(DO[1])
+			);
+			(* BEL=BEL5, LOC=LOC, keep, dont_touch *) RAMD32 #(.INIT(INIT[31:0]), .IS_CLK_INVERTED(CLKINV)) ram0_i (
+				.WADR0(WA[0]), .WADR1(WA[1]), .WADR2(WA[2]), .WADR3(WA[3]), .WADR4(WA[4]),
+				.RADR0(A[0]), .RADR1(A[1]), .RADR2(A[2]), .RADR3(A[3]), .RADR4(A[4]), 
+				.CLK(CLK), .WE(CE),
+				.I(DI[0]), .O(DO[0])
+			);
+			assign MC31 = DO[1];
+		end else begin
+			$error("unsupported mode");
+		end
+	endgenerate
+endmodule
\ No newline at end of file
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 0000000..c18aed5
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1,5 @@
+dump_bitstream
+correlate
+explain
+stripdb
+assemble
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644
index 0000000..de2ccde
--- /dev/null
+++ b/tools/Makefile
@@ -0,0 +1,20 @@
+all: dump_bitstream correlate explain stripdb assemble
+
+CXXFLAGS := -std=c++17 -O3 -g
+LINK.o = $(LINK.cc)
+
+%.o: common.h
+
+dump_bitstream: dump_bitstream.o common.o
+
+correlate: correlate.o
+
+explain: explain.o common.o
+
+stripdb: stripdb.o
+
+assemble: assemble.o common.o
+
+clean:
+	rm *.o dump_bitstream correlate stripdb assemble
+.PHONY: clean
\ No newline at end of file
diff --git a/tools/assemble.cpp b/tools/assemble.cpp
new file mode 100644
index 0000000..f86e494
--- /dev/null
+++ b/tools/assemble.cpp
@@ -0,0 +1,347 @@
+// 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.
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <unordered_set>
+#include <unordered_map>
+#include <map>
+#include <set>
+#include <cassert>
+#include <stdexcept>
+#include <stdarg.h>
+#include "common.h"
+
+enum BitstreamRegister : uint16_t {
+	#define X(a, b) a = b,
+	#include "registers.inc"
+	#undef X
+};
+
+
+ChipData chip;
+
+std::unordered_set<uint32_t> roi_frames;
+std::map<uint32_t, std::vector<bool>> frames;
+std::map<uint32_t, uint32_t> next_frame;
+std::string db_root;
+
+
+void set_tile_bit(const TileInstance &tile, int bit) {
+	int pos = 0;
+	for (auto &bm : tile.bits) {
+		if (bit >= pos && bit < (pos + bm.size)) {
+			frames.at(bm.frame_offset).at(bm.bit_offset + (bit - pos)) = true;
+			return;
+		}
+		pos += bm.size;
+	}
+	throw std::runtime_error("bad bit " + tile.name + "." + std::to_string(bit));
+}
+
+
+void set_feature(const std::string &tile, const std::string &feature) {
+	auto &db = chip.load_tile_database(tile);
+	if (!db.features.count(feature)) {
+		throw std::runtime_error("unknown feature " + feature + " in tile " + tile);
+	}
+	const auto &fbits = db.features.at(feature);
+	auto &t = chip.tiles[tile];
+	for (auto bit : fbits)
+		set_tile_bit(t, bit);
+}
+
+void read_quasi_fasm(const std::string &filename) {
+	LineReader rd(filename);
+	std::vector<std::string> split;
+	for (auto &line : rd) {
+		split_str(line, split, ".", true, 1);
+		if (split.size() < 2)
+			continue;
+		set_feature(split.at(0), split.at(1));
+	}
+}
+
+// Add ECCs to frames
+void add_frame_ecc() {
+
+	auto get_ecc_value = [](int word, int bit) {
+		int nib = bit / 4;
+		int nibbit = bit % 4;
+		// ECC offset is expanded to 1 bit per nibble,
+		// and then shifted based on the bit index in nibble
+		// e.g. word 3, bit 9
+		// offset: 0b10100110010 - concatenate (3 + (255 - 92)) [frame offset] and 9/4 [nibble offset]
+		// becomes: 0x10100110010
+		// shifted by bit in nibble (9%4): 0x20200220020 
+		uint32_t offset =  (word + (255 - 92)) << 3  | nib;
+		uint64_t exp_offset = 0;
+		// Odd parity
+		offset ^= (1 << 11);
+		for (int i = 0; i < 11; i++)
+			if (offset & (1 << i)) offset ^= (1 << 11);
+		// Expansion
+		for (int i = 0; i < 12; i++)
+			if (offset & (1 << i)) exp_offset |= (1ULL << (4 * i));
+		return exp_offset << nibbit;
+	};
+
+	for (auto &fr : frames) {
+		auto &bits = fr.second;
+		uint64_t ecc = 0;
+		for (int word = 0; word < 93; word++) {
+			for (int i = 0; i < 32; i++) {
+				if (!bits.at(word * 32 + i))
+					continue;
+				if ((word == 45) || ((word == 46 && (i < 16)))) {
+					bits.at(word * 32 + i) = false;
+					continue;
+				}
+				ecc ^= get_ecc_value(word, i);
+			}
+		}
+		for (int i = 0; i < 48; i++)
+			if (ecc & (1ULL << i))
+				bits.at(45*32 + i) = true;
+	}
+}
+
+
+struct ByteStreamWriter {
+	std::vector<uint8_t> data;
+
+	uint32_t curr_crc = 0;
+	uint32_t curr_addr = 0;
+
+	void write_byte(uint8_t byte) {
+		data.push_back(byte);
+	}
+	void write_bytes(const std::vector<uint8_t> &bytes) {
+		data.insert(data.end(), bytes.begin(), bytes.end());
+	}
+	void write_string(const std::string &str) {
+		int len = int(str.length()) + 1;
+		data.push_back((len >> 8) & 0xFF);
+		data.push_back(len & 0xFF);
+		for (char c : str) data.push_back(uint8_t(c));
+		data.push_back(0x00);
+	}
+	void write_u32(uint32_t word, bool update_crc = false) {
+		data.push_back((word >> 24) & 0xFF);
+		data.push_back((word >> 16) & 0xFF);
+		data.push_back((word >>  8) & 0xFF);
+		data.push_back((word >>  0) & 0xFF);
+		if (update_crc)
+			curr_crc = icap_crc(curr_addr, word, curr_crc);
+	}
+	void write_short_packet(BitstreamOp op, BitstreamRegister reg = BitstreamRegister(0x00), const std::vector<uint32_t> &payload = {}) {
+		curr_addr = uint32_t(reg);
+		write_u32((0b001UL << 29) | ((uint32_t(op) & 0x03) << 27) | ((uint32_t(reg) & 0x3FFF) << 13) | (uint32_t(payload.size()) & 0x3FFF));
+		for (uint32_t x : payload)
+			write_u32(x, true);
+	}
+	void write_long_packet(const std::vector<uint32_t> &payload) {
+		write_u32((0b010UL << 29) | (uint32_t(OP_READ) << 27) | (uint32_t(payload.size()) & 0x3FFFFFF));
+		for (uint32_t x : payload)
+			write_u32(x, true);
+	}
+	void write_crc() {
+		write_short_packet(OP_WRITE, CRC, {curr_crc});
+		curr_crc = 0;
+	}
+};
+
+void write_bitstream(std::ofstream &f) {
+#if 0
+	// FIXME - write an actual binary bitstream
+	for (auto &frame : frames) {
+		for (size_t i = 0; i < frame.second.size(); i++) {
+			if (frame.second.at(i))
+				f << stringf("F%08xW%03dB%02d", frame.first, int(i / 32), int(i % 32)) << std::endl;
+		}
+	}
+#else
+
+	add_frame_ecc();
+
+	ByteStreamWriter bsw;
+	// Header
+	bsw.write_bytes({0x00, 0x09, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x00, 0x00, 0x01, 0x61});
+	bsw.write_string("top;UserID=0XFFFFFFFF;Version=2019.1");
+	bsw.write_byte(0x62);
+	bsw.write_string("xczu7ev-ffvc1156-2-e");
+	bsw.write_byte(0x63);
+	bsw.write_string("2019/09/08");
+	bsw.write_byte(0x64);
+	bsw.write_string("00:00:00");
+	bsw.write_bytes({0x65, 0x01, 0x36, 0x6F, 0xB0});
+	for (int i = 0; i < 64; i++)
+		bsw.write_byte(0xFF);
+	bsw.write_bytes({0x00, 0x00, 0x00, 0xBB, 0x11, 0x22, 0x00, 0x44});
+	for (int i = 0; i < 8; i++)
+		bsw.write_byte(0xFF);
+	// Preamble
+	bsw.write_u32(0xAA995566);
+	// Initial commands
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, TIMER, {0x00000000});
+	bsw.write_short_packet(OP_WRITE, WBSTAR, {0x00000000});
+	bsw.write_short_packet(OP_WRITE, CMD, {0x00000000});
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, CMD, {0x00000007});
+	bsw.curr_crc = 0;
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, FAR, {0x00000000});
+	bsw.write_short_packet(OP_WRITE, BitstreamRegister(0x13), {0x00000000});
+	bsw.write_short_packet(OP_WRITE, COR0, {0x38003fe5});
+	bsw.write_short_packet(OP_WRITE, COR1, {0x00400000});
+	bsw.write_short_packet(OP_WRITE, IDCODE, {0x04a5a093});
+	bsw.write_short_packet(OP_WRITE, CMD, {0x00000009});
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, MASK, {0x00000001});
+	bsw.write_short_packet(OP_WRITE, CTL0, {0x00000101});
+	bsw.write_short_packet(OP_WRITE, MASK, {0x00200000});
+	bsw.write_short_packet(OP_WRITE, CTL1, {0x00200000});
+	for (int i = 0; i < 8; i++)
+		bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, CMD, {0x00000001});
+	bsw.write_short_packet(OP_NOP);
+	// Frame data
+	std::vector<uint32_t> fdata(93, 0x00000000);
+	bsw.write_short_packet(OP_WRITE, FAR, {frames.begin()->first});
+	for (auto &fr : frames) {
+		for (int i = 0; i < 93; i++) {
+			fdata[i] = 0x00000000;
+			for (int j = 0; j < 32; j++)
+				if (fr.second.at(i * 32 + j))
+					fdata[i] |= (1 << j);
+		}
+		bsw.write_short_packet(OP_WRITE, FDRI, fdata);
+		bsw.write_short_packet(OP_WRITE, FAR, {fr.first});
+		bsw.write_crc();
+		if (!next_frame.count(fr.first) || ((next_frame.at(fr.first) ^ fr.first) & 0xFFFC0000)) {
+			// Duplicate last frame in a row, but empty??
+			for (int i = 0; i < 93; i++)
+				fdata[i] = 0;
+			bsw.write_short_packet(OP_WRITE, FDRI, fdata);				
+			bsw.write_short_packet(OP_WRITE, FAR, {fr.first});
+			bsw.write_crc();
+			if (next_frame.count(fr.first)) {
+				// CMD 1 (WCFG)
+				bsw.write_short_packet(OP_WRITE, CMD, {0x00000001});
+				bsw.write_short_packet(OP_NOP);
+				// Next frame address
+				bsw.write_short_packet(OP_WRITE, FAR, {next_frame.at(fr.first)});
+			}
+		}
+
+		//bsw.write_long_packet(fdata);
+	}
+	// End of bitstream
+	bsw.write_short_packet(OP_WRITE, CMD, {0x00000000});
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, MASK, {0x00200000});
+	bsw.write_short_packet(OP_WRITE, CTL1, {0x00000000});
+	bsw.write_crc();
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, CMD, {0x0000000a});
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, CMD, {0x00000003});
+	for (int i = 0; i < 20; i++)
+		bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, CMD, {0x00000005});
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, FAR, {0x07FC0000});
+	bsw.write_short_packet(OP_WRITE, MASK, {0x00000101});
+	bsw.write_short_packet(OP_WRITE, CTL0, {0x00000101});
+	bsw.write_crc();
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_NOP);
+	bsw.write_short_packet(OP_WRITE, CMD, {0x0000000d});
+	for (int i = 0; i < 400; i++)
+		bsw.write_short_packet(OP_NOP);
+	f.write(reinterpret_cast<const char *>(&(bsw.data[0])), bsw.data.size());
+#endif
+}
+
+void parse_harness(const std::string &filename) {
+	LineReader rd(filename);
+	for (auto &line : rd) {
+		assert(line.at(0) == 'F');
+		const char *curr = line.c_str() + 1;
+		char *next = nullptr;
+		uint32_t frame = std::strtoul(curr, &next, 16);
+		if (roi_frames.count(frame))
+			continue;
+		assert(*next == 'W');
+		curr = next + 1;
+		int word = std::strtol(curr, &next, 10);
+		curr = next + 1;
+		assert(*next == 'B');
+		int bit = std::strtol(curr, &next, 10);
+		frames[frame].at(word * 32 + bit) = true;
+	}
+}
+
+void parse_frames(const std::string &filename, std::unordered_set<uint32_t> &dest_set) {
+	LineReader rd(filename);
+	for (auto &line : rd) {
+		const char *start = line.c_str();
+		char *end = nullptr;
+		uint32_t addr = std::strtol(start, &end, 16);
+		if ((end == nullptr) || (end == start))
+			continue;
+		dest_set.insert(addr);
+		frames[addr].clear();
+		frames[addr].resize(93*32, false); 
+	}
+}
+
+void setup_base_frames() {
+	uint32_t last_frame = 0xFFFFFFFF;
+	for (auto addr : chip.all_frames) {
+		if (addr == 0x07FC0000)
+			continue;
+		frames[addr].clear();
+		frames[addr].resize(93*32, false); 
+		if (last_frame != 0xFFFFFFFF)
+			next_frame[last_frame] = addr;
+		last_frame = addr;
+	}
+}
+
+int main(int argc, char *argv[]) {
+	if (argc < 6) {
+		std::cerr << "Usage: ./assemble dbdir roi_frames.txt harness.txt input.fasm out.bit" << std::endl;
+		return 2;
+	}
+	db_root = argv[1];
+	chip.open(db_root);
+	setup_base_frames();
+	parse_frames(argv[2], roi_frames);
+	parse_harness(argv[3]);
+	read_quasi_fasm(argv[4]);
+	std::ofstream obf(argv[5], std::ios::binary);
+	if (!obf) {
+		std::cerr << "failed to open " << argv[5] << std::endl;
+		return 1;
+	}
+	write_bitstream(obf);
+}
\ No newline at end of file
diff --git a/tools/bits.py b/tools/bits.py
new file mode 100644
index 0000000..e493cc4
--- /dev/null
+++ b/tools/bits.py
@@ -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.
+
+import re
+import sys
+import json
+# Usage: frames.txt tiles.txt tilegrid.json
+
+frame_line_re = re.compile(r'0x([0-9A-Fa-f]+).*')
+
+frame_rc_height = {}
+
+with open(sys.argv[1], 'r') as f:
+	for line in f:
+		m = frame_line_re.match(line)
+		if not m:
+			continue
+		frame = int(m.group(1), 16)
+		bus = (frame >> 24) & 0x7
+		half = (frame >> 23) & 0x1
+		row = (frame >> 18) & 0x1F
+		col = (frame >> 8) & 0x3FF
+		minor = frame & 0xFF
+		if bus != 0 or half != 0:
+			continue
+		if (row, col) not in frame_rc_height:
+			frame_rc_height[(row, col, frame & ~0xFF)] = minor + 1
+		else:
+			frame_rc_height[(row, col, frame & ~0xFF)] = max(frame_rc_height[(row, col)], minor + 1)
+
+tiles_to_xy = {}
+with open(sys.argv[2], 'r') as tilef:
+	for line in tilef:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		x = int(sl[0])
+		y = int(sl[1])
+		name = sl[2]
+		tiles_to_xy[name] = (x, y)
+
+with open(sys.argv[3]) as tb_f:
+	tbj = json.load(tb_f)
+
+frames_to_tiles = {} 
+for tilename, tiledata in tbj.items():
+	tile_offset = 0
+	for chunk in tiledata:
+		frame, start, size = chunk
+		if frame not in frames_to_tiles:
+			frames_to_tiles[frame] = []
+		name = tilename.split(":")[0]
+		frames_to_tiles[frame].append((start, tiles_to_xy[name][1], tiles_to_xy[name][0], name))
+		tile_offset += size
+
+for frame, tiles in frames_to_tiles.items():
+	tiles.sort()
+
+for rc, height in sorted(frame_rc_height.items()):
+	row, col, frame = rc
+	line = "%08x %6d %6d %6d" % (frame, row, col, height)
+	print(line)
+	frame = (row << 18) | (col << 8)
+	last_start = 0;
+	if frame in frames_to_tiles and len(frames_to_tiles[frame]) > 0:
+		for tile in frames_to_tiles[frame]:
+			start, ty, tx, tname = tile
+			print("                            %6d (%4d) %6d %6d %s" % (start, start - last_start, tx, ty, tname))
+			last_start = start
+
diff --git a/tools/bits_to_tiles.py b/tools/bits_to_tiles.py
new file mode 100644
index 0000000..42a5928
--- /dev/null
+++ b/tools/bits_to_tiles.py
@@ -0,0 +1,61 @@
+# 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
+import sys
+import json
+
+line_re = re.compile(r'F(0x[0-9A-Fa-f]+)W(\d+)B(\d+)')
+frames_to_tiles = {} # (start, size, tile, tile offset)
+ 
+with open(sys.argv[1]) as tb_f:
+	tbj = json.load(tb_f)
+
+for tilename, tiledata in tbj.items():
+	tile_offset = 0
+	for chunk in tiledata:
+		frame, start, size = chunk
+		if frame not in frames_to_tiles:
+			frames_to_tiles[frame] = []
+		frames_to_tiles[frame].append((start, size, tilename, tile_offset))
+		tile_offset += size
+
+tile_bits = {}
+
+# Always write these tiles to avoid correlation issues
+# (distinguishing between all/no bits)
+for tilename, tiledata in tbj.items():
+	if "INT_INTF_L_IO" in tilename:
+		tile_bits[tilename] = set()
+
+with open(sys.argv[2]) as df:
+	for line in df:
+		m = line_re.match(line)
+		if not m:
+			continue
+		frame = int(m[1], 16)
+		if frame not in frames_to_tiles:
+			continue
+		framebit = int(m[2]) * 32 + int(m[3])
+		for fb in frames_to_tiles[frame]:
+			start, size, tile, toff = fb
+			if framebit >= start and framebit < (start + size):
+				if tile not in tile_bits:
+					tile_bits[tile] = set()
+				tile_bits[tile].add(toff + (framebit - start))
+
+for tile, bits in sorted(tile_bits.items()):
+	print(".tile %s" % tile)
+	for b in sorted(bits):
+		print(b)
diff --git a/tools/columns.py b/tools/columns.py
new file mode 100644
index 0000000..826cc04
--- /dev/null
+++ b/tools/columns.py
@@ -0,0 +1,80 @@
+# 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
+import sys
+import json
+# Usage: frames.txt tiles.txt tilegrid.json
+
+frame_line_re = re.compile(r'0x([0-9A-Fa-f]+).*')
+
+frame_rc_height = {}
+
+with open(sys.argv[1], 'r') as f:
+	for line in f:
+		m = frame_line_re.match(line)
+		if not m:
+			continue
+		frame = int(m.group(1), 16)
+		bus = (frame >> 24) & 0x7
+		half = (frame >> 23) & 0x1
+		row = (frame >> 18) & 0x1F
+		col = (frame >> 8) & 0x3FF
+		minor = frame & 0xFF
+		if bus != 0 or half != 0:
+			continue
+		if (row, col) not in frame_rc_height:
+			frame_rc_height[(row, col)] = minor + 1
+		else:
+			frame_rc_height[(row, col)] = max(frame_rc_height[(row, col)], minor + 1)
+
+tiles_to_xy = {}
+with open(sys.argv[2], 'r') as tilef:
+	for line in tilef:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		x = int(sl[0])
+		y = int(sl[1])
+		name = sl[2]
+		tiles_to_xy[name] = (x, y)
+
+with open(sys.argv[3]) as tb_f:
+	tbj = json.load(tb_f)
+
+frames_to_tiles = {} 
+for tilename, tiledata in tbj.items():
+	tile_offset = 0
+	for chunk in tiledata:
+		frame, start, size = chunk
+		if frame not in frames_to_tiles:
+			frames_to_tiles[frame] = []
+		name = tilename.split(":")[0]
+		frames_to_tiles[frame].append((tiles_to_xy[name][1], tiles_to_xy[name][0], name))
+		tile_offset += size
+
+for frame, tiles in frames_to_tiles.items():
+	tiles.sort()
+
+print("row    col    height tx     ty     tname")
+
+for rc, height in sorted(frame_rc_height.items()):
+	row, col = rc
+	line = "%6d %6d %6d" % (row, col, height)
+	frame = (row << 18) | (col << 8)
+	if frame in frames_to_tiles and len(frames_to_tiles[frame]) > 0:
+		ty, tx, tname = frames_to_tiles[frame][0]
+		line += " %6d %6d %s" % (tx, ty, tname)
+	print(line)
+
diff --git a/tools/common.cpp b/tools/common.cpp
new file mode 100644
index 0000000..01d80b0
--- /dev/null
+++ b/tools/common.cpp
@@ -0,0 +1,80 @@
+// 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.
+
+#include "common.h"
+
+
+void ChipData::open(const std::string &root) {
+	this->root = root;
+	load_frames();
+	load_tiles();
+}
+
+void ChipData::load_frames() {
+	LineReader rd(root + "/frames.txt");
+	for (const std::string &line : rd) {
+		all_frames.insert(std::strtoul(line.c_str(), nullptr, 16));
+	}
+}
+
+void ChipData::load_tiletype_database(int type) {
+	auto &tt = tiletypes.at(type);
+	if (tt.loaded_db)
+		return;
+	LineReader rd(root + "/" + tt.type + ".bits");
+	std::vector<std::string> split;
+	for (const std::string &line : rd) {
+		split_str(line, split);
+		if (split.size() < 1)
+			continue;
+		auto &feat = tt.features[split.at(0)];
+		for (size_t i = 1; i < split.size(); i++)
+			feat.push_back(std::stoi(split.at(i)));
+	}
+	tt.loaded_db = true;
+}
+
+void ChipData::load_tiles() {
+	LineReader rd(root + "/tiles.txt");
+	std::vector<std::string> split;
+	TileInstance *curr = nullptr;
+
+	for (const std::string &line : rd) {
+		split_str(line, split);
+		if (split.size() < 1)
+			continue;
+		if (split.at(0) == ".tile") {
+			curr = &(tiles[split.at(1)]);
+			curr->name = split.at(1);
+			if (!tiletype_by_name.count(split.at(2))) {
+				tiletype_by_name[split.at(2)] = int(tiletypes.size());
+				curr->type = int(tiletypes.size());
+				tiletypes.emplace_back();
+				tiletypes.back().type = split.at(2);
+			} else {
+				curr->type = tiletype_by_name.at(split.at(2));
+			}
+			curr->x = std::stoi(split.at(3));
+			curr->y = std::stoi(split.at(4));
+		} else if (split.at(0) == "frame") {
+			TileInstance::TileBitMapping tbm;
+			tbm.frame_offset = std::strtoul(split.at(1).c_str(), nullptr, 16);
+			tbm.bit_offset = std::stoi(split.at(3));
+			tbm.size = std::stoi(split.at(5));
+			curr->bits.push_back(tbm);
+		}
+	}
+}
+
+
diff --git a/tools/common.h b/tools/common.h
new file mode 100644
index 0000000..481e842
--- /dev/null
+++ b/tools/common.h
@@ -0,0 +1,211 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+#include <map>
+#include <set>
+#include <unordered_map>
+#include <stdarg.h>
+constexpr uint32_t kCrc32CastagnoliPolynomial = 0x82F63B78;
+
+// From prjxray
+// The CRC is calculated from each written data word and the current
+// register address the data is written to.
+
+// Extend the current CRC value with one register address (5bit) and
+// frame data (32bit) pair and return the newly computed CRC value.
+
+inline uint32_t icap_crc(uint32_t addr, uint32_t data, uint32_t prev) {
+	constexpr int kAddressBitWidth = 5;
+	constexpr int kDataBitWidth = 32;
+
+	uint64_t poly = static_cast<uint64_t>(kCrc32CastagnoliPolynomial) << 1;
+	uint64_t val = (static_cast<uint64_t>(addr) << 32) | data;
+	uint64_t crc = prev;
+
+	for (int i = 0; i < kAddressBitWidth + kDataBitWidth; i++) {
+		if ((val & 1) != (crc & 1))
+			crc ^= poly;
+
+		val >>= 1;
+		crc >>= 1;
+	}
+	return crc;
+}
+
+// From Yosys
+inline std::string vstringf(const char *fmt, va_list ap)
+{
+	std::string string;
+	char *str = NULL;
+
+#if defined(_WIN32 )|| defined(__CYGWIN__)
+	int sz = 64, rc;
+	while (1) {
+		va_list apc;
+		va_copy(apc, ap);
+		str = (char*)realloc(str, sz);
+		rc = vsnprintf(str, sz, fmt, apc);
+		va_end(apc);
+		if (rc >= 0 && rc < sz)
+			break;
+		sz *= 2;
+	}
+#else
+	if (vasprintf(&str, fmt, ap) < 0)
+		str = NULL;
+#endif
+
+	if (str != NULL) {
+		string = str;
+		free(str);
+	}
+
+	return string;
+}
+
+inline std::string stringf(const char *fmt, ...)
+{
+	std::string string;
+	va_list ap;
+
+	va_start(ap, fmt);
+	string = vstringf(fmt, ap);
+	va_end(ap);
+
+	return string;
+}
+
+// Bitstream definitions
+
+enum BitstreamOp : uint8_t {
+	OP_NOP = 0,
+	OP_READ = 1,
+	OP_WRITE = 2
+};
+
+// File and database convenience functions
+// Line-by-line reader, skipping over blank lines and comments
+struct LineReader {
+	LineReader(const std::string &filename) {
+		in.open(filename);
+		if (!in) {
+			throw std::runtime_error("failed to open " + filename);
+		}
+	}
+	std::ifstream in;
+	std::string linebuf;
+	bool at_sof = true;
+
+	struct iterator {
+		LineReader *parent = nullptr;
+		bool at_end = false;
+		inline bool operator!=(const iterator &other) const {
+			return at_end != other.at_end;
+		};
+		inline const std::string& operator*() const {
+			return parent->linebuf;
+		}
+		inline iterator &operator++() {
+			parent->next();
+			at_end = parent->linebuf.empty();
+			return *this;
+		}
+	};
+
+	void next() {
+		while (std::getline(in, linebuf)) {
+			auto cpos = linebuf.find('#');
+			if (cpos != std::string::npos)
+				linebuf = linebuf.substr(0, cpos);
+			if (linebuf.empty())
+				continue;
+			linebuf = linebuf.substr(linebuf.find_first_not_of(" \t"));
+			if (linebuf.empty())
+				continue;			
+			break;
+		}
+		at_sof = false;
+	}
+
+	iterator begin() {
+		if (at_sof)
+			next();
+		return iterator{this, linebuf.empty()};
+	}
+
+	iterator end() {
+		return iterator{this, true};
+	}
+};
+
+struct TileInstance {
+	std::string name;
+	int type;
+	int x, y;
+	struct TileBitMapping {
+		int frame_offset;
+		int bit_offset;
+		int size;
+	};
+	std::vector<TileBitMapping> bits;
+};
+
+struct TileType {
+	std::string type;
+	bool loaded_db = false;
+	std::unordered_map<std::string, std::vector<int>> features;
+};
+
+struct ChipData {
+
+	std::string root;
+	void open(const std::string &root);
+	void load_frames();
+	void load_tiles();
+	void load_tiletype_database(int type);
+
+	std::unordered_map<std::string, TileInstance> tiles;
+	inline TileInstance &get_tile_by_name(const std::string &name) {
+		return tiles.at(name);
+	}
+
+	std::vector<TileType> tiletypes;
+	std::unordered_map<std::string, int> tiletype_by_name;
+	TileType &get_tiletype_by_name(const std::string &name) {
+		return tiletypes.at(tiletype_by_name.at(name));
+	}
+	TileType &load_tile_database(const std::string &tile) {
+		int type = tiles.at(tile).type;
+		load_tiletype_database(type);
+		return tiletypes.at(type);
+	}
+
+	std::set<uint32_t> all_frames;
+
+};
+
+inline void split_str(const std::string &s, std::vector<std::string> &dest, const std::string &delim = " ", bool skip_empty = true, int lim = -1) {
+	dest.clear();
+	std::string buf;
+
+	for (char c : s) {
+		if (delim.find(c) != std::string::npos && (lim == -1 || int(dest.size()) < lim)) {
+			if (!buf.empty() || !skip_empty)
+				dest.push_back(buf);
+			buf.clear();
+		} else {
+			buf += c;
+		}
+	}
+
+	if (!buf.empty())
+		dest.push_back(buf);
+}
+
+#endif
\ No newline at end of file
diff --git a/tools/correlate.cpp b/tools/correlate.cpp
new file mode 100644
index 0000000..2c4676d
--- /dev/null
+++ b/tools/correlate.cpp
@@ -0,0 +1,254 @@
+// 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.
+
+#include <vector>
+#include <iostream>
+#include <map>
+#include <string>
+#include <fstream>
+#include <stdexcept>
+#include <iterator>
+#include <stdarg.h>
+#include <unordered_set>
+#include <unordered_map>
+#include <set>
+#include <sstream>
+#include <algorithm>
+#include <filesystem>
+namespace fs = std::filesystem;
+
+struct FeatureData {
+	std::unordered_set<int> always_set_with_feature;
+	std::unordered_set<int> never_set_with_feature;
+	std::unordered_set<int> always_set_without_feature;
+
+	std::vector<std::pair<int, bool>> featbits;
+	std::set<std::string> deps;
+	int count;
+};
+
+struct TileTypeData {
+	std::unordered_map<std::string, std::unordered_set<std::string>> inst_features;
+	std::unordered_map<std::string, std::unordered_set<int>> inst_bits;
+	std::unordered_set<int> settable_bits;
+	std::set<std::string> extant_features;
+	std::unordered_map<std::string, FeatureData> features;
+};
+
+std::map<std::string, TileTypeData> tiletypes;
+std::set<std::string> include_tts;
+
+std::pair<std::string, std::string> split_tilename(const std::string &name) {
+	size_t idx = name.find(':');
+	return std::make_pair(name.substr(0, idx), name.substr(idx+1));
+}
+
+void parse_bits(const std::string &prefix, std::istream &in) {
+	std::string line;
+	std::unordered_set<int> *bits = nullptr;
+	std::unordered_set<int> *settable_bits = nullptr;
+	bool skip = false;
+
+	while (std::getline(in, line)) {
+		if (line.empty())
+			continue;
+		std::istringstream iss(line);
+		if (line.front() == '.') {
+			std::string t, tn;
+			iss >> t >> tn;
+			auto spn = split_tilename(tn);
+			if (!include_tts.empty() && !include_tts.count(spn.second)) {
+				skip = true;
+				continue;
+			} else {
+				skip = false;
+			}
+			settable_bits = &(tiletypes[spn.second].settable_bits);
+			bits = &(tiletypes[spn.second].inst_bits[prefix + spn.first]);
+		} else {
+			int bit = -1;
+			iss >> bit;
+			if (!skip && bit != -1) {
+				bits->insert(bit);
+				settable_bits->insert(bit);
+			}
+		}
+	}
+}
+
+void parse_features(const std::string &prefix, std::istream &in) {
+	std::string line;
+	std::unordered_set<std::string> *feats = nullptr;
+	std::set<std::string> *ext_feats = nullptr;
+	bool skip = false;
+
+	while (std::getline(in, line)) {
+		if (line.empty())
+			continue;
+		std::istringstream iss(line);
+		if (line.front() == '.') {
+			std::string t, tn;
+			iss >> t >> tn;
+			auto spn = split_tilename(tn);
+			if (!include_tts.empty() && !include_tts.count(spn.second)) {
+				skip = true;
+				continue;
+			} else {
+				skip = false;
+			}
+			ext_feats = &(tiletypes[spn.second].extant_features);
+			feats = &(tiletypes[spn.second].inst_features[prefix + spn.first]);
+		} else {
+			std::string feat;
+			iss >> feat;
+			if (!feat.empty() && !skip) {
+				feats->insert(feat);
+				ext_feats->insert(feat);
+			}
+		}
+	}
+}
+
+template <typename Tc, typename Tv, typename Tf> void set_erase_if(Tc &target, std::vector<Tv> &temp, Tf pred) {
+	temp.clear();
+	for (auto &entry : target)
+		if (pred(entry))
+			temp.push_back(entry);
+	for (auto &toerase : temp)
+		target.erase(toerase);
+}
+
+void find_feature_deps(TileTypeData &tt) {
+	for (auto &f : tt.extant_features) {
+		auto &fd = tt.features[f];
+		fd.deps = tt.extant_features;
+		fd.deps.erase(f);
+		std::vector<std::string> temp;
+		for (auto &inst : tt.inst_features) {
+			if (!inst.second.count(f))
+				continue;
+			set_erase_if(fd.deps, temp, [&](const std::string &ef){ return !inst.second.count(ef); });
+		}
+	}
+}
+
+void process_feature(TileTypeData &tt, const std::string &feature) {
+
+	FeatureData &fd = tt.features[feature];
+
+	fd.always_set_with_feature = tt.settable_bits;
+	//fd.never_set_with_feature = tt.settable_bits;
+	fd.always_set_without_feature = tt.settable_bits;
+
+	std::vector<int> temp;
+	fd.count = 0;
+	bool always_have_feature = true;
+	for (auto &inst : tt.inst_bits) {
+		if (!tt.inst_features.count(inst.first))
+			continue;
+		auto &ib = inst.second;
+		bool has_feature =  tt.inst_features.at(inst.first).count(feature);
+		if (!has_feature)
+			always_have_feature = false;
+
+		if (has_feature)
+			++fd.count;
+
+		if (has_feature)
+			set_erase_if(fd.always_set_with_feature, temp, [&](int bit){ return !ib.count(bit); });
+		//if (has_feature)
+		//	set_erase_if(fd.never_set_with_feature, temp, [&](int bit){ return ib.count(bit); });
+		if (!has_feature)
+			set_erase_if(fd.always_set_without_feature, temp, [&](int bit){ return !ib.count(bit); });
+	}
+	for (int as : fd.always_set_with_feature)
+		if (always_have_feature || !fd.always_set_without_feature.count(as))
+			if (std::all_of(fd.deps.begin(), fd.deps.end(), [&](const std::string &dep) {
+						auto &dd = tt.features[dep];
+						return std::find(dd.featbits.begin(), dd.featbits.end(), std::make_pair(as, false)) == dd.featbits.end();
+					}))
+				fd.featbits.emplace_back(as, false);
+	/*for (int nv : fd.never_set_with_feature)
+		if (fd.always_set_without_feature.count(nv))
+			fd.featbits.emplace_back(nv, true);*/
+	// FIXME: inverted feature bits?
+	std::sort(fd.featbits.begin(), fd.featbits.end());
+}
+
+int main(int argc, char *argv[]) {
+	if (argc < 3) {
+		std::cerr << "usage: correlate specfolder tiledata" << std::endl;
+		return 2;
+	}
+
+	if (argc > 3) {
+		for (int i = 3; i < argc; i++)
+			include_tts.insert(argv[i]);
+	}
+
+	for (const auto &entry : fs::directory_iterator(argv[1])) {
+		auto p = entry.path();
+		if (p.extension() != ".features")
+			continue;
+		std::ifstream tilebits(p.parent_path().string() + "/" + p.stem().string() + ".tbits");
+		if (!tilebits) {
+			std::cerr << "Failed to open " << (p.parent_path().string() + "/" + p.stem().string() + ".tbits") << std::endl;
+			return 1;
+		}
+		parse_bits(p.stem().string(), tilebits);
+		std::ifstream features(p.string());
+		if (!features) {
+			std::cerr << "Failed to open " << p.string() << std::endl;
+			return 1;
+		}
+		parse_features(p.stem().string(), features);
+	}
+
+
+
+	for (auto &tiletype : tiletypes) {
+		auto &t = tiletype.second;
+		std::ofstream td(std::string(argv[2]) + "/" + tiletype.first + ".bits");
+		find_feature_deps(tiletype.second);
+		std::vector<std::string> ord_feats(t.extant_features.begin(),
+			t.extant_features.end());
+		std::stable_sort(ord_feats.begin(), ord_feats.end(), [&](const std::string &a, const std::string &b) {
+			return t.features[a].deps.size() < t.features[b].deps.size();
+		});
+
+		for (auto &feat : ord_feats) {
+			std::cerr << "Processing " << tiletype.first << "." << feat << std::endl;
+			FeatureData fd;
+			process_feature(t, feat);
+		}
+
+		for (auto &feat : t.extant_features) {
+			auto &fd = t.features[feat];
+			if (fd.count < 2)
+				continue;
+			td << feat;
+			for (auto &fb : fd.featbits)
+				td << " " << (fb.second ? "!" : "") << fb.first;
+			td << " # count: " << fd.count;
+			if  (fd.deps.size() > 0) {
+				td << ", deps: ";
+				for (auto &d : fd.deps)
+					td << " " << d;
+			}
+			td << std::endl;
+		}
+	}
+
+	return 0;
+}
\ No newline at end of file
diff --git a/tools/dump_bitstream.cpp b/tools/dump_bitstream.cpp
new file mode 100644
index 0000000..c1b3689
--- /dev/null
+++ b/tools/dump_bitstream.cpp
@@ -0,0 +1,278 @@
+// 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.
+
+#include <vector>
+#include <iostream>
+#include <map>
+#include <string>
+#include <fstream>
+#include <stdexcept>
+#include <iterator>
+#include <stdarg.h>
+#include <iomanip>
+#include "common.h"
+
+const uint32_t preamble = 0xAA995566;
+
+#define SKIP_CRC
+#define SKIP_CHECKSUM
+#define SKIP_COMMENT
+
+class dummy_ostream : public std::ostream {
+
+};
+
+dummy_ostream dummy_out;
+
+bool verbose_flag = false;
+
+#define COMMENT(x) (verbose_flag ? std::cout : dummy_out) << x << std::endl
+
+
+struct ByteStreamReader {
+	std::vector<uint8_t> data;
+	std::size_t ptr = 0;
+	void reset() {
+		ptr = 0;
+	}
+	bool done() {
+		return ptr >= (data.size() - 3);
+	}
+	uint32_t curr_crc = 0;
+	uint32_t curr_addr = 0;
+
+	uint32_t next_u32(bool skip_crc = false) {
+		if (done())
+			throw std::runtime_error("at end of bitstream");
+		uint32_t val = data[ptr] << 24UL | data[ptr+1] << 16UL | data[ptr+2] << 8UL | data[ptr+3];
+		ptr += 4;
+		if (!skip_crc)
+			curr_crc = icap_crc(curr_addr, val, curr_crc);
+		return val;
+	}
+
+	uint32_t peek_u32() {
+		if (done())
+			throw std::runtime_error("at end of bitstream");
+		uint32_t val = data[ptr] << 24UL | data[ptr+1] << 16UL | data[ptr+2] << 8UL | data[ptr+3];
+		return val;
+	}
+	void skip_till_preamble() {
+		while (peek_u32() != preamble)
+			++ptr;
+		COMMENT("# found preamble at offset " << ptr);
+		ptr += 4;
+	}
+};
+
+
+
+enum BitstreamRegister : uint16_t {
+	#define X(a, b) a = b,
+	#include "registers.inc"
+	#undef X
+};
+
+
+
+std::string get_register_name(uint16_t val) {
+	BitstreamRegister r = (BitstreamRegister)val;
+	#define X(a, b) if (val == a) return #a;
+	#include "registers.inc"
+	#undef X
+	return stringf("reg%04x", val);
+}
+
+std::map<uint32_t, uint32_t> next_frame;
+
+void parse_bitstream(ByteStreamReader &rd) {
+	rd.reset();
+	rd.skip_till_preamble();
+	uint32_t frame = 0;
+	uint16_t last_reg = 0;
+	uint64_t checksum = 0, exp_checksum = 0;
+	int word = 0;
+	auto is_checksum = [] (int w, int b) {
+		return ((w == 45 && b <= 31) || (w == 46 && b <= 15));
+	};
+
+	auto get_ecc_value = [](int word, int bit) {
+		int nib = bit / 4;
+		int nibbit = bit % 4;
+		// ECC offset is expanded to 1 bit per nibble,
+		// and then shifted based on the bit index in nibble
+		// e.g. word 3, bit 9
+		// offset: 0b10100110010 - concatenate (3 + (255 - 92)) [frame offset] and 9/4 [nibble offset]
+		// becomes: 0x10100110010
+		// shifted by bit in nibble (9%4): 0x20200220020 
+		uint32_t offset =  (word + (255 - 92)) << 3  | nib;
+		uint64_t exp_offset = 0;
+		// Odd parity
+		offset ^= (1 << 11);
+		for (int i = 0; i < 11; i++)
+			if (offset & (1 << i)) offset ^= (1 << 11);
+		// Expansion
+		for (int i = 0; i < 12; i++)
+			if (offset & (1 << i)) exp_offset |= (1ULL << (4 * i));
+		return exp_offset << nibbit;
+	};
+
+	auto process_word = [&](uint32_t data) {
+
+
+		for (int i = 0; i < 32; i++)
+			if (data & (1 << i)) {
+				if (is_checksum(word, i)) {
+					checksum |= 1ULL << ((word - 45) * 32 + i);
+#ifdef SKIP_CHECKSUM
+					continue;
+#endif
+				} else {
+					exp_checksum ^= get_ecc_value(word, i);
+				}
+				std::cout <<  stringf("F0x%08xW%03dB%02d", frame, word, i) << std::endl;
+			}			
+		++word;
+		if (word >= 93 && next_frame.count(frame)) {
+			// 4 parity bits for each bit in nibbles
+#if 0
+			for (int i = 0; i < 4; i++)
+				for (int j = 0; j < 11; j++)
+					if (exp_checksum & (1ULL << (4 * j + i)))
+						exp_checksum ^= (1ULL << (44 + i));
+#endif
+			COMMENT(stringf("# checksum: 0x%012llX calc: 0x%012llX %s", checksum, exp_checksum, (checksum == exp_checksum) ? "" : "~~~~~"));
+			checksum = 0;
+			exp_checksum = 0;
+			frame = next_frame.at(frame);
+		}
+	};
+
+	while (!rd.done()) {
+		uint32_t hdr = rd.next_u32(true);
+		if (hdr == 0xFFFFFFFF) {
+			COMMENT("# desync");
+			rd.skip_till_preamble();
+			continue;
+		}
+		uint8_t type = (hdr >> 29) & 0x07;
+		if (type == 0b001) {
+			// Type 1 (short) packet
+			uint8_t op = (hdr >> 27) & 0x03;
+			switch(op) {
+				case 0x00:
+					COMMENT("# NOP ");
+					// NOP
+					break;
+				case 0x01:
+				    // READ
+				    break;
+				case 0x02: {
+					// WRITE
+					uint16_t reg = (hdr >> 13) & 0x3FFF;
+					rd.curr_addr = reg;
+					last_reg = reg;
+					int count = hdr & 0x3FF;
+					COMMENT("# write " << get_register_name(reg));
+					if (reg == FAR) {
+						if (count != 1)
+							COMMENT("# bad FAR length " << count);
+						exp_checksum = 0;
+						checksum = 0;
+						frame = rd.next_u32();
+						word = 0;
+						COMMENT(stringf("# frame 0x%08x", frame));
+					} else if (reg == CRC) {
+						uint32_t crc = 0;
+						for(int i = 0; i < count; i++)
+							crc = rd.next_u32(true);
+						COMMENT(stringf("# CRC written=%08x calc=%08x %s", crc, rd.curr_crc, (crc == rd.curr_crc) ? "" : "*****"));
+						rd.curr_crc = 0;
+					} else if (reg == FDRI) {
+						for(int i = 0; i < count; i++)
+							process_word(rd.next_u32());
+					} else if (reg == CMD) {
+						uint32_t cmd = 0;
+						for(int i = 0; i < count; i++)
+							cmd = rd.next_u32();
+						COMMENT(stringf("#     CMD %08x", cmd));
+						if (cmd == 0x7)
+							rd.curr_crc = 0;
+					} else {
+						for(int i = 0; i < count; i++)
+							COMMENT(stringf("#     data %08x", rd.next_u32()));
+					}
+
+					if (reg == CRC && next_frame.count(frame)) {
+						exp_checksum = 0;
+						checksum = 0;
+						frame = next_frame.at(frame);
+					}
+
+				} break;
+			}
+		} else if (type == 0b010) {
+			// Type 2 (long) packet
+			int count = hdr & 0x3FFFFFF;
+			if (last_reg == FDRI) {
+				for(int i = 0; i < count; i++)
+					process_word(rd.next_u32());
+			} else {
+				for(int i = 0; i < count; i++)
+					COMMENT(stringf("#     data %08x", rd.next_u32()));
+			}
+		} else {
+			std::cout << stringf("# unknown packet type %01x (header: %08x)", type, hdr) << std::endl;
+			return;
+		}
+	}
+}
+
+int main(int argc, char *argv[]) {
+	if (argc < 2) {
+		std::cerr << "Usage: dump_bitstream file.bit [frames.txt] [verbose]" << std::endl;
+		return 2;
+	}
+
+	if (argc > 2) {
+		std::ifstream frame_db(argv[2]);
+		bool had_last = false;
+		uint32_t last;
+		uint32_t val;
+		frame_db.unsetf(std::ios::dec);
+		frame_db.unsetf(std::ios::hex);
+		frame_db.unsetf(std::ios::oct);
+		while (frame_db >> val) {
+			if (had_last)
+				next_frame[last] = val;
+			last = val;
+			had_last = true;
+		}
+	}
+
+	if (argc > 3) {
+		if (std::string(argv[3]) == "verbose")
+			verbose_flag = true;
+	}
+
+	ByteStreamReader rd;
+	std::ifstream file(argv[1], std::ios::binary);
+	file.unsetf(std::ios::skipws);
+	if (!file) {
+		std::cerr << "Failed to open input file" << std::endl;
+		return 2;
+	}
+	rd.data.insert(rd.data.begin(), std::istream_iterator<uint8_t>(file), std::istream_iterator<uint8_t>()); 
+	parse_bitstream(rd);
+}
\ No newline at end of file
diff --git a/tools/explain.cpp b/tools/explain.cpp
new file mode 100644
index 0000000..acc1d07
--- /dev/null
+++ b/tools/explain.cpp
@@ -0,0 +1,146 @@
+// 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.
+
+#include <vector>
+#include <iostream>
+#include <map>
+#include <string>
+#include <fstream>
+#include <stdexcept>
+#include <iterator>
+#include <stdarg.h>
+#include <iomanip>
+#include <filesystem>
+#include <map>
+#include <set>
+#include <cassert>
+#include "common.h"
+
+ChipData chip;
+
+struct Tile {
+	std::string name, type;
+	TileInstance *data;
+	std::set<int> set_bits;
+	std::set<int> unknown_bits;
+	std::set<std::string> matched_features;
+};
+
+std::vector<Tile> tiles;
+std::unordered_map<std::string, int> tile_by_name;
+
+struct InverseTileBitMap {
+	int tile;
+	int offset_in_frame;
+	int offset_in_tile;
+	int size;
+};
+
+std::unordered_map<uint32_t, std::vector<InverseTileBitMap>> tiles_by_frame;
+
+void parse_bits(const std::string &filename) {
+	LineReader rd(filename);
+	for (auto &line : rd) {
+		assert(line.at(0) == 'F');
+		const char *curr = line.c_str() + 1;
+		char *next = nullptr;
+		uint32_t frame = std::strtoul(curr, &next, 16);
+		assert(*next == 'W');
+		curr = next + 1;
+		int word = std::strtol(curr, &next, 10);
+		curr = next + 1;
+		assert(*next == 'B');
+		int bit = std::strtol(curr, &next, 10);
+		if (tiles_by_frame.count(frame)) {
+			int fb = word * 32 + bit;
+			for (auto &t : tiles_by_frame.at(frame)) {
+				if (fb >= t.offset_in_frame && fb < (t.offset_in_frame + t.size)) {
+					int tilebit = (fb - t.offset_in_frame) + t.offset_in_tile;
+					tiles[t.tile].set_bits.insert(tilebit);
+					tiles[t.tile].unknown_bits.insert(tilebit);
+				}
+			}
+		}
+	}
+}
+
+// Currently have poor quality DBs for these tiles,
+// skip outputting them
+std::set<std::string> skip_tiles = {
+	"CLEL_L", "CLEM_R", "RCLK_INT_R", 
+};
+
+
+void setup_tiles() {
+	for (auto &tile : chip.tiles) {
+		auto &ti = tile.second;
+		if (skip_tiles.count(chip.tiletypes[ti.type].type))
+			continue;
+		Tile t;
+		t.name = ti.name;
+		t.type = chip.tiletypes[ti.type].type;
+		t.data = &ti;
+		tile_by_name[ti.name] = int(tiles.size());
+
+		int off = 0;
+		for (auto &b : ti.bits) {
+			tiles_by_frame[b.frame_offset].push_back(InverseTileBitMap{int(tiles.size()), b.bit_offset, off, b.size});
+			off += b.size;
+		}
+
+		tiles.push_back(t);
+
+	}
+}
+
+
+void process_tile(Tile &t) {
+	if (t.set_bits.empty())
+		return;
+	auto &td = chip.load_tile_database(t.name);
+	for (const auto &feat : td.features) {
+		if (feat.second.empty())
+			continue;
+		bool matched = true;
+		for (auto bit : feat.second)
+			if (!t.set_bits.count(bit)) {
+				matched = false;
+				break;
+			}
+		if (!matched)
+			continue;
+		t.matched_features.insert(feat.first);
+		for (auto bit : feat.second)
+			t.unknown_bits.erase(bit);
+	}
+}
+
+int main(int argc, char *argv[]) {
+	if (argc < 3) {
+		std::cerr << "usage: explain dbdir/ bitstream.dump" << std::endl;
+		return 2;
+	}
+	chip.open(argv[1]);
+	setup_tiles();
+	parse_bits(argv[2]);
+	for (auto &t : tiles) {
+		process_tile(t);
+		for (auto &f : t.matched_features)
+			std::cout << t.name << "." << f << std::endl;
+		for (auto b : t.unknown_bits)
+			std::cout << t.name << ".?" << b << std::endl;
+		if (!t.matched_features.empty() || !t.unknown_bits.empty())
+			std::cout << std::endl;
+	}
+}
\ No newline at end of file
diff --git a/tools/filter.py b/tools/filter.py
new file mode 100644
index 0000000..8776311
--- /dev/null
+++ b/tools/filter.py
@@ -0,0 +1,30 @@
+# 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
+import sys
+import json
+
+line_re = re.compile(r'F(0x[0-9A-Fa-f]+)W(\d+)B(\d+)')
+frames_to_tiles = {} # (start, size, tile, tile offset)
+
+active = False
+
+with open(sys.argv[1]) as f:
+	for line in f:
+		sl = line.strip()
+		if sl[0] == '.':
+			active = sys.argv[2] in sl
+		if active:
+			print(sl)
\ No newline at end of file
diff --git a/tools/frames.py b/tools/frames.py
new file mode 100644
index 0000000..8265cb3
--- /dev/null
+++ b/tools/frames.py
@@ -0,0 +1,37 @@
+# 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
+import sys
+
+line_re = re.compile(r'F(0x[0-9A-Fa-f]+).*')
+
+outlines = set()
+
+with open(sys.argv[1], 'r') as f:
+	for line in f:
+		m = line_re.match(line)
+		if not m:
+			continue
+		frame = int(m.group(1), 16)
+		bus = (frame >> 24) & 0x7
+		half = (frame >> 23) & 0x1
+		row = (frame >> 18) & 0x1F
+		col = (frame >> 8) & 0x3FF
+		minor = frame & 0xFF
+		outlines.add("F=%08x B=%d H=%d R=%03d C=%04d M=%03d"
+							% (frame, bus, half, row, col, minor))
+
+for o in sorted(outlines):
+	print(o)
diff --git a/tools/frames_2.py b/tools/frames_2.py
new file mode 100644
index 0000000..0fe5890
--- /dev/null
+++ b/tools/frames_2.py
@@ -0,0 +1,37 @@
+# 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
+import sys
+
+line_re = re.compile(r'0x([0-9A-Fa-f]+).*')
+
+outlines = set()
+
+with open(sys.argv[1], 'r') as f:
+	for line in f:
+		m = line_re.match(line)
+		if not m:
+			continue
+		frame = int(m.group(1), 16)
+		bus = (frame >> 24) & 0x7
+		half = (frame >> 23) & 0x1
+		row = (frame >> 18) & 0x1F
+		col = (frame >> 8) & 0x3FF
+		minor = frame & 0xFF
+		outlines.add("F=%08x B=%d H=%d R=%03d C=%04d M=%03d"
+							% (frame, bus, half, row, col, minor))
+
+for o in sorted(outlines):
+	print(o)
diff --git a/tools/ll.py b/tools/ll.py
new file mode 100644
index 0000000..f65beb9
--- /dev/null
+++ b/tools/ll.py
@@ -0,0 +1,38 @@
+# 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
+import sys
+
+line_re = re.compile(r'Bit\s+\d+\s+(0x[0-9A-Fa-f]+)\s+\d+\s+SLR\d\s+\d+\s+Block=([A-Za-z0-9_]+).*')
+
+outlines = set()
+
+with open(sys.argv[1], 'r') as f:
+	for line in f:
+		m = line_re.match(line)
+		if not m:
+			continue
+		frame = int(m.group(1), 16)
+		site = m.group(2)
+		bus = (frame >> 24) & 0x7
+		half = (frame >> 23) & 0x1
+		row = (frame >> 18) & 0x1F
+		col = (frame >> 8) & 0x3FF
+		minor = frame & 0xFF
+		outlines.add("F=%08x B=%d H=%d R=%03d C=%04d M=%03d %s"
+							% (frame, bus, half, row, col, minor, site))
+
+for o in sorted(outlines):
+	print(o)
diff --git a/tools/oddtiles.py b/tools/oddtiles.py
new file mode 100644
index 0000000..82451cf
--- /dev/null
+++ b/tools/oddtiles.py
@@ -0,0 +1,58 @@
+# 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
+import sys
+import json
+
+line_re = re.compile(r'F(0x[0-9A-Fa-f]+)W(\d+)B(\d+)')
+frames_to_tiles = {} # (start, size, tile, tile offset)
+
+with open(sys.argv[1]) as tb_f:
+	tbj = json.load(tb_f)
+
+for tilename, tiledata in tbj.items():
+	tile_offset = 0
+	for chunk in tiledata:
+		frame, start, size = chunk
+		if frame not in frames_to_tiles:
+			frames_to_tiles[frame] = []
+		frames_to_tiles[frame].append((start, size, tilename, tile_offset))
+		tile_offset += size
+
+tile_bits = {}
+
+with open(sys.argv[2]) as df:
+	for line in df:
+		m = line_re.match(line)
+		if not m:
+			continue
+		frame = int(m[1], 16)
+		if frame not in frames_to_tiles:
+			continue
+		framebit = int(m[2]) * 32 + int(m[3])
+		for fb in frames_to_tiles[frame]:
+			start, size, tile, toff = fb
+			if framebit > start and framebit < (start + size):
+				if tile not in tile_bits:
+					tile_bits[tile] = set()
+				tile_bits[tile].add(toff + (framebit - start))
+
+for tile, bits in sorted(tile_bits.items()):
+	if "CLE" in tile:
+		if 152 not in bits:
+			print(tile)
+	if "INT" in tile:
+		if 3640 not in bits:
+			print(tile)
\ No newline at end of file
diff --git a/tools/registers.inc b/tools/registers.inc
new file mode 100644
index 0000000..0f0c8d8
--- /dev/null
+++ b/tools/registers.inc
@@ -0,0 +1,20 @@
+X(CRC    , 0b00000)
+X(FAR    , 0b00001)
+X(FDRI   , 0b00010)
+X(FDRO   , 0b00011)
+X(CMD    , 0b00100)
+X(CTL0   , 0b00101)
+X(MASK   , 0b00110)
+X(STAT   , 0b00111)
+X(LOUT   , 0b01000)
+X(COR0   , 0b01001)
+X(MFWR   , 0b01010)
+X(CBC    , 0b01011)
+X(IDCODE , 0b01100)
+X(AXSS   , 0b01101)
+X(COR1   , 0b01110)
+X(WBSTAR , 0b10000)
+X(TIMER  , 0b10001)
+X(BOOTSTS, 0b10110)
+X(CTL1   , 0b11000)
+X(BSPI   , 0b11111)
\ No newline at end of file
diff --git a/tools/roi.py b/tools/roi.py
new file mode 100644
index 0000000..e1b217a
--- /dev/null
+++ b/tools/roi.py
@@ -0,0 +1,78 @@
+# 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
+import sys
+import json
+# Usage: tilegrid.json
+with open(sys.argv[1]) as tb_f:
+	tbj = json.load(tb_f)
+tile_to_frames = {}
+frame_to_tiles = {}
+for tilename, tiledata in tbj.items():
+	tn = tilename.split(":")[0]
+	tile_offset = 0
+	tile_to_frames[tn] = []
+	for chunk in tiledata:
+		frame, start, size = chunk
+		tile_to_frames[tn].append(frame)
+		if frame not in frame_to_tiles:
+			frame_to_tiles[frame] = []
+		frame_to_tiles[frame].append(tn)
+basis_tiles = [
+	"CLEM_X41Y120",
+	"INT_X41Y120",
+	"CLEL_R_X41Y120",
+	"BRAM_X42Y120",
+	"INT_INTF_L_X42Y120",
+	"INT_X42Y120",
+	"CLEL_R_X42Y120",
+	"CLEM_X43Y120",
+	"INT_X43Y120",
+	"INT_INTF_R_X43Y120",
+	"DSP_X43Y120",
+	"CLEM_X44Y120",
+	"INT_X44Y120",
+	"CLEL_R_X44Y120",
+	"CLEM_X45Y120",
+	"INT_X45Y120",
+	"INT_INTF_R_X45Y120",
+	"DSP_X45Y120",
+	"CLEM_X46Y120",
+	"INT_X46Y120",
+	"CLEL_R_X46Y120",
+	"INT_X47Y120",
+	"CLEL_R_X47Y120"
+]
+
+roi_frames = set()
+
+for tile in basis_tiles:
+	if tile not in tile_to_frames:
+		continue
+	for frame in tile_to_frames[tile]:
+		roi_frames.add(frame)
+
+roi_tiles = set()
+for frame in roi_frames:
+	for tile in frame_to_tiles[frame]:
+		roi_tiles.add(tile)
+
+with open(sys.argv[2], "w") as frames_f: 
+	for frame in sorted(roi_frames):
+		print("0x%08x" % frame, file=frames_f)
+
+with open(sys.argv[3], "w") as tiles_f:
+	for tile in sorted(roi_tiles):
+		print("tile %s" % tile, file=tiles_f)
\ No newline at end of file
diff --git a/tools/stripdb.cpp b/tools/stripdb.cpp
new file mode 100644
index 0000000..01ff1ab
--- /dev/null
+++ b/tools/stripdb.cpp
@@ -0,0 +1,104 @@
+// 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.
+
+#include <vector>
+#include <iostream>
+#include <map>
+#include <string>
+#include <fstream>
+#include <stdexcept>
+#include <iterator>
+#include <stdarg.h>
+#include <iomanip>
+#include <filesystem>
+#include <map>
+#include <set>
+
+namespace fs = std::filesystem;
+
+
+struct TileType {
+	std::map<std::string, std::vector<int>> features;
+};
+
+std::map<std::string, TileType> tiletypes;
+
+
+void parse_database(const std::string &name, std::istream &in) {
+	std::string line;
+	while (std::getline(in, line)) {
+		auto cpos = line.find('#');
+		if (cpos != std::string::npos)
+			line = line.substr(0, cpos);
+		if (line.empty())
+			continue;
+		std::istringstream iss(line);
+		std::string featname;
+		iss >> featname;
+		if (featname.empty())
+			continue;
+		tiletypes[name].features[featname];
+		int bit = -1;
+		iss >> bit;
+		while (bit != -1) {
+			tiletypes[name].features[featname].push_back(bit);
+			bit = -1;
+			iss >> bit;
+		}
+	}
+}
+
+
+int main(int argc, char *argv[]) {
+	if (argc < 3) {
+		std::cerr << "usage: stripdb in/ out/" << std::endl;
+		return 2;
+	}
+
+	// Currently have poor quality DBs for these tiles,
+	// skip outputting them
+	std::set<std::string> skip_tiles = {
+		"CLEL_L", "CLEM_R", "RCLK_INT_R", 
+	};
+
+	for (const auto &entry : fs::directory_iterator(argv[1])) {
+		auto p = entry.path();
+		if (p.extension() != ".bits")
+			continue;
+		std::ifstream tiledata(p.string());
+		if (skip_tiles.count(p.stem()))
+			continue;
+		parse_database(p.stem(), tiledata);
+	}
+
+	// Misc cleanups
+	for (auto &f : tiletypes["INT"].features)
+		if (f.first.find(".VCC_WIRE") != std::string::npos)
+			f.second.clear();
+
+	for (const auto &tt : tiletypes) {
+		if(tt.second.features.empty())
+			continue;
+		std::string dbname = std::string(argv[2]) + "/" + tt.first + ".bits";
+		std::ofstream out(dbname);
+		if (!out)
+			std::cerr << "failed to open " <<  dbname << " for writing." << std::endl;
+		for (auto &f : tt.second.features) {
+			out << f.first;
+			for (int bit : f.second)
+				out << " " << bit;
+			out << std::endl;
+		}
+	}
+}
\ No newline at end of file
diff --git a/tools/tilebits.py b/tools/tilebits.py
new file mode 100644
index 0000000..57fbe2b
--- /dev/null
+++ b/tools/tilebits.py
@@ -0,0 +1,151 @@
+# 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
+import sys
+import json
+
+tiles = {}
+site_to_tile = {}
+tile_to_bits = {} # (frame, bit start, bit size)
+
+with open(sys.argv[1], 'r') as tilef:
+	for line in tilef:
+		sl = line.strip().split(",")
+		if len(sl) < 4:
+			continue
+		x = int(sl[0])
+		y = int(sl[1])
+		name = sl[2]
+		ttype = sl[3]
+		tiles[(x, y)] = (name + ":" + ttype, ttype, [])
+		for site in sl[4:]:
+			sitename, sitetype = site.split(":")
+			tiles[(x, y)][2].append((sitename, sitetype))
+			site_to_tile[sitename] = (x, y)
+
+ll_line_re = re.compile(r'Bit\s+\d+\s+(0x[0-9A-Fa-f]+)\s+(\d+)\s+SLR\d\s+\d+\s+Block=([A-Za-z0-9_]+).*')
+site_re = re.compile(r'SLICE_X(\d+)Y(\d+)')
+with open(sys.argv[2], 'r') as llf:
+	for line in llf:
+		m = ll_line_re.match(line)
+		if not m:
+			continue
+		frame = int(m.group(1), 16)
+		bit = int(m.group(2))
+		start_bit = bit - 2
+		site = m.group(3)
+		bus = (frame >> 24) & 0x7
+		half = (frame >> 23) & 0x1
+		row = (frame >> 18) & 0x1F
+		col = (frame >> 8) & 0x3FF
+		m = frame & 0xFF
+
+
+		sm = site_re.match(site)
+		site_x = int(sm.group(1))
+		site_y = int(sm.group(2))
+		frame_upper = frame & ~0xFF
+
+
+		if site not in site_to_tile:
+			continue
+		tx, ty = site_to_tile[site]
+		tiledata = tiles[tx, ty]
+		tile_to_bits[tiledata[0]] = []
+		for m in range(16):
+			tile_to_bits[tiledata[0]].append((frame_upper | m, start_bit, 48))
+
+		def process_nonlogic(x, y, icol):
+			if (x, y) not in tiles:
+				return
+			itiledata = tiles[x, y]
+			if itiledata[1] == "INT":
+				if (x + 1, y) not in tiles:
+					return
+
+				int_frame_base = (frame_upper & ~0x3FFFF) | (icol << 8)
+				tile_to_bits[itiledata[0]] = []
+				for m in range(76):
+					tile_to_bits[itiledata[0]].append((int_frame_base | m, start_bit, 48))
+				process_clock(x, y-1, int_frame_base, start_bit + 48, 76)
+				process_cmt(x-2, y, icol-2)
+				process_int_intf(x-1, y, icol-1)
+			elif itiledata[1] == "BRAM":
+				bram_frame_base = (frame_upper & ~0x3FFFF) | (icol << 8)
+				tile_to_bits[itiledata[0]] = []
+				for m in range(6):
+					tile_to_bits[itiledata[0]].append((bram_frame_base | m, start_bit, 5 * 48))
+				process_clock(x, y-5, bram_frame_base, start_bit + 5*48, 6)
+			elif itiledata[1] == "DSP":
+				dsp_frame_base = (frame_upper & ~0x3FFFF) | (icol << 8)
+				tile_to_bits[itiledata[0]] = []
+				for m in range(8):
+					tile_to_bits[itiledata[0]].append((dsp_frame_base | m, start_bit, 5 * 48))
+				process_clock(x, y-5, dsp_frame_base, start_bit + 5*48, 8)
+
+				if (x - 1, y) in tiles and "INT_INTF" in tiles[x - 1, y][1]:
+					process_clock(x-1, y-5, dsp_frame_base, start_bit + 5*48, 8)
+
+		def process_clock(cx, cy, frame_base, end_bit, height):
+			if (cx, cy) not in tiles:
+				return
+			if end_bit != (1392 + 48):
+				return
+			ctiledata = tiles[cx, cy]
+			if not ctiledata[1].startswith("RCLK"):
+				return
+			tile_to_bits[ctiledata[0]] = []
+			for m in range(height):
+				tile_to_bits[ctiledata[0]].append((frame_base | m, end_bit + 48, 48))
+
+		def process_cmt(x, y, icol):
+			if (x, y) not in tiles:
+				return
+			ctiledata = tiles[x, y]
+			if not ctiledata[1] in ("CMT_L", "CMT_RIGHT"):
+				return
+			cmt_frame_base = (frame_upper & ~0x3FFFF) | (icol << 8)
+			tile_to_bits[ctiledata[0]] = []
+			for m in range(12):
+				tile_to_bits[ctiledata[0]].append((cmt_frame_base | m, start_bit, 60 * 48))
+
+		def process_int_intf(x, y, icol):
+			if (x, y) not in tiles:
+				return
+			itiledata = tiles[x, y]
+			if not itiledata[1] in ("INT_INTF_L_IO", "INT_INTF_R_IO"):
+				return
+			int_frame_base = (frame_upper & ~0x3FFFF) | (icol << 8)
+			tile_to_bits[itiledata[0]] = []
+			for m in range(4):
+				tile_to_bits[itiledata[0]].append((int_frame_base | m, start_bit, 48))
+
+
+		process_nonlogic(tx-1, ty, col-1)
+		process_nonlogic(tx+1, ty, col+1)
+		process_clock(tx, ty-1, frame_upper, start_bit + 48, 16)
+# Original JSON
+with open(sys.argv[3], 'w') as tj:
+	tj.write(json.dumps(tile_to_bits, sort_keys=True, indent=4, separators=(',', ': ')))
+	tj.write("\n")
+# New simplified text format
+with open(sys.argv[4], 'w') as tf:
+	for loc, tiledata in sorted(tiles.items()):
+		print(".tile %s %s %d %d" % (tiledata[0].split(":")[0], tiledata[1], loc[0], loc[1]), file=tf)
+		for site in tiledata[2]:
+			print("site %s %s" % site, file=tf)
+		if tiledata[0] in tile_to_bits:
+			for frame, offset, size in tile_to_bits[tiledata[0]]:
+				print("frame 0x%08x bits %d +: %d" % (frame, offset, size), file=tf)
\ No newline at end of file
diff --git a/tools/update_tilebits_all.sh b/tools/update_tilebits_all.sh
new file mode 100644
index 0000000..0596c19
--- /dev/null
+++ b/tools/update_tilebits_all.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env 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.
+
+for x in $1/*.dump; do
+	python bits_to_tiles.py $2 $x > ${x%.*}.tbits 
+done