Add prjuray tools

Signed-off-by: Tomasz Michalak <tmichalak@antmicro.com>
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 0000000..ec121a9
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1,4 @@
+CMakeCache.txt
+CMakeFiles
+Makefile
+cmake_install.cmake
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 2ebadf1..612bef1 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -33,3 +33,19 @@
 	gflags
 	libprjxray
 )
+
+add_library(common common.cpp)
+
+add_executable(dump_bitstream dump_bitstream.cpp)
+target_compile_options(dump_bitstream PRIVATE -Wno-error)
+target_link_libraries(dump_bitstream common)
+
+add_executable(correlate correlate.cpp)
+add_executable(stripdb stripdb.cpp)
+set_target_properties(correlate stripdb PROPERTIES CXX_STANDARD 17)
+
+add_executable(explain explain.cpp)
+target_link_libraries(explain common)
+
+add_executable(assemble assemble.cpp)
+target_link_libraries(assemble common)
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