Initial version of SymbiFlow tools

Signed-off-by: Tomasz Michalak <tmichalak@antmicro.com>
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5c032ba
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,24 @@
+[submodule "third_party/abseil-cpp"]
+	path = third_party/abseil-cpp
+	url = https://github.com/abseil/abseil-cpp
+[submodule "third_party/cctz"]
+	path = third_party/cctz
+	url = https://github.com/google/cctz
+[submodule "third_party/googletest"]
+	path = third_party/googletest
+	url = https://github.com/google/googletest
+[submodule "third_party/gflags"]
+	path = third_party/gflags
+	url = https://github.com/gflags/gflags
+[submodule "third_party/yaml-cpp"]
+	path = third_party/yaml-cpp
+	url = https://github.com/jbeder/yaml-cpp.git
+[submodule "third_party/python-sdf-timing"]
+	path = third_party/python-sdf-timing
+	url = https://github.com/SymbiFlow/python-sdf-timing.git
+[submodule "third_party/yosys"]
+	path = third_party/yosys
+	url = https://github.com/YosysHQ/yosys
+[submodule "third_party/sanitizers-cmake"]
+	path = third_party/sanitizers-cmake
+	url = https://github.com/arsenm/sanitizers-cmake.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..3ec89c6
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,44 @@
+cmake_minimum_required(VERSION 3.5.0)
+
+project(prjxray)
+option(PRJXRAY_BUILD_TESTING "" OFF)
+
+# Add sanitizers-cmake package
+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
+find_package(Sanitizers)
+if(NOT CMAKE_BUILD_TYPE)
+	set(CMAKE_BUILD_TYPE Release CACHE STRING
+		"Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
+		FORCE)
+endif()
+
+# Hack for missing option in cctz
+option(BUILD_TESTING "" OFF)
+
+if(PRJXRAY_BUILD_TESTING)
+	enable_testing()
+endif()
+
+add_subdirectory(third_party/googletest EXCLUDE_FROM_ALL)
+add_subdirectory(third_party/gflags EXCLUDE_FROM_ALL)
+add_subdirectory(third_party/cctz EXCLUDE_FROM_ALL)
+add_subdirectory(third_party/abseil-cpp EXCLUDE_FROM_ALL)
+
+include(CheckCXXCompilerFlag)
+CHECK_CXX_COMPILER_FLAG("-Wundefined-var-template" CXX_COMPILER_SUPPORTS_UNDEF_VAR)
+if(${CXX_COMPILER_SUPPORTS_UNDEF_VAR})
+    add_compile_options("-Wno-undefined-var-template")
+endif()
+
+option(YAML_CPP_BUILD_TESTS "" OFF)
+add_subdirectory(third_party/yaml-cpp EXCLUDE_FROM_ALL)
+target_include_directories(yaml-cpp PUBLIC
+	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/third_party/yaml-cpp/include>
+	)
+
+# Set the CXX standard and compile time for our code only.
+set(CMAKE_CXX_STANDARD 14)
+add_compile_options(-Wall -Werror)
+
+add_subdirectory(lib)
+add_subdirectory(tools)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..03cae06
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,187 @@
+SHELL = bash
+ALL_EXCLUDE = third_party .git env build
+
+# Check if root
+ifeq ($(shell id -u),0)
+        $(error ERROR: Running as ID 0)
+endif
+
+# Tools + Environment
+IN_ENV = if [ -e env/bin/activate ]; then . env/bin/activate; fi;
+env:
+	virtualenv --python=python3 env
+	# Install prjxray
+	ln -sf $(PWD)/prjxray env/lib/python3.*/site-packages/
+	$(IN_ENV) python -c "import prjxray"
+	# Install fasm from third_party
+	$(IN_ENV) pip install --upgrade -e third_party/fasm
+	# Install sdfparse form third party
+	$(IN_ENV) pip install --upgrade -e third_party/python-sdf-timing
+	# Install project dependencies
+	$(IN_ENV) pip install -r requirements.txt
+	# Install project's documentation dependencies
+	$(IN_ENV) pip install -r docs/requirements.txt
+	# Check fasm library was installed
+	$(IN_ENV) python -c "import fasm"
+	$(IN_ENV) python -c "import fasm.output"
+	# Check sdfparse lib was installed
+	$(IN_ENV) python -c "import sdf_timing"
+	$(IN_ENV) python -c "import sdf_timing.sdfparse"
+	# Check YAML is installed
+	$(IN_ENV) python -c "import yaml" || (echo "Unable to find python-yaml" && exit 1)
+
+build:
+	git submodule update --init --recursive
+	mkdir -p build
+	cd build; cmake ..; $(MAKE)
+
+.PHONY: env build
+
+# Run tests of code.
+# ------------------------
+TEST_EXCLUDE = $(foreach x,$(ALL_EXCLUDE) docs fuzzers minitests experiments,--ignore $(x))
+
+test: test-py test-cpp
+	@true
+
+test-py:
+	$(IN_ENV) which py.test; py.test $(TEST_EXCLUDE) --doctest-modules --junitxml=build/py_test_results.xml
+
+test-cpp:
+	mkdir -p build
+	cd build && cmake -DPRJXRAY_BUILD_TESTING=ON ..
+	cd build && $(MAKE) -s
+	cd build && ctest --no-compress-output -T Test -C RelWithDebInfo --output-on-failure
+	xsltproc .github/kokoro/ctest2junit.xsl build/Testing/*/Test.xml > build/cpp_test_results.xml
+
+.PHONY: test test-py test-cpp
+
+# Auto formatting of code.
+# ------------------------
+FORMAT_EXCLUDE = $(foreach x,$(ALL_EXCLUDE),-and -not -path './$(x)/*') -and -not -name *.bit
+
+CLANG_FORMAT ?= clang-format-5.0
+format-cpp:
+	find . -name \*.cc $(FORMAT_EXCLUDE) -print0 | xargs -0 -P $$(nproc) ${CLANG_FORMAT} -style=file -i
+	find . -name \*.h $(FORMAT_EXCLUDE) -print0 | xargs -0 -P $$(nproc) ${CLANG_FORMAT} -style=file -i
+
+format-docs:
+	./.github/update-contributing.py
+
+PYTHON_FORMAT ?= yapf
+format-py:
+	$(IN_ENV) find . -name \*.py $(FORMAT_EXCLUDE) -print0 | xargs -0 -P $$(nproc) yapf -p -i
+
+TCL_FORMAT ?= utils//tcl-reformat.sh
+format-tcl:
+	find . -name \*.tcl $(FORMAT_EXCLUDE) -print0 | xargs -0 -P $$(nproc) -n 1 $(TCL_FORMAT)
+
+# Command to find and replace trailing whitespace in-place using `sed` (This is
+# placed inside quotes later so need to escape the "'")
+WS_CMD = sed -i '\''s@\s\+$$@@g'\''
+
+# File filter for files to fix trailing whitespace in, this is just a couple of
+# chained bash conditionals ensuring that the file (indicated by {}, provided by
+# xargs later) is a file, and not a directory or link.  Also filters out .bit
+# files as these are the only binary files currently tracked by Git and we don't
+# want to inadvertently change these at all.
+WS_FILTER = [ -f {} -a ! -L {} ] && [[ {} != *.bit ]]
+
+# For every file piped to $(WS_FORMAT) apply the filter and perform the command,
+# if a file does not match the filter, just returns true.
+WS_FORMAT = xargs -P $$(nproc) -n 1 -I{} bash -c '$(WS_FILTER) && $(WS_CMD) {} || true'
+
+format-trailing-ws:
+	# Use `git ls-files` to give us a complete list of tracked files to fix
+	# whitespace in; there is no point spending time processing anything that is
+	# not known to Git.
+	git ls-files | $(WS_FORMAT)
+
+	# Additionally fix untracked (but not ignored) files.
+	git ls-files -o --exclude-standard | $(WS_FORMAT)
+
+format: format-cpp format-docs format-py format-tcl format-trailing-ws
+	@true
+
+.PHONY: format format-cpp format-py format-tcl format-trailing-ws
+
+# Targets related to Project X-Ray databases
+# ------------------------
+
+DATABASES=artix7 kintex7 zynq7
+
+define database
+
+# $(1) - Database name
+
+db-check-$(1):
+	@echo
+	@echo "Checking $(1) database"
+	@echo "============================"
+	@$(IN_ENV) python3 utils/checkdb.py --db-root database/$(1)
+
+db-format-$(1):
+	@echo
+	@echo "Formatting $(1) database"
+	@echo "============================"
+	@$(IN_ENV) cd database/$(1); python3 ../../utils/sort_db.py
+	@if [ -e database/Info.md ]; then $(IN_ENV) ./utils/info_md.py --keep; fi
+
+.PHONY: db-check-$(1) db-format-$(1)
+.NOTPARALLEL: db-check-$(1) db-format-$(1)
+
+db-check: db-check-$(1)
+db-format: db-format-$(1)
+
+endef
+
+$(foreach DB,$(DATABASES),$(eval $(call database,$(DB))))
+
+.PHONY: db-extras-artix7 db-extras-kintex7 db-extras-zynq7
+
+db-extras-artix7:
+	+source minitests/roi_harness/basys3-swbut.sh && $(MAKE) -C fuzzers part_only
+	+source minitests/roi_harness/arty-uart.sh && $(MAKE) -C fuzzers part_only
+	+source minitests/roi_harness/basys3-swbut.sh && \
+		$(MAKE) -C minitests/roi_harness \
+			HARNESS_DIR=$(XRAY_DATABASE_DIR)/artix7/harness/basys3/swbut run copy
+	+source minitests/roi_harness/basys3-swbut.sh && \
+		$(MAKE) -C minitests/roi_harness \
+			XRAY_ROIV=../roi_base_div2.v \
+			HARNESS_DIR=$(XRAY_DATABASE_DIR)/artix7/harness/basys3/swbut_50 run copy
+	+source minitests/roi_harness/arty-uart.sh && \
+		$(MAKE) -C minitests/roi_harness \
+			HARNESS_DIR=$(XRAY_DATABASE_DIR)/artix7/harness/arty-a7/uart run copy
+	+source minitests/roi_harness/arty-pmod.sh && \
+		$(MAKE) -C minitests/roi_harness \
+			HARNESS_DIR=$(XRAY_DATABASE_DIR)/artix7/harness/arty-a7/pmod run copy
+	+source minitests/roi_harness/arty-swbut.sh && \
+		$(MAKE) -C minitests/roi_harness \
+			HARNESS_DIR=$(XRAY_DATABASE_DIR)/artix7/harness/arty-a7/swbut run copy
+
+db-extras-kintex7:
+	@true
+
+db-extras-zynq7:
+	+source minitests/roi_harness/zybo-swbut.sh && $(MAKE) -C fuzzers part_only
+	+source minitests/roi_harness/zybo-swbut.sh && \
+		$(MAKE) -C minitests/roi_harness \
+			HARNESS_DIR=$(XRAY_DATABASE_DIR)/zynq7/harness/zybo/swbut run
+
+db-check:
+	@true
+
+db-format:
+	@true
+
+db-info:
+	$(IN_ENV) ./utils/info_md.py
+
+.PHONY: db-check db-format
+
+clean:
+	$(MAKE) -C database clean
+	$(MAKE) -C fuzzers clean
+	rm -rf build
+
+.PHONY: clean
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
new file mode 100644
index 0000000..3edcf3c
--- /dev/null
+++ b/lib/CMakeLists.txt
@@ -0,0 +1,91 @@
+add_library(libprjxray
+	database.cc
+	memory_mapped_file.cc
+	segbits_file_reader.cc
+	xilinx/bitstream_writer.cc
+	xilinx/configuration_packet.cc
+	xilinx/configuration_register.cc
+	xilinx/configuration.cc
+	xilinx/frames.cc
+	# Spartan6 specific
+	xilinx/spartan6/frame_address.cc
+	xilinx/spartan6/global_clock_region.cc
+	xilinx/spartan6/part.cc
+	xilinx/spartan6/configuration_row.cc
+	xilinx/spartan6/block_type.cc
+	xilinx/spartan6/configuration_bus.cc
+	xilinx/spartan6/configuration_column.cc
+	# Series-7 specific
+	xilinx/xc7series/frame_address.cc
+	xilinx/xc7series/global_clock_region.cc
+	xilinx/xc7series/part.cc
+	xilinx/xc7series/configuration_row.cc
+	xilinx/xc7series/block_type.cc
+	xilinx/xc7series/configuration_bus.cc
+	xilinx/xc7series/configuration_column.cc
+	xilinx/xc7series/ecc.cc
+)
+target_include_directories(libprjxray PUBLIC "include")
+target_link_libraries(libprjxray absl::optional absl::variant absl::strings absl::span absl::time yaml-cpp)
+
+if (PRJXRAY_BUILD_TESTING)
+	add_executable(big_endian_span_test big_endian_span_test.cc)
+	target_link_libraries(big_endian_span_test libprjxray gtest_main)
+	add_test(NAME big_endian_span_test
+		 COMMAND big_endian_span_test)
+
+	add_executable(bit_ops_test bit_ops_test.cc)
+	target_link_libraries(bit_ops_test libprjxray gtest_main)
+	add_test(NAME bit_ops_test
+		 COMMAND bit_ops_test)
+
+	add_executable(memory_mapped_file_test memory_mapped_file_test.cc)
+	target_link_libraries(memory_mapped_file_test libprjxray gtest_main)
+	add_test(NAME memory_mapped_file_test
+		 COMMAND memory_mapped_file_test
+		 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test_data)
+
+	add_executable(segbits_file_reader_test segbits_file_reader_test.cc)
+	target_link_libraries(segbits_file_reader_test libprjxray gtest_main)
+	add_test(NAME segbits_file_reader_test
+		 COMMAND segbits_file_reader_test
+		 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test_data)
+
+	add_executable(xilinx_xc7series_test
+		xilinx/tests/xc7series/bitstream_reader_test.cc
+		xilinx/tests/xc7series/bitstream_writer_test.cc
+		xilinx/tests/xc7series/block_type_test.cc
+		xilinx/tests/xc7series/configuration_bus_test.cc
+		xilinx/tests/xc7series/configuration_column_test.cc
+		xilinx/tests/xc7series/configuration_test.cc
+		xilinx/tests/xc7series/configuration_packet_test.cc
+		xilinx/tests/xc7series/crc_test.cc
+		xilinx/tests/xc7series/ecc_test.cc
+		xilinx/tests/xc7series/frame_address_test.cc
+		xilinx/tests/xc7series/global_clock_region_test.cc
+		xilinx/tests/xc7series/part_test.cc
+		xilinx/tests/xc7series/row_test.cc
+		xilinx/tests/xc7series/frames_test.cc)
+	target_link_libraries(xilinx_xc7series_test libprjxray gtest_main)
+	add_test(NAME xilinx_xc7series_test
+		 COMMAND xilinx_xc7series_test
+		 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test_data)
+
+	add_executable(xilinx_spartan6_test
+		xilinx/tests/spartan6/bitstream_reader_test.cc
+		xilinx/tests/spartan6/bitstream_writer_test.cc
+		xilinx/tests/spartan6/block_type_test.cc
+		xilinx/tests/spartan6/configuration_bus_test.cc
+		xilinx/tests/spartan6/configuration_column_test.cc
+		xilinx/tests/spartan6/configuration_test.cc
+		xilinx/tests/spartan6/configuration_packet_test.cc
+		xilinx/tests/spartan6/frame_address_test.cc
+		xilinx/tests/spartan6/global_clock_region_test.cc
+		xilinx/tests/spartan6/part_test.cc
+		xilinx/tests/spartan6/row_test.cc
+		xilinx/tests/spartan6/frames_test.cc)
+	target_link_libraries(xilinx_spartan6_test libprjxray gtest_main)
+	add_test(NAME xilinx_spartan6_test
+		 COMMAND xilinx_spartan6_test
+		 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test_data)
+endif()
diff --git a/lib/big_endian_span_test.cc b/lib/big_endian_span_test.cc
new file mode 100644
index 0000000..dcc673a
--- /dev/null
+++ b/lib/big_endian_span_test.cc
@@ -0,0 +1,42 @@
+#include <prjxray/big_endian_span.h>
+
+#include <cstdint>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+TEST(BigEndianSpanTest, Read32WithEmptySpan) {
+	std::vector<uint8_t> bytes;
+	auto words = prjxray::make_big_endian_span<uint32_t>(bytes);
+	EXPECT_EQ(words.size(), static_cast<size_t>(0));
+}
+
+TEST(BigEndianSpanTest, Read32WithTooFewBytes) {
+	std::vector<uint8_t> bytes{0x0, 0x1, 0x2};
+	auto words = prjxray::make_big_endian_span<uint32_t>(bytes);
+	EXPECT_EQ(words.size(), static_cast<size_t>(0));
+}
+
+TEST(BigEndianSpanTest, Read32WithExactBytes) {
+	std::vector<uint8_t> bytes{0x0, 0x1, 0x2, 0x3};
+	auto words = prjxray::make_big_endian_span<uint32_t>(bytes);
+	ASSERT_EQ(words.size(), static_cast<size_t>(1));
+	EXPECT_EQ(words[0], static_cast<uint32_t>(0x00010203));
+}
+
+TEST(BigEndianSpanTest, Read32WithMultipleWords) {
+	std::vector<uint8_t> bytes{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7};
+	auto words = prjxray::make_big_endian_span<uint32_t>(bytes);
+	ASSERT_EQ(words.size(), static_cast<size_t>(2));
+	EXPECT_EQ(words[0], static_cast<uint32_t>(0x00010203));
+	EXPECT_EQ(words[1], static_cast<uint32_t>(0x04050607));
+}
+
+TEST(BigEndianSpanTest, Write32) {
+	std::vector<uint8_t> bytes{0x0, 0x1, 0x2, 0x3};
+	auto words = prjxray::make_big_endian_span<uint32_t>(bytes);
+	words[0] = 0x04050607;
+
+	std::vector<uint8_t> expected{0x4, 0x5, 0x6, 0x7};
+	EXPECT_EQ(bytes, expected);
+}
diff --git a/lib/bit_ops_test.cc b/lib/bit_ops_test.cc
new file mode 100644
index 0000000..1837778
--- /dev/null
+++ b/lib/bit_ops_test.cc
@@ -0,0 +1,67 @@
+#include <prjxray/bit_ops.h>
+
+#include <gtest/gtest.h>
+
+TEST(BitMaskTest, Bit0) {
+	uint32_t expected = prjxray::bit_mask<uint32_t>(0);
+	EXPECT_EQ(static_cast<uint32_t>(0x1), expected);
+}
+
+TEST(BitMaskTest, Bit3) {
+	uint32_t expected = prjxray::bit_mask<uint32_t>(3);
+	EXPECT_EQ(static_cast<uint32_t>(0x8), expected);
+}
+
+TEST(BitMaskRange, SingleBit) {
+	uint32_t expected = prjxray::bit_mask_range<uint32_t>(23, 23);
+	EXPECT_EQ(static_cast<uint32_t>(0x800000), expected);
+}
+
+TEST(BitMaskRange, DownToZero) {
+	uint32_t expected = prjxray::bit_mask_range<uint32_t>(7, 0);
+	EXPECT_EQ(static_cast<uint32_t>(0xFF), expected);
+}
+
+TEST(BitMaskRange, MiddleBits) {
+	uint32_t expected = prjxray::bit_mask_range<uint32_t>(18, 8);
+	EXPECT_EQ(static_cast<uint32_t>(0x7FF00), expected);
+}
+
+TEST(BitFieldGetTest, OneSelectedBit) {
+	uint32_t expected = prjxray::bit_field_get(0xFFFFFFFF, 23, 23);
+	EXPECT_EQ(static_cast<uint32_t>(1), expected);
+}
+
+TEST(BitFieldGetTest, SelectDownToZero) {
+	uint32_t expected = prjxray::bit_field_get(0xFFCCBBAA, 7, 0);
+	EXPECT_EQ(static_cast<uint32_t>(0xAA), expected);
+}
+
+TEST(BitFieldGetTest, SelectMidway) {
+	uint32_t expected = prjxray::bit_field_get(0xFFCCBBAA, 18, 8);
+	EXPECT_EQ(static_cast<uint32_t>(0x4BB), expected);
+}
+
+TEST(BitFieldSetTest, WriteOneBit) {
+	uint32_t actual = prjxray::bit_field_set(
+	    static_cast<uint32_t>(0x0), 23, 23, static_cast<uint32_t>(0x1));
+	EXPECT_EQ(actual, static_cast<uint32_t>(0x800000));
+}
+
+TEST(BitFieldSetTest, WriteOneBitWithOutOfRangeValue) {
+	uint32_t actual = prjxray::bit_field_set(
+	    static_cast<uint32_t>(0x0), 23, 23, static_cast<uint32_t>(0x3));
+	EXPECT_EQ(actual, static_cast<uint32_t>(0x800000));
+}
+
+TEST(BitFieldSetTest, WriteMultipleBits) {
+	uint32_t actual = prjxray::bit_field_set(
+	    static_cast<uint32_t>(0x0), 18, 8, static_cast<uint32_t>(0x123));
+	EXPECT_EQ(actual, static_cast<uint32_t>(0x12300));
+}
+
+TEST(BitFieldSetTest, WriteMultipleBitsWithOutOfRangeValue) {
+	uint32_t actual = prjxray::bit_field_set(
+	    static_cast<uint32_t>(0x0), 18, 8, static_cast<uint32_t>(0x1234));
+	EXPECT_EQ(actual, static_cast<uint32_t>(0x23400));
+}
diff --git a/lib/database.cc b/lib/database.cc
new file mode 100644
index 0000000..6fac62d
--- /dev/null
+++ b/lib/database.cc
@@ -0,0 +1,37 @@
+#include <prjxray/database.h>
+
+#include <glob.h>
+
+#include <memory>
+
+#include <absl/strings/str_cat.h>
+
+namespace prjxray {
+
+static constexpr const char kSegbitsGlobPattern[] = "segbits_*.db";
+
+std::vector<std::unique_ptr<prjxray::SegbitsFileReader>> Database::segbits()
+    const {
+	std::vector<std::unique_ptr<prjxray::SegbitsFileReader>> segbits;
+
+	glob_t segbits_glob_results;
+	int ret = glob(absl::StrCat(db_path_, "/", kSegbitsGlobPattern).c_str(),
+	               GLOB_NOSORT | GLOB_TILDE, NULL, &segbits_glob_results);
+	if (ret < 0) {
+		return {};
+	}
+
+	for (size_t idx = 0; idx < segbits_glob_results.gl_pathc; idx++) {
+		auto this_segbit = SegbitsFileReader::InitWithFile(
+		    segbits_glob_results.gl_pathv[idx]);
+		if (this_segbit) {
+			segbits.emplace_back(std::move(this_segbit));
+		}
+	}
+
+	globfree(&segbits_glob_results);
+
+	return segbits;
+}
+
+}  // namespace prjxray
diff --git a/lib/include/prjxray/big_endian_span.h b/lib/include/prjxray/big_endian_span.h
new file mode 100644
index 0000000..683f9c3
--- /dev/null
+++ b/lib/include/prjxray/big_endian_span.h
@@ -0,0 +1,115 @@
+#ifndef PRJXRAY_LIB_BIG_ENDIAN_SPAN
+#define PRJXRAY_LIB_BIG_ENDIAN_SPAN
+
+#include <cassert>
+#include <iterator>
+
+#include <absl/types/span.h>
+
+namespace prjxray {
+
+template <typename WordType, typename ByteType>
+class BigEndianSpan {
+       public:
+	constexpr static size_t kBytesPerElement = sizeof(WordType);
+
+	using byte_type = ByteType;
+	using word_type = WordType;
+	using size_type = std::size_t;
+
+	class value_type {
+	       public:
+		operator WordType() const {
+			WordType word = 0;
+			for (size_t ii = 0; ii < kBytesPerElement; ++ii) {
+				word |= (static_cast<WordType>(bytes_[ii])
+				         << ((kBytesPerElement - 1 - ii) * 8));
+			}
+			return word;
+		}
+
+		value_type& operator=(WordType word) {
+			for (size_t ii = 0; ii < kBytesPerElement; ++ii) {
+				bytes_[ii] =
+				    ((word >>
+				      ((kBytesPerElement - 1 - ii) * 8)) &
+				     0xFF);
+			}
+			return *this;
+		}
+
+	       protected:
+		friend class BigEndianSpan<WordType, ByteType>;
+
+		value_type(absl::Span<ByteType> bytes) : bytes_(bytes){};
+
+	       private:
+		absl::Span<ByteType> bytes_;
+	};
+
+	class iterator
+	    : public std::iterator<std::input_iterator_tag, value_type> {
+	       public:
+		value_type operator*() const { return value_type(bytes_); }
+
+		bool operator==(const iterator& other) const {
+			return bytes_ == other.bytes_;
+		}
+
+		bool operator!=(const iterator& other) const {
+			return bytes_ != other.bytes_;
+		}
+
+		iterator& operator++() {
+			bytes_ = bytes_.subspan(kBytesPerElement);
+			return *this;
+		}
+
+	       protected:
+		friend class BigEndianSpan<WordType, ByteType>;
+
+		iterator(absl::Span<ByteType> bytes) : bytes_(bytes){};
+
+	       private:
+		absl::Span<ByteType> bytes_;
+	};
+
+	using pointer = value_type*;
+	using reference = value_type&;
+
+	BigEndianSpan(absl::Span<ByteType> bytes) : bytes_(bytes){};
+
+	constexpr size_type size() const noexcept {
+		return bytes_.size() / kBytesPerElement;
+	};
+
+	constexpr size_type length() const noexcept { return size(); }
+
+	constexpr bool empty() const noexcept { return size() == 0; }
+
+	value_type operator[](size_type pos) const {
+		assert(pos >= 0 && pos < size());
+		return value_type(bytes_.subspan((pos * kBytesPerElement)));
+	}
+
+	constexpr reference at(size_type pos) const {
+		return this->operator[](pos);
+	}
+
+	iterator begin() const { return iterator(bytes_); }
+	iterator end() const { return iterator({}); }
+
+       private:
+	absl::Span<ByteType> bytes_;
+};
+
+template <typename WordType, typename Container>
+BigEndianSpan<WordType, typename Container::value_type> make_big_endian_span(
+    Container& bytes) {
+	return BigEndianSpan<WordType, typename Container::value_type>(
+	    absl::Span<typename Container::value_type>(bytes));
+}
+
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_BIG_ENDIAN_SPAN
diff --git a/lib/include/prjxray/bit_ops.h b/lib/include/prjxray/bit_ops.h
new file mode 100644
index 0000000..c7d3fa8
--- /dev/null
+++ b/lib/include/prjxray/bit_ops.h
@@ -0,0 +1,48 @@
+#ifndef PRJXRAY_LIB_BIT_OPS_H
+#define PRJXRAY_LIB_BIT_OPS_H
+
+namespace prjxray {
+
+template <typename UInt>
+constexpr UInt bit_mask(const int bit) {
+	return (static_cast<UInt>(1) << bit);
+}
+
+template <typename UInt>
+constexpr UInt bit_sizeof() {
+	return sizeof(UInt) * 8;
+}
+
+template <typename UInt>
+constexpr UInt bit_all_ones() {
+	return ~static_cast<UInt>(0);
+}
+
+template <typename UInt>
+constexpr UInt bit_mask_range(const int top_bit, const int bottom_bit) {
+	return ((bit_all_ones<UInt>() >> (bit_sizeof<UInt>() - 1 - top_bit)) &
+	        (bit_all_ones<UInt>() - bit_mask<UInt>(bottom_bit) +
+	         static_cast<UInt>(1)));
+}
+
+template <typename UInt>
+constexpr UInt bit_field_get(UInt value,
+                             const int top_bit,
+                             const int bottom_bit) {
+	return (value & bit_mask_range<UInt>(top_bit, bottom_bit)) >>
+	       bottom_bit;
+}
+
+template <typename UInt, typename ValueType>
+constexpr UInt bit_field_set(const UInt reg_value,
+                             const int top_bit,
+                             const int bottom_bit,
+                             const ValueType field_value) {
+	return ((reg_value & ~bit_mask_range<UInt>(top_bit, bottom_bit)) |
+	        ((static_cast<UInt>(field_value) << bottom_bit) &
+	         bit_mask_range<UInt>(top_bit, bottom_bit)));
+}
+
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_BIT_OPS_H
diff --git a/lib/include/prjxray/database.h b/lib/include/prjxray/database.h
new file mode 100644
index 0000000..76b1093
--- /dev/null
+++ b/lib/include/prjxray/database.h
@@ -0,0 +1,24 @@
+#ifndef PRJXRAY_LIB_DATABASE_H
+#define PRJXRAY_LIB_DATABASE_H
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <prjxray/segbits_file_reader.h>
+
+namespace prjxray {
+
+class Database {
+       public:
+	Database(const std::string& path) : db_path_(path) {}
+
+	std::vector<std::unique_ptr<SegbitsFileReader>> segbits() const;
+
+       private:
+	std::string db_path_;
+};
+
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_DATABASE_H
diff --git a/lib/include/prjxray/memory_mapped_file.h b/lib/include/prjxray/memory_mapped_file.h
new file mode 100644
index 0000000..9a944b7
--- /dev/null
+++ b/lib/include/prjxray/memory_mapped_file.h
@@ -0,0 +1,34 @@
+#ifndef PRJXRAY_LIB_MEMORY_MAPPED_FILE
+#define PRJXRAY_LIB_MEMORY_MAPPED_FILE
+
+#include <memory>
+#include <string>
+
+#include <absl/types/span.h>
+
+namespace prjxray {
+
+class MemoryMappedFile {
+       public:
+	~MemoryMappedFile();
+
+	static std::unique_ptr<MemoryMappedFile> InitWithFile(
+	    const std::string& path);
+
+	void* const data() const { return data_; }
+	const size_t size() const { return size_; }
+
+	absl::Span<uint8_t> as_bytes() const {
+		return {static_cast<uint8_t*>(data_), size_};
+	}
+
+       private:
+	MemoryMappedFile(void* data, size_t size) : data_(data), size_(size){};
+
+	void* data_;
+	size_t size_;
+};
+
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_MEMORY_MAPPED_FILE
diff --git a/lib/include/prjxray/segbits_file_reader.h b/lib/include/prjxray/segbits_file_reader.h
new file mode 100644
index 0000000..19c4f51
--- /dev/null
+++ b/lib/include/prjxray/segbits_file_reader.h
@@ -0,0 +1,69 @@
+#ifndef PRJXRAY_LIB_SEGBITS_FILE_READER_H
+#define PRJXRAY_LIB_SEGBITS_FILE_READER_H
+
+#include <iterator>
+#include <memory>
+
+#include <absl/strings/string_view.h>
+#include <prjxray/memory_mapped_file.h>
+
+namespace prjxray {
+
+class SegbitsFileReader {
+       public:
+	class value_type {
+	       public:
+		absl::string_view tag() const { return tag_; }
+		absl::string_view bit() const { return bit_; }
+
+	       private:
+		friend SegbitsFileReader;
+
+		value_type(const absl::string_view& view);
+
+		absl::string_view tag_;
+		absl::string_view bit_;
+	};
+
+	class iterator
+	    : public std::iterator<std::input_iterator_tag, value_type> {
+	       public:
+		iterator& operator++();
+
+		bool operator==(iterator other) const {
+			return view_ == other.view_;
+		}
+		bool operator!=(iterator other) const {
+			return !(*this == other);
+		}
+
+		const value_type& operator*() const { return value_; }
+		const value_type* operator->() const { return &value_; }
+
+	       protected:
+		explicit iterator(absl::string_view view)
+		    : view_(view), value_(view) {}
+
+	       private:
+		friend SegbitsFileReader;
+
+		absl::string_view view_;
+		value_type value_;
+	};
+
+	static std::unique_ptr<SegbitsFileReader> InitWithFile(
+	    const std::string& path);
+
+	iterator begin();
+	iterator end();
+
+       private:
+	SegbitsFileReader(std::unique_ptr<MemoryMappedFile>&& mapped_file)
+	    : mapped_file_(std::move(mapped_file)){};
+
+	std::unique_ptr<MemoryMappedFile> mapped_file_;
+};
+
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_SEGBITS_FILE_READER_H
diff --git a/lib/include/prjxray/xilinx/architectures.h b/lib/include/prjxray/xilinx/architectures.h
new file mode 100644
index 0000000..cdf992e
--- /dev/null
+++ b/lib/include/prjxray/xilinx/architectures.h
@@ -0,0 +1,92 @@
+#ifndef PRJXRAY_LIB_XILINX_ARCHITECTURES_H_
+#define PRJXRAY_LIB_XILINX_ARCHITECTURES_H_
+
+#include <absl/types/variant.h>
+#include <memory>
+#include <vector>
+
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+#include <prjxray/xilinx/spartan6/part.h>
+#include <prjxray/xilinx/xc7series/frame_address.h>
+#include <prjxray/xilinx/xc7series/part.h>
+
+namespace prjxray {
+namespace xilinx {
+
+class Spartan6;
+class Series7;
+class UltraScale;
+class UltraScalePlus;
+
+class Architecture {
+       public:
+	using Container =
+	    absl::variant<Series7, UltraScale, UltraScalePlus, Spartan6>;
+	Architecture(const std::string& name) : name_(name) {}
+	const std::string& name() const { return name_; }
+	virtual ~Architecture() {}
+
+       private:
+	const std::string name_;
+};
+
+class Spartan6 : public Architecture {
+       public:
+	using ConfRegType = Spartan6ConfigurationRegister;
+	using Part = spartan6::Part;
+	using ConfigurationPackage =
+	    std::vector<std::unique_ptr<ConfigurationPacket<ConfRegType>>>;
+	using FrameAddress = spartan6::FrameAddress;
+	using WordType = uint16_t;
+	Spartan6() : Architecture("Spartan6") {}
+	static constexpr int words_per_frame = 65;
+};
+
+class Series7 : public Architecture {
+       public:
+	using ConfRegType = Series7ConfigurationRegister;
+	using Part = xc7series::Part;
+	using ConfigurationPackage =
+	    std::vector<std::unique_ptr<ConfigurationPacket<ConfRegType>>>;
+	using FrameAddress = xc7series::FrameAddress;
+	using WordType = uint32_t;
+	Series7() : Architecture("Series7") {}
+	Series7(const std::string& name) : Architecture(name) {}
+	static constexpr int words_per_frame = 101;
+};
+
+class UltraScalePlus : public Series7 {
+       public:
+	UltraScalePlus() : Series7("UltraScalePlus") {}
+	static constexpr int words_per_frame = 93;
+};
+
+class UltraScale : public Series7 {
+       public:
+	UltraScale() : Series7("UltraScale") {}
+	static constexpr int words_per_frame = 123;
+};
+
+class ArchitectureFactory {
+       public:
+	static Architecture::Container create_architecture(
+	    const std::string& arch) {
+		if (arch == "Spartan6") {
+			return Spartan6();
+		} else if (arch == "Series7") {
+			return Series7();
+		} else if (arch == "UltraScale") {
+			return UltraScale();
+		} else if (arch == "UltraScalePlus") {
+			return UltraScalePlus();
+		} else {
+			return Architecture::Container();
+		}
+	}
+};
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_ARCHITECTURES_H_
diff --git a/lib/include/prjxray/xilinx/bitstream_reader.h b/lib/include/prjxray/xilinx/bitstream_reader.h
new file mode 100644
index 0000000..5464a52
--- /dev/null
+++ b/lib/include/prjxray/xilinx/bitstream_reader.h
@@ -0,0 +1,181 @@
+#ifndef PRJXRAY_LIB_XILINX_BITSTREAM_READER_H
+#define PRJXRAY_LIB_XILINX_BITSTREAM_READER_H
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+#include <absl/types/span.h>
+
+#include <prjxray/big_endian_span.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/configuration_packet.h>
+
+namespace prjxray {
+namespace xilinx {
+
+// Constructs a collection of 32-bit big-endian words from a bitstream file.
+// Provides an iterator over the configuration packets.
+template <typename ArchType>
+class BitstreamReader {
+       public:
+	using value_type = ConfigurationPacket<typename ArchType::ConfRegType>;
+
+	// Implements an iterator over the words grouped in configuration
+	// packets.
+	class iterator
+	    : public std::iterator<std::input_iterator_tag, value_type> {
+	       public:
+		iterator& operator++();
+
+		bool operator==(const iterator& other) const;
+		bool operator!=(const iterator& other) const;
+
+		const value_type& operator*() const;
+		const value_type* operator->() const;
+
+	       protected:
+		explicit iterator(absl::Span<uint32_t> words);
+
+	       private:
+		friend BitstreamReader;
+
+		typename value_type::ParseResult parse_result_;
+		absl::Span<uint32_t> words_;
+	};
+
+	// Construct a reader from a collection of 32-bit, big-endian words.
+	// Assumes that any sync word has already been removed.
+	BitstreamReader(std::vector<uint32_t>&& words)
+	    : words_(std::move(words)) {}
+
+	BitstreamReader() {}
+	size_t size() { return words_.size(); }
+
+	// Construct a `BitstreamReader` from a Container of bytes.
+	// Any bytes preceding an initial sync word are ignored.
+	template <typename T>
+	static absl::optional<BitstreamReader<ArchType>> InitWithBytes(
+	    T bitstream);
+
+	const std::vector<uint32_t>& words() { return words_; };
+
+	// Returns an iterator that yields `ConfigurationPackets`
+	// as read from the bitstream.
+	iterator begin();
+	iterator end();
+
+       private:
+	static std::array<uint8_t, 4> kSyncWord;
+
+	std::vector<uint32_t> words_;
+};
+
+template <typename ArchType>
+template <typename T>
+absl::optional<BitstreamReader<ArchType>>
+BitstreamReader<ArchType>::InitWithBytes(T bitstream) {
+	// If this is really a Xilinx bitstream, there will be a sync
+	// word somewhere toward the beginning.
+	auto sync_pos = std::search(bitstream.begin(), bitstream.end(),
+	                            kSyncWord.begin(), kSyncWord.end());
+	if (sync_pos == bitstream.end()) {
+		return absl::optional<BitstreamReader<ArchType>>();
+	}
+	sync_pos += kSyncWord.size();
+
+	// Wrap the provided container in a span that strips off the preamble.
+	absl::Span<typename T::value_type> bitstream_span(bitstream);
+	auto config_packets =
+	    bitstream_span.subspan(sync_pos - bitstream.begin());
+
+	// Convert the bytes into 32-bit or 16-bit in case of Spartan6,
+	// big-endian words.
+	auto big_endian_reader =
+	    make_big_endian_span<typename ArchType::WordType>(config_packets);
+	std::vector<uint32_t> words{big_endian_reader.begin(),
+	                            big_endian_reader.end()};
+
+	return BitstreamReader<ArchType>(std::move(words));
+}
+
+// Sync word as specified in UG470 page 81
+template <typename ArchType>
+std::array<uint8_t, 4> BitstreamReader<ArchType>::kSyncWord{0xAA, 0x99, 0x55,
+                                                            0x66};
+
+template <typename ArchType>
+typename BitstreamReader<ArchType>::iterator
+BitstreamReader<ArchType>::begin() {
+	return iterator(absl::MakeSpan(words_));
+}
+
+template <typename ArchType>
+typename BitstreamReader<ArchType>::iterator BitstreamReader<ArchType>::end() {
+	return iterator({});
+}
+
+template <typename ArchType>
+BitstreamReader<ArchType>::iterator::iterator(absl::Span<uint32_t> words) {
+	parse_result_.first = words;
+	parse_result_.second = {};
+	++(*this);
+}
+
+template <typename ArchType>
+typename BitstreamReader<ArchType>::iterator&
+    BitstreamReader<ArchType>::iterator::operator++() {
+	do {
+		auto new_result =
+		    ConfigurationPacket<typename ArchType::ConfRegType>::
+		        InitWithWords(parse_result_.first,
+		                      parse_result_.second.has_value()
+		                          ? parse_result_.second.operator->()
+		                          : nullptr);
+
+		// If the a valid header is being found but there are
+		// insufficient words to yield a packet, consider it the end.
+		if (new_result.first == parse_result_.first) {
+			words_ = absl::Span<uint32_t>();
+			break;
+		}
+
+		words_ = parse_result_.first;
+		parse_result_ = new_result;
+	} while (!parse_result_.first.empty() && !parse_result_.second);
+
+	if (!parse_result_.second) {
+		words_ = absl::Span<uint32_t>();
+	}
+
+	return *this;
+}
+
+template <typename ArchType>
+bool BitstreamReader<ArchType>::iterator::operator==(
+    const iterator& other) const {
+	return words_ == other.words_;
+}
+
+template <typename ArchType>
+bool BitstreamReader<ArchType>::iterator::operator!=(
+    const iterator& other) const {
+	return !(*this == other);
+}
+
+template <typename ArchType>
+const typename BitstreamReader<ArchType>::value_type&
+    BitstreamReader<ArchType>::iterator::operator*() const {
+	return *(parse_result_.second);
+}
+
+template <typename ArchType>
+const typename BitstreamReader<ArchType>::value_type*
+    BitstreamReader<ArchType>::iterator::operator->() const {
+	return parse_result_.second.operator->();
+}
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_BITSTREAM_READER_H
diff --git a/lib/include/prjxray/xilinx/bitstream_writer.h b/lib/include/prjxray/xilinx/bitstream_writer.h
new file mode 100644
index 0000000..09d69f4
--- /dev/null
+++ b/lib/include/prjxray/xilinx/bitstream_writer.h
@@ -0,0 +1,424 @@
+/*
+ * Takes in a collection of ConfigurationPacket and writes them to specified
+ * file This includes the following: -Bus auto detection -Sync Word -FPGA
+ * configuration
+ */
+#ifndef PRJXRAY_LIB_XILINX_BITSTREAM_WRITER_H
+#define PRJXRAY_LIB_XILINX_BITSTREAM_WRITER_H
+
+#include <algorithm>
+#include <fstream>
+#include <memory>
+#include <vector>
+
+#include <absl/strings/str_cat.h>
+#include <absl/time/clock.h>
+#include <absl/time/time.h>
+#include <absl/types/optional.h>
+#include <absl/types/span.h>
+
+#include <prjxray/big_endian_span.h>
+#include <prjxray/xilinx/configuration_packet.h>
+
+namespace prjxray {
+namespace xilinx {
+
+uint32_t packet2header(
+    const ConfigurationPacket<Series7ConfigurationRegister>& packet);
+uint32_t packet2header(
+    const ConfigurationPacket<Spartan6ConfigurationRegister>& packet);
+// Writes out the complete Xilinx bitstream including
+// header, sync word and configuration sequence.
+template <typename ArchType>
+class BitstreamWriter {
+       public:
+	typedef std::vector<uint32_t> header_t;
+	typedef std::vector<std::unique_ptr<
+	    ConfigurationPacket<typename ArchType::ConfRegType>>>
+	    packets_t;
+	typedef std::vector<uint8_t> BitstreamHeader;
+	// Only defined if a packet exists
+	typedef absl::optional<absl::Span<const uint32_t>> op_data_t;
+	typedef absl::Span<const uint32_t>::iterator data_iterator_t;
+	using itr_value_type = uint32_t;
+
+	class packet_iterator
+	    : public std::iterator<std::input_iterator_tag, itr_value_type> {
+	       public:
+		packet_iterator& operator++();
+
+		bool operator==(const packet_iterator& other) const;
+		bool operator!=(const packet_iterator& other) const;
+
+		const itr_value_type operator*() const;
+		const itr_value_type operator->() const;
+
+		typedef enum {
+			STATE_HEADER = 1,
+			STATE_DATA = 2,
+			STATE_END = 3,
+		} state_t;
+
+	       protected:
+		explicit packet_iterator(
+		    const ConfigurationPacket<typename ArchType::ConfRegType>*
+		        packet,
+		    state_t state,
+		    data_iterator_t itr_data);
+
+	       private:
+		friend iterator;
+		friend BitstreamWriter;
+
+		// Data iterators
+		// First over the fixed header, then the configuration data
+		state_t state_;
+		// Over packet.data()
+		data_iterator_t itr_data_;
+
+		const ConfigurationPacket<typename ArchType::ConfRegType>*
+		    packet_;
+	};
+
+	class iterator
+	    : public std::iterator<std::input_iterator_tag, itr_value_type> {
+	       public:
+		iterator& operator++();
+
+		bool operator==(const iterator& other) const;
+		bool operator!=(const iterator& other) const;
+
+		const itr_value_type operator*() const;
+		const itr_value_type operator->() const;
+
+		packet_iterator packet_begin();
+		packet_iterator packet_end();
+
+	       protected:
+		explicit iterator(
+		    header_t::iterator itr_header,
+		    const packets_t& packets,
+		    typename packets_t::const_iterator itr_packets,
+		    absl::optional<packet_iterator> op_itr_packet);
+
+	       private:
+		friend BitstreamWriter;
+		// Data iterators
+		// First over the fixed header, then the configuration data
+		header_t::iterator itr_header_;
+		const packets_t& packets_;
+		typename packets_t::const_iterator itr_packets_;
+		absl::optional<packet_iterator> op_itr_packet_;
+	};
+
+	BitstreamWriter(const packets_t& packets) : packets_(packets) {}
+
+	// Writes out the complete bitstream for Xilinx FPGA based on
+	// the Configuration Package which holds the complete programming
+	// sequence.
+	int writeBitstream(
+	    const typename ArchType::ConfigurationPackage& packets,
+	    const std::string& part_name,
+	    const std::string& frames_file,
+	    const std::string& generator_name,
+	    const std::string& output_file);
+	iterator begin();
+	iterator end();
+
+       private:
+	static header_t header_;
+	const packets_t& packets_;
+
+	// Creates a Xilinx bit header which is mostly a
+	// Tag-Length-Value(TLV) format documented here:
+	// http://www.fpga-faq.com/FAQ_Pages/0026_Tell_me_about_bit_files.htm
+	BitstreamHeader create_header(const std::string& part_name,
+	                              const std::string& frames_file_name,
+	                              const std::string& generator_name);
+};
+
+template <typename ArchType>
+int BitstreamWriter<ArchType>::writeBitstream(
+    const typename ArchType::ConfigurationPackage& packets,
+    const std::string& part_name,
+    const std::string& frames_file,
+    const std::string& generator_name,
+    const std::string& output_file) {
+	std::ofstream out_file(output_file, std::ofstream::binary);
+	if (!out_file) {
+		std::cerr << "Unable to open file for writting: " << output_file
+		          << std::endl;
+		return 1;
+	}
+
+	BitstreamHeader bit_header(
+	    create_header(part_name, frames_file, generator_name));
+	out_file.write(reinterpret_cast<const char*>(bit_header.data()),
+	               bit_header.size());
+
+	auto end_of_header_pos = out_file.tellp();
+	auto header_data_length_pos =
+	    end_of_header_pos - static_cast<std::ofstream::off_type>(4);
+
+	BitstreamWriter<ArchType> out_bitstream_writer(packets);
+	int bytes_per_word = sizeof(typename ArchType::WordType);
+	for (uint32_t word : out_bitstream_writer) {
+		for (int byte = bytes_per_word - 1; byte >= 0; byte--) {
+			out_file.put((word >> (byte * 8)) & 0xFF);
+		}
+	}
+
+	uint32_t length_of_data = out_file.tellp() - end_of_header_pos;
+
+	out_file.seekp(header_data_length_pos);
+	for (int byte = 3; byte >= 0; byte--) {
+		out_file.put((length_of_data >> (byte * 8)) & 0xFF);
+	}
+	return 0;
+}
+
+template <typename ArchType>
+typename BitstreamWriter<ArchType>::BitstreamHeader
+BitstreamWriter<ArchType>::create_header(const std::string& part_name,
+                                         const std::string& frames_file_name,
+                                         const std::string& generator_name) {
+	// Sync header
+	BitstreamHeader bit_header{0x0,  0x9,  0x0f, 0xf0, 0x0f, 0xf0, 0x0f,
+	                           0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x01, 'a'};
+	auto build_source =
+	    absl::StrCat(frames_file_name, ";Generator=" + generator_name);
+	bit_header.push_back(
+	    static_cast<uint8_t>((build_source.size() + 1) >> 8));
+	bit_header.push_back(static_cast<uint8_t>(build_source.size() + 1));
+	bit_header.insert(bit_header.end(), build_source.begin(),
+	                  build_source.end());
+	bit_header.push_back(0x0);
+
+	// Source file.
+	bit_header.push_back('b');
+	bit_header.push_back(static_cast<uint8_t>((part_name.size() + 1) >> 8));
+	bit_header.push_back(static_cast<uint8_t>(part_name.size() + 1));
+	bit_header.insert(bit_header.end(), part_name.begin(), part_name.end());
+	bit_header.push_back(0x0);
+
+	// Build timestamp.
+	auto build_time = absl::Now();
+	auto build_date_string =
+	    absl::FormatTime("%E4Y/%m/%d", build_time, absl::UTCTimeZone());
+	auto build_time_string =
+	    absl::FormatTime("%H:%M:%S", build_time, absl::UTCTimeZone());
+
+	bit_header.push_back('c');
+	bit_header.push_back(
+	    static_cast<uint8_t>((build_date_string.size() + 1) >> 8));
+	bit_header.push_back(
+	    static_cast<uint8_t>(build_date_string.size() + 1));
+	bit_header.insert(bit_header.end(), build_date_string.begin(),
+	                  build_date_string.end());
+	bit_header.push_back(0x0);
+
+	bit_header.push_back('d');
+	bit_header.push_back(
+	    static_cast<uint8_t>((build_time_string.size() + 1) >> 8));
+	bit_header.push_back(
+	    static_cast<uint8_t>(build_time_string.size() + 1));
+	bit_header.insert(bit_header.end(), build_time_string.begin(),
+	                  build_time_string.end());
+	bit_header.push_back(0x0);
+	bit_header.insert(bit_header.end(), {'e', 0x0, 0x0, 0x0, 0x0});
+	return bit_header;
+}
+
+template <typename ArchType>
+typename BitstreamWriter<ArchType>::packet_iterator
+BitstreamWriter<ArchType>::iterator::packet_begin() {
+	// itr_packets = packets.begin();
+	const ConfigurationPacket<typename ArchType::ConfRegType>& packet =
+	    **itr_packets_;
+
+	return BitstreamWriter::packet_iterator(
+	    &packet, BitstreamWriter::packet_iterator::STATE_HEADER,
+	    packet.data().begin());
+}
+
+template <typename ArchType>
+typename BitstreamWriter<ArchType>::packet_iterator
+BitstreamWriter<ArchType>::iterator::packet_end() {
+	const ConfigurationPacket<typename ArchType::ConfRegType>& packet =
+	    **itr_packets_;
+
+	return BitstreamWriter<ArchType>::packet_iterator(
+	    &packet, BitstreamWriter::packet_iterator::STATE_END,
+	    // Essentially ignored
+	    packet.data().end());
+}
+
+template <typename ArchType>
+BitstreamWriter<ArchType>::packet_iterator::packet_iterator(
+    const ConfigurationPacket<typename ArchType::ConfRegType>* packet,
+    state_t state,
+    data_iterator_t itr_data)
+    : state_(state), itr_data_(itr_data), packet_(packet) {}
+
+template <typename ArchType>
+typename BitstreamWriter<ArchType>::packet_iterator&
+    BitstreamWriter<ArchType>::packet_iterator::operator++() {
+	if (state_ == STATE_HEADER) {
+		itr_data_ = packet_->data().begin();
+		if (itr_data_ == packet_->data().end()) {
+			state_ = STATE_END;
+		} else {
+			state_ = STATE_DATA;
+		}
+	} else if (state_ == STATE_DATA) {
+		/// Advance. data must be valid while not at end
+		itr_data_++;
+		// Reached this end of this packet?
+		if (itr_data_ == packet_->data().end()) {
+			state_ = STATE_END;
+		}
+	}
+	return *this;
+}
+
+template <typename ArchType>
+bool BitstreamWriter<ArchType>::packet_iterator::operator==(
+    const packet_iterator& other) const {
+	return state_ == other.state_ && itr_data_ == other.itr_data_;
+}
+
+template <typename ArchType>
+bool BitstreamWriter<ArchType>::packet_iterator::operator!=(
+    const packet_iterator& other) const {
+	return !(*this == other);
+}
+
+template <typename ArchType>
+const typename BitstreamWriter<ArchType>::itr_value_type
+    BitstreamWriter<ArchType>::packet_iterator::operator*() const {
+	if (state_ == STATE_HEADER) {
+		return packet2header(*packet_);
+	} else if (state_ == STATE_DATA) {
+		return *itr_data_;
+	}
+	return 0;  // XXX: assert or something?
+}
+
+template <typename ArchType>
+const typename BitstreamWriter<ArchType>::itr_value_type
+    BitstreamWriter<ArchType>::packet_iterator::operator->() const {
+	return *(*this);
+}
+
+/**************************************************
+ * BitstreamWriter::iterator
+ *************************************************/
+
+template <typename ArchType>
+typename BitstreamWriter<ArchType>::iterator
+BitstreamWriter<ArchType>::begin() {
+	typename packets_t::const_iterator itr_packets = packets_.begin();
+	absl::optional<packet_iterator> op_packet_itr;
+
+	// May have no packets
+	if (itr_packets != packets_.end()) {
+		// op_packet_itr = packet_begin();
+		// FIXME: de-duplicate this
+		const ConfigurationPacket<typename ArchType::ConfRegType>&
+		    packet = **itr_packets;
+		packet_iterator packet_itr =
+		    packet_iterator(&packet, packet_iterator::STATE_HEADER,
+		                    packet.data().begin());
+		op_packet_itr = packet_itr;
+	}
+	return iterator(header_.begin(), packets_, itr_packets, op_packet_itr);
+}
+
+template <typename ArchType>
+typename BitstreamWriter<ArchType>::iterator BitstreamWriter<ArchType>::end() {
+	return iterator(header_.end(), packets_, packets_.end(),
+	                absl::optional<packet_iterator>());
+}
+
+template <typename ArchType>
+BitstreamWriter<ArchType>::iterator::iterator(
+    header_t::iterator itr_header,
+    const typename BitstreamWriter<ArchType>::packets_t& packets,
+    typename BitstreamWriter<ArchType>::packets_t::const_iterator itr_packets,
+    absl::optional<packet_iterator> itr_packet)
+    : itr_header_(itr_header),
+      packets_(packets),
+      itr_packets_(itr_packets),
+      op_itr_packet_(itr_packet) {}
+
+template <typename ArchType>
+typename BitstreamWriter<ArchType>::iterator&
+    BitstreamWriter<ArchType>::iterator::operator++() {
+	// Still generating header?
+	if (itr_header_ != header_.end()) {
+		itr_header_++;
+		// Finished header?
+		// Will advance to initialized itr_packets value
+		// XXX: maybe should just overwrite here
+		if (itr_header_ == header_.end()) {
+			itr_packets_ = packets_.begin();
+			if (itr_packets_ != packets_.end()) {
+				op_itr_packet_ = packet_begin();
+			}
+		}
+		// Then somewhere in packets
+	} else {
+		// We are either at end() in which case this operation is
+		// invalid Or there is a packet in progress packet in progress?
+		// Advance it
+		++(*op_itr_packet_);
+		// Done with this packet?
+		if (*op_itr_packet_ == packet_end()) {
+			itr_packets_++;
+			if (itr_packets_ == packets_.end()) {
+				// we are at the very end
+				// invalidate data to be neat
+				op_itr_packet_.reset();
+			} else {
+				op_itr_packet_ = packet_begin();
+			}
+		}
+	}
+	return *this;
+}
+
+template <typename ArchType>
+bool BitstreamWriter<ArchType>::iterator::operator==(
+    const iterator& other) const {
+	return itr_header_ == other.itr_header_ &&
+	       itr_packets_ == other.itr_packets_ &&
+	       op_itr_packet_ == other.op_itr_packet_;
+}
+
+template <typename ArchType>
+bool BitstreamWriter<ArchType>::iterator::operator!=(
+    const iterator& other) const {
+	return !(*this == other);
+}
+
+template <typename ArchType>
+const typename BitstreamWriter<ArchType>::itr_value_type
+    BitstreamWriter<ArchType>::iterator::operator*() const {
+	if (itr_header_ != header_.end()) {
+		return *itr_header_;
+	} else {
+		// Iterating over packets, get data from current packet position
+		return *(*op_itr_packet_);
+	}
+}
+
+template <typename ArchType>
+const typename BitstreamWriter<ArchType>::itr_value_type
+    BitstreamWriter<ArchType>::iterator::operator->() const {
+	return *(*this);
+}
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_BITSTREAM_WRITER_H
diff --git a/lib/include/prjxray/xilinx/configuration.h b/lib/include/prjxray/xilinx/configuration.h
new file mode 100644
index 0000000..6236345
--- /dev/null
+++ b/lib/include/prjxray/xilinx/configuration.h
@@ -0,0 +1,355 @@
+#ifndef PRJXRAY_LIB_XILINX_CONFIGURATION_H_
+#define PRJXRAY_LIB_XILINX_CONFIGURATION_H_
+
+#include <map>
+#include <type_traits>
+
+#include <absl/types/span.h>
+
+#include <prjxray/bit_ops.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/frames.h>
+
+namespace prjxray {
+namespace xilinx {
+
+template <typename ArchType>
+class Configuration {
+       public:
+	using FrameMap = std::map<typename ArchType::FrameAddress,
+	                          absl::Span<const uint32_t>>;
+	using PacketData = std::vector<uint32_t>;
+
+	// Returns a configuration, i.e. collection of frame addresses
+	// and corresponding data from a collection of configuration packets.
+	template <typename Collection>
+	static absl::optional<Configuration<ArchType>> InitWithPackets(
+	    const typename ArchType::Part& part,
+	    Collection& packets);
+
+	// Creates the complete configuration package which is later on
+	// used by the bitstream writer to generate the bitstream file.
+	// The pacakge forms a sequence suitable for Xilinx devices.
+	// The programming sequence for Series-7 is taken from
+	// https://www.kc8apf.net/2018/05/unpacking-xilinx-7-series-bitstreams-part-2/
+	static void createConfigurationPackage(
+	    typename ArchType::ConfigurationPackage& out_packets,
+	    const PacketData& packet_data,
+	    absl::optional<typename ArchType::Part>& part);
+
+	// Returns the payload for a type 2 packet
+	// which allows for bigger payload compared to type 1.
+	static PacketData createType2ConfigurationPacketData(
+	    const typename Frames<ArchType>::Frames2Data& frames,
+	    absl::optional<typename ArchType::Part>& part);
+
+	Configuration(const typename ArchType::Part& part,
+	              std::map<typename ArchType::FrameAddress,
+	                       std::vector<uint32_t>>* frames)
+	    : part_(part) {
+		for (auto& frame : *frames) {
+			frames_[frame.first] =
+			    absl::Span<const uint32_t>(frame.second);
+		}
+	}
+
+	Configuration(const typename ArchType::Part& part,
+	              const FrameMap& frames)
+	    : part_(part), frames_(std::move(frames)) {}
+
+	const typename ArchType::Part& part() const { return part_; }
+	const FrameMap& frames() const { return frames_; }
+
+       private:
+	typename ArchType::Part part_;
+	FrameMap frames_;
+};
+
+template <typename ArchType>
+typename Configuration<ArchType>::PacketData
+Configuration<ArchType>::createType2ConfigurationPacketData(
+    const typename Frames<ArchType>::Frames2Data& frames,
+    absl::optional<typename ArchType::Part>& part) {
+	PacketData packet_data;
+	// Certain configuration frames blocks are separated by Zero Frames,
+	// i.e. frames with words with all zeroes. For Series-7, US and US+
+	// there zero frames separator consists of two frames.
+	static const int kZeroFramesSeparatorWords =
+	    ArchType::words_per_frame * 2;
+	for (auto& frame : frames) {
+		std::copy(frame.second.begin(), frame.second.end(),
+		          std::back_inserter(packet_data));
+
+		auto next_address = part->GetNextFrameAddress(frame.first);
+		if (next_address &&
+		    (next_address->block_type() != frame.first.block_type() ||
+		     next_address->is_bottom_half_rows() !=
+		         frame.first.is_bottom_half_rows() ||
+		     next_address->row() != frame.first.row())) {
+			packet_data.insert(packet_data.end(),
+			                   kZeroFramesSeparatorWords, 0);
+		}
+	}
+	packet_data.insert(packet_data.end(), kZeroFramesSeparatorWords, 0);
+	return packet_data;
+}
+
+template <>
+template <typename Collection>
+absl::optional<Configuration<Spartan6>>
+Configuration<Spartan6>::InitWithPackets(const typename Spartan6::Part& part,
+                                         Collection& packets) {
+	using ArchType = Spartan6;
+	// Registers that can be directly written to.
+	uint32_t command_register = 0;
+	uint32_t frame_address_register = 0;
+	uint32_t mask_register = 0;
+	__attribute__((unused)) uint32_t ctl1_register = 0;
+
+	// Internal state machine for writes.
+	bool start_new_write = false;
+	typename ArchType::FrameAddress current_frame_address = 0;
+
+	Configuration<ArchType>::FrameMap frames;
+	for (auto packet : packets) {
+		if (packet.opcode() !=
+		    ConfigurationPacket<
+		        typename ArchType::ConfRegType>::Opcode::Write) {
+			continue;
+		}
+
+		switch (packet.address()) {
+			case ArchType::ConfRegType::MASK:
+				if (packet.data().size() < 1)
+					continue;
+				mask_register = packet.data()[0];
+				break;
+			case ArchType::ConfRegType::CTL:
+				if (packet.data().size() < 1)
+					continue;
+				ctl1_register =
+				    packet.data()[0] & mask_register;
+				break;
+			case ArchType::ConfRegType::CMD:
+				if (packet.data().size() < 1)
+					continue;
+				command_register = packet.data()[0];
+				// Writes to CMD trigger an immediate action. In
+				// the case of WCFG, that is just setting a flag
+				// for the next FDRI.
+				if (command_register == 0x1) {
+					start_new_write = true;
+				}
+				break;
+			case ArchType::ConfRegType::IDCODE: {
+				// This really should be a two-word write.
+				if (packet.data().size() < 2)
+					continue;
+
+				// If the IDCODE doesn't match our expected
+				// part, consider the bitstream invalid.
+				uint32_t idcode = (packet.data()[0] << 16) |
+				                  (packet.data()[1]);
+				if (idcode != part.idcode()) {
+					return {};
+				}
+				break;
+			}
+			// UG380 describes the frame addressing scheme where two
+			// words for FAR_MAJ update FAR_MAJ anda FAR_MIN -
+			// FAR_MAJ comes first
+			case ArchType::ConfRegType::FAR_MAJ: {
+				size_t packet_size = packet.data().size();
+				assert(packet_size < 3);
+				if (packet_size < 1) {
+					continue;
+				} else if (packet_size < 2) {
+					frame_address_register =
+					    (packet.data()[0] & 0xFFFF) << 16;
+				} else {
+					frame_address_register =
+					    ((packet.data()[0] & 0xFFFF)
+					     << 16) |
+					    (packet.data()[1] & 0xFFFF);
+				}
+				break;
+			}
+			case ArchType::ConfRegType::FAR_MIN:
+				// This really should be a one-word write.
+				if (packet.data().size() < 1)
+					continue;
+
+				frame_address_register |=
+				    packet.data()[0] & 0x3FF;
+
+				break;
+			case ArchType::ConfRegType::FDRI: {
+				if (start_new_write) {
+					current_frame_address =
+					    frame_address_register;
+					start_new_write = false;
+				}
+
+				// Spartan6 frames are 65-words long.  Writes
+				// to this register can be multiples of that to
+				// do auto-incrementing block writes.
+
+				for (size_t ii = 0; ii < packet.data().size();
+				     ii += ArchType::words_per_frame) {
+					frames[current_frame_address] =
+					    packet.data().subspan(
+					        ii, ArchType::words_per_frame);
+
+					auto next_address =
+					    part.GetNextFrameAddress(
+					        current_frame_address);
+					if (!next_address)
+						break;
+
+					current_frame_address = *next_address;
+				}
+				break;
+			}
+			default:
+				break;
+		}
+	}
+
+	return Configuration(part, frames);
+}
+
+template <typename ArchType>
+template <typename Collection>
+absl::optional<Configuration<ArchType>>
+Configuration<ArchType>::InitWithPackets(const typename ArchType::Part& part,
+                                         Collection& packets) {
+	// Registers that can be directly written to.
+	uint32_t command_register = 0;
+	uint32_t frame_address_register = 0;
+	uint32_t mask_register = 0;
+	uint32_t ctl1_register = 0;
+
+	// Internal state machine for writes.
+	bool start_new_write = false;
+	typename ArchType::FrameAddress current_frame_address = 0;
+
+	Configuration<ArchType>::FrameMap frames;
+	for (auto packet : packets) {
+		if (packet.opcode() !=
+		    ConfigurationPacket<
+		        typename ArchType::ConfRegType>::Opcode::Write) {
+			continue;
+		}
+
+		switch (packet.address()) {
+			case ArchType::ConfRegType::MASK:
+				if (packet.data().size() < 1)
+					continue;
+				mask_register = packet.data()[0];
+				break;
+			case ArchType::ConfRegType::CTL1:
+				if (packet.data().size() < 1)
+					continue;
+				ctl1_register =
+				    packet.data()[0] & mask_register;
+				break;
+			case ArchType::ConfRegType::CMD:
+				if (packet.data().size() < 1)
+					continue;
+				command_register = packet.data()[0];
+				// Writes to CMD trigger an immediate action. In
+				// the case of WCFG, that is just setting a flag
+				// for the next FDIR.
+				if (command_register == 0x1) {
+					start_new_write = true;
+				}
+				break;
+			case ArchType::ConfRegType::IDCODE:
+				// This really should be a one-word write.
+				if (packet.data().size() < 1)
+					continue;
+
+				// If the IDCODE doesn't match our expected
+				// part, consider the bitstream invalid.
+				if (packet.data()[0] != part.idcode()) {
+					return {};
+				}
+				break;
+			case ArchType::ConfRegType::FAR:
+				// This really should be a one-word write.
+				if (packet.data().size() < 1)
+					continue;
+				frame_address_register = packet.data()[0];
+
+				// Per UG470, the command present in the CMD
+				// register is executed each time the FAR
+				// register is laoded with a new value.  As we
+				// only care about WCFG commands, just check
+				// that here.  CTRL1 is completely undocumented
+				// but looking at generated bitstreams, bit 21
+				// is used when per-frame CRC is enabled.
+				// Setting this bit seems to inhibit the
+				// re-execution of CMD during a FAR write.  In
+				// practice, this is used so FAR writes can be
+				// added in the bitstream to show progress
+				// markers without impacting the actual write
+				// operation.
+				if (bit_field_get(ctl1_register, 21, 21) == 0 &&
+				    command_register == 0x1) {
+					start_new_write = true;
+				}
+				break;
+			case ArchType::ConfRegType::FDRI: {
+				if (start_new_write) {
+					current_frame_address =
+					    frame_address_register;
+					start_new_write = false;
+				}
+
+				// Number of words in configuration frames
+				// depend on tje architecture.  Writes to this
+				// register can be multiples of that number to
+				// do auto-incrementing block writes.
+				for (size_t ii = 0; ii < packet.data().size();
+				     ii += ArchType::words_per_frame) {
+					frames[current_frame_address] =
+					    packet.data().subspan(
+					        ii, ArchType::words_per_frame);
+
+					auto next_address =
+					    part.GetNextFrameAddress(
+					        current_frame_address);
+					if (!next_address)
+						break;
+
+					// Bitstreams appear to have 2 frames of
+					// padding between rows.
+					if (next_address &&
+					    (next_address->block_type() !=
+					         current_frame_address
+					             .block_type() ||
+					     next_address
+					             ->is_bottom_half_rows() !=
+					         current_frame_address
+					             .is_bottom_half_rows() ||
+					     next_address->row() !=
+					         current_frame_address.row())) {
+						ii += 2 *
+						      ArchType::words_per_frame;
+					}
+					current_frame_address = *next_address;
+				}
+				break;
+			}
+			default:
+				break;
+		}
+	}
+
+	return Configuration(part, frames);
+}
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_CONFIGURATION_H_
diff --git a/lib/include/prjxray/xilinx/configuration_packet.h b/lib/include/prjxray/xilinx/configuration_packet.h
new file mode 100644
index 0000000..4df2105
--- /dev/null
+++ b/lib/include/prjxray/xilinx/configuration_packet.h
@@ -0,0 +1,68 @@
+#ifndef PRJXRAY_LIB_XILINX_CONFIGURATION_PACKET_H
+#define PRJXRAY_LIB_XILINX_CONFIGURATION_PACKET_H
+
+#include <cstdint>
+#include <ostream>
+
+#include <absl/types/optional.h>
+#include <absl/types/span.h>
+#include <prjxray/xilinx/configuration_register.h>
+
+namespace prjxray {
+namespace xilinx {
+
+// As described in the configuration user guide for Series-7
+// (UG470, pg. 108) there are two types of configuration packets
+enum ConfigurationPacketType { NONE, TYPE1, TYPE2 };
+
+// Creates a Type1 or Type2 configuration packet.
+// Specification of the packets for Series-7 can be found in UG470, pg. 108
+// For Spartan6 UG380, pg. 98
+template <typename ConfigRegType>
+class ConfigurationPacket {
+       public:
+	typedef std::pair<absl::Span<uint32_t>,
+	                  absl::optional<ConfigurationPacket<ConfigRegType>>>
+	    ParseResult;
+
+	// Opcodes as specified in UG470 page 108
+	enum Opcode {
+		NOP = 0,
+		Read = 1,
+		Write = 2,
+		/* reserved = 3 */
+	};
+
+	ConfigurationPacket(unsigned int header_type,
+	                    Opcode opcode,
+	                    ConfigRegType address,
+	                    const absl::Span<const uint32_t>& data)
+	    : header_type_(header_type),
+	      opcode_(opcode),
+	      address_(address),
+	      data_(std::move(data)) {}
+
+	unsigned int header_type() const { return header_type_; }
+	const Opcode opcode() const { return opcode_; }
+	const ConfigRegType address() const { return address_; }
+	const absl::Span<const uint32_t>& data() const { return data_; }
+	static ParseResult InitWithWords(
+	    absl::Span<uint32_t> words,
+	    const ConfigurationPacket<ConfigRegType>* previous_packet =
+	        nullptr);
+
+       private:
+	unsigned int header_type_;
+	Opcode opcode_;
+	ConfigRegType address_;
+	absl::Span<const uint32_t> data_;
+};
+
+template <class ConfigRegType>
+std::ostream& operator<<(std::ostream& o,
+                         const ConfigurationPacket<ConfigRegType>& packet);
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_CONFIGURATION_PACKET_H
diff --git a/lib/include/prjxray/xilinx/configuration_packet_with_payload.h b/lib/include/prjxray/xilinx/configuration_packet_with_payload.h
new file mode 100644
index 0000000..0561938
--- /dev/null
+++ b/lib/include/prjxray/xilinx/configuration_packet_with_payload.h
@@ -0,0 +1,36 @@
+#ifndef PRJXRAY_LIB_XILINX_CONFIGURATION_PACKET_WITH_PAYLOAD_H
+#define PRJXRAY_LIB_XILINX_CONFIGURATION_PACKET_WITH_PAYLOAD_H
+
+#include <memory>
+
+#include <absl/types/span.h>
+#include <prjxray/bit_ops.h>
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/configuration_register.h>
+
+namespace prjxray {
+namespace xilinx {
+
+template <int Words, typename ConfigRegType>
+class ConfigurationPacketWithPayload
+    : public ConfigurationPacket<ConfigRegType> {
+       public:
+	ConfigurationPacketWithPayload(
+	    typename ConfigurationPacket<ConfigRegType>::Opcode op,
+	    ConfigRegType reg,
+	    const std::array<uint32_t, Words>& payload)
+	    : ConfigurationPacket<ConfigRegType>(
+	          ConfigurationPacketType::TYPE1,
+	          op,
+	          reg,
+	          absl::Span<uint32_t>(payload_)),
+	      payload_(std::move(payload)) {}
+
+       private:
+	std::array<uint32_t, Words> payload_;
+};
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_CONFIGURATION_PACKET_WITH_PAYLOAD_H
diff --git a/lib/include/prjxray/xilinx/configuration_register.h b/lib/include/prjxray/xilinx/configuration_register.h
new file mode 100644
index 0000000..0699f6c
--- /dev/null
+++ b/lib/include/prjxray/xilinx/configuration_register.h
@@ -0,0 +1,84 @@
+#ifndef PRJXRAY_LIB_XILINX_CONFIGURATION_REGISTER_H_
+#define PRJXRAY_LIB_XILINX_CONFIGURATION_REGISTER_H_
+
+#include <ostream>
+
+namespace prjxray {
+namespace xilinx {
+
+// Spartan6 configuration register addresses
+// according to UG380, pg. 100
+enum class Spartan6ConfigurationRegister : unsigned int {
+	CRC = 0x00,
+	FAR = 0x01,
+	FAR_MAJ = 0x01,
+	FAR_MIN = 0x02,
+	FDRI = 0x03,
+	FDRO = 0x04,
+	CMD = 0x05,
+	CTL = 0x06,
+	CTL1 = 0x06,
+	MASK = 0x07,
+	STAT = 0x08,
+	LOUT = 0x09,
+	COR1 = 0x0a,
+	COR2 = 0x0b,
+	PWRDN_REG = 0x0c,
+	FLR = 0x0d,
+	IDCODE = 0x0e,
+	CWDT = 0x0f,
+	HC_OPT_REG = 0x10,
+	CSBO = 0x12,
+	GENERAL1 = 0x13,
+	GENERAL2 = 0x14,
+	GENERAL3 = 0x15,
+	GENERAL4 = 0x16,
+	GENERAL5 = 0x17,
+	MODE_REG = 0x18,
+	PU_GWE = 0x19,
+	PU_GTS = 0x1a,
+	MFWR = 0x1b,
+	CCLK_FREQ = 0x1c,
+	SEU_OPT = 0x1d,
+	EXP_SIGN = 0x1e,
+	RDBK_SIGN = 0x1f,
+	BOOTSTS = 0x20,
+	EYE_MASK = 0x21,
+	CBC_REG = 0x22,
+};
+
+// Series-7 configuration register addresses
+// according to UG470, pg. 109
+enum class Series7ConfigurationRegister : unsigned int {
+	CRC = 0x00,
+	FAR = 0x01,
+	FDRI = 0x02,
+	FDRO = 0x03,
+	CMD = 0x04,
+	CTL0 = 0x05,
+	MASK = 0x06,
+	STAT = 0x07,
+	LOUT = 0x08,
+	COR0 = 0x09,
+	MFWR = 0x0a,
+	CBC = 0x0b,
+	IDCODE = 0x0c,
+	AXSS = 0x0d,
+	COR1 = 0x0e,
+	WBSTAR = 0x10,
+	TIMER = 0x11,
+	UNKNOWN = 0x13,
+	BOOTSTS = 0x16,
+	CTL1 = 0x18,
+	BSPI = 0x1F,
+};
+
+std::ostream& operator<<(std::ostream& o,
+                         const Spartan6ConfigurationRegister& value);
+std::ostream& operator<<(std::ostream& o,
+                         const Series7ConfigurationRegister& value);
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_CONFIGURATION_REGISTER_H_
diff --git a/lib/include/prjxray/xilinx/frames.h b/lib/include/prjxray/xilinx/frames.h
new file mode 100644
index 0000000..f4a96f1
--- /dev/null
+++ b/lib/include/prjxray/xilinx/frames.h
@@ -0,0 +1,125 @@
+#ifndef PRJXRAY_LIB_XILINX_FRAMES_H
+#define PRJXRAY_LIB_XILINX_FRAMES_H
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <absl/strings/str_split.h>
+#include <prjxray/xilinx/architectures.h>
+
+namespace prjxray {
+namespace xilinx {
+
+// Contains frame information which is used for the generation
+// of the configuration package that is used in bitstream generation.
+template <typename ArchType>
+class Frames {
+       public:
+	typedef std::vector<uint32_t> FrameData;
+	typedef std::map<typename ArchType::FrameAddress, FrameData>
+	    Frames2Data;
+
+	// Reads the contents of the frames file and populates
+	// the Frames container.
+	int readFrames(const std::string& frm_file_str);
+
+	// Adds empty frames that are present in the tilegrid of a specific part
+	// but are missing in the current frames container.
+	void addMissingFrames(
+	    const absl::optional<typename ArchType::Part>& part);
+
+	// Returns the map with frame addresses and corresponding data
+	Frames2Data& getFrames() { return frames_data_; }
+
+       private:
+	Frames2Data frames_data_;
+
+	// Updates the ECC information in the frame
+	void updateECC(FrameData& data);
+};
+
+template <typename ArchType>
+int Frames<ArchType>::readFrames(const std::string& frm_file_str) {
+	assert(!frm_file_str.empty());
+
+	std::ifstream frm_file(frm_file_str);
+	if (!frm_file) {
+		std::cerr << "Unable to open frm file: " << frm_file_str
+		          << std::endl;
+		return 1;
+	}
+	std::string frm_line;
+
+	while (std::getline(frm_file, frm_line)) {
+		if (frm_line[0] == '#')
+			continue;
+
+		std::pair<std::string, std::string> frame_delta =
+		    absl::StrSplit(frm_line, ' ');
+
+		uint32_t frame_address =
+		    std::stoul(frame_delta.first, nullptr, 16);
+
+		std::vector<std::string> frame_data_strings =
+		    absl::StrSplit(frame_delta.second, ',');
+
+		// Spartan6's IOB frames can have different word count
+		if (!std::is_same<ArchType, Spartan6>::value) {
+			if (frame_data_strings.size() !=
+			    ArchType::words_per_frame) {
+				std::cerr
+				    << "Frame " << std::hex << frame_address
+				    << ": found " << std::dec
+				    << frame_data_strings.size()
+				    << " words instead of "
+				    << ArchType::words_per_frame << std::endl;
+				continue;
+			}
+		}
+
+		FrameData frame_data(frame_data_strings.size(), 0);
+		std::transform(frame_data_strings.begin(),
+		               frame_data_strings.end(), frame_data.begin(),
+		               [](const std::string& val) -> uint32_t {
+			               return std::stoul(val, nullptr, 16);
+		               });
+
+		updateECC(frame_data);
+
+		// Insert the frame address and corresponding frame data to the
+		// map
+		typename ArchType::FrameAddress frm_addr(frame_address);
+		frames_data_.insert(
+		    std::pair<typename ArchType::FrameAddress, FrameData>(
+		        frm_addr, frame_data));
+	}
+	return 0;
+}
+
+template <typename ArchType>
+void Frames<ArchType>::addMissingFrames(
+    const absl::optional<typename ArchType::Part>& part) {
+	auto current_frame_address =
+	    absl::optional<typename ArchType::FrameAddress>(
+	        typename ArchType::FrameAddress(0));
+	do {
+		auto iter = frames_data_.find(*current_frame_address);
+		if (iter == frames_data_.end()) {
+			FrameData frame_data(ArchType::words_per_frame, 0);
+			frames_data_.insert(
+			    std::pair<typename ArchType::FrameAddress,
+			              FrameData>(*current_frame_address,
+			                         frame_data));
+		}
+		current_frame_address =
+		    part->GetNextFrameAddress(*current_frame_address);
+	} while (current_frame_address);
+}
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_FRAMES_H
diff --git a/lib/include/prjxray/xilinx/nop_packet.h b/lib/include/prjxray/xilinx/nop_packet.h
new file mode 100644
index 0000000..6f89e84
--- /dev/null
+++ b/lib/include/prjxray/xilinx/nop_packet.h
@@ -0,0 +1,24 @@
+#ifndef PRJXRAY_LIB_XILINX_NOP_PACKET_H
+#define PRJXRAY_LIB_XILINX_NOP_PACKET_H
+
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/configuration_register.h>
+
+namespace prjxray {
+namespace xilinx {
+
+template <typename ConfigRegType>
+class NopPacket : public ConfigurationPacket<ConfigRegType> {
+       public:
+	NopPacket()
+	    : ConfigurationPacket<ConfigRegType>(
+	          ConfigurationPacketType::TYPE1,
+	          ConfigurationPacket<ConfigRegType>::Opcode::NOP,
+	          ConfigRegType::CRC,
+	          {}) {}
+};
+
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_NOP_PACKET_H
diff --git a/lib/include/prjxray/xilinx/spartan6/block_type.h b/lib/include/prjxray/xilinx/spartan6/block_type.h
new file mode 100644
index 0000000..a42a8db
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/block_type.h
@@ -0,0 +1,37 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_BLOCK_TYPE_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_BLOCK_TYPE_H_
+
+#include <ostream>
+
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+// According to UG380 pg. 97 there are 3 types of configuration frames:
+// Type 0: Core: CLB, DSP, input/output interconnect (IOI), clocking
+// Type 1: Block RAM
+// Type 2: IOB
+enum class BlockType : unsigned int {
+	CLB_IOI_CLK = 0x0,
+	BLOCK_RAM = 0x1,
+	IOB = 0x2,
+};
+
+std::ostream& operator<<(std::ostream& o, BlockType value);
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::spartan6::BlockType> {
+	static Node encode(const prjxray::xilinx::spartan6::BlockType& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::spartan6::BlockType& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_BLOCK_TYPE_H_
diff --git a/lib/include/prjxray/xilinx/spartan6/command.h b/lib/include/prjxray/xilinx/spartan6/command.h
new file mode 100644
index 0000000..93bafdb
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/command.h
@@ -0,0 +1,35 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_COMMAND_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_COMMAND_H_
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+// Command register map according to UG380 pg. 102
+enum class Command : uint32_t {
+	NOP = 0x0,
+	WCFG = 0x1,
+	MFW = 0x2,
+	LFRM = 0x3,
+	RCFG = 0x4,
+	START = 0x5,
+	RCAP = 0x6,
+	RCRC = 0x7,
+	AGHIGH = 0x8,
+	SWITCH = 0x9,
+	GRESTORE = 0xA,
+	SHUTDOWN = 0xB,
+	GCAPTURE = 0xC,
+	DESYNC = 0xD,
+	IPROG = 0xF,
+	CRCC = 0x10,
+	LTIMER = 0x11,
+	BSPI_READ = 0x12,
+	FALL_EDGE = 0x13,
+};
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_COMMAND_H_
diff --git a/lib/include/prjxray/xilinx/spartan6/configuration_bus.h b/lib/include/prjxray/xilinx/spartan6/configuration_bus.h
new file mode 100644
index 0000000..a5d0081
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/configuration_bus.h
@@ -0,0 +1,91 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_BUS_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_BUS_H_
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+#include <memory>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/spartan6/configuration_column.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+// ConfigurationBus represents a bus for sending frames to a specific BlockType
+// within a Row.  An instance of ConfigurationBus will contain one or more
+// ConfigurationColumns.
+class ConfigurationBus {
+       public:
+	ConfigurationBus() = default;
+
+	// Constructs a ConfigurationBus from iterators yielding
+	// FrameAddresses.  The frame address need not be contiguous or sorted
+	// but they must all have the same block type, row half, and row
+	// address components.
+	template <typename T>
+	ConfigurationBus(T first, T last);
+
+	// Returns true if the provided address falls into a valid segment of
+	// the address range on this bus.  Only the column and minor components
+	// of the address are considered as all other components are outside
+	// the scope of a bus.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next valid address on the bus in numerically increasing
+	// order. If the next address would fall outside this bus, no object is
+	// returned.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<ConfigurationBus>;
+
+	std::map<unsigned int, ConfigurationColumn> configuration_columns_;
+};
+
+template <typename T>
+ConfigurationBus::ConfigurationBus(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return (addr.block_type() == first->block_type() &&
+		            addr.row() == first->row());
+	    }));
+
+	std::sort(first, last,
+	          [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		          return lhs.column() < rhs.column();
+	          });
+
+	for (auto col_first = first; col_first != last;) {
+		auto col_last = std::upper_bound(
+		    col_first, last, col_first->column(),
+		    [](const unsigned int& lhs, const FrameAddress& rhs) {
+			    return lhs < rhs.column();
+		    });
+
+		configuration_columns_.emplace(
+		    col_first->column(),
+		    std::move(ConfigurationColumn(col_first, col_last)));
+		col_first = col_last;
+	}
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::spartan6::ConfigurationBus> {
+	static Node encode(
+	    const prjxray::xilinx::spartan6::ConfigurationBus& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::spartan6::ConfigurationBus& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_BUS_H_
diff --git a/lib/include/prjxray/xilinx/spartan6/configuration_column.h b/lib/include/prjxray/xilinx/spartan6/configuration_column.h
new file mode 100644
index 0000000..af8edd6
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/configuration_column.h
@@ -0,0 +1,75 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_COLUMN_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_COLUMN_H_
+
+#include <algorithm>
+#include <cassert>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+// ConfigurationColumn represents an endpoint on a ConfigurationBus.
+class ConfigurationColumn {
+       public:
+	ConfigurationColumn() = default;
+	ConfigurationColumn(unsigned int frame_count)
+	    : frame_count_(frame_count) {}
+
+	// Returns a ConfigurationColumn that describes a continguous range of
+	// minor addresses that encompasses the given
+	// FrameAddresses.  The provided addresses must only
+	// differ only by their minor addresses.
+	template <typename T>
+	ConfigurationColumn(T first, T last);
+
+	// Returns true if the minor field of the address is within the valid
+	// range of this column.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next address in numerical order.  If the next address
+	// would be outside this column, return no object.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<ConfigurationColumn>;
+
+	unsigned int frame_count_;
+};
+
+template <typename T>
+ConfigurationColumn::ConfigurationColumn(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return (addr.block_type() == first->block_type() &&
+		            addr.row() == first->row() &&
+		            addr.column() == first->column());
+	    }));
+
+	auto max_minor = std::max_element(
+	    first, last, [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		    return lhs.minor() < rhs.minor();
+	    });
+
+	frame_count_ = max_minor->minor() + 1;
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::spartan6::ConfigurationColumn> {
+	static Node encode(
+	    const prjxray::xilinx::spartan6::ConfigurationColumn& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::spartan6::ConfigurationColumn& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_COLUMN_H_
diff --git a/lib/include/prjxray/xilinx/spartan6/configuration_row.h b/lib/include/prjxray/xilinx/spartan6/configuration_row.h
new file mode 100644
index 0000000..a2b267e
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/configuration_row.h
@@ -0,0 +1,90 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_ROW_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_ROW_H_
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+#include <memory>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/spartan6/block_type.h>
+#include <prjxray/xilinx/spartan6/configuration_bus.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+class Row {
+       public:
+	Row() = default;
+
+	// Construct a row from a range of iterators that yield FrameAddresses.
+	// The addresses may be noncontinguous and/or unsorted but all must
+	// share the same row half and row components.
+	template <typename T>
+	Row(T first, T last);
+
+	// Returns true if the provided address falls within a valid range
+	// attributed to this row.  Only the block type, column, and minor
+	// address components are considerd as the remaining components are
+	// outside the scope of a row.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next numerically increasing address within the Row. If
+	// the next address would fall outside the Row, no object is returned.
+	// If the next address would cross from one block type to another, no
+	// object is returned as other rows of the same block type come before
+	// other block types numerically.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<Row>;
+
+	std::map<BlockType, ConfigurationBus> configuration_buses_;
+};
+
+template <typename T>
+Row::Row(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return (addr.is_bottom_half_rows() ==
+		                first->is_bottom_half_rows() &&
+		            addr.row() == first->row());
+	    }));
+
+	std::sort(first, last,
+	          [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		          return lhs.block_type() < rhs.block_type();
+	          });
+
+	for (auto bus_first = first; bus_first != last;) {
+		auto bus_last = std::upper_bound(
+		    bus_first, last, bus_first->block_type(),
+		    [](const BlockType& lhs, const FrameAddress& rhs) {
+			    return lhs < rhs.block_type();
+		    });
+
+		configuration_buses_.emplace(
+		    bus_first->block_type(),
+		    std::move(ConfigurationBus(bus_first, bus_last)));
+		bus_first = bus_last;
+	}
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::spartan6::Row> {
+	static Node encode(const prjxray::xilinx::spartan6::Row& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::spartan6::Row& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_ROW_H_
diff --git a/lib/include/prjxray/xilinx/spartan6/frame_address.h b/lib/include/prjxray/xilinx/spartan6/frame_address.h
new file mode 100644
index 0000000..651d86a
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/frame_address.h
@@ -0,0 +1,55 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_FRAME_ADDRESS_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_FRAME_ADDRESS_H_
+
+#include <cstdint>
+#include <ostream>
+
+#include <prjxray/xilinx/spartan6/block_type.h>
+#include <yaml-cpp/yaml.h>
+
+#ifdef _GNU_SOURCE
+#undef minor
+#endif
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+class FrameAddress {
+       public:
+	FrameAddress() : address_(0) {}
+
+	FrameAddress(uint32_t address) : address_(address){};
+
+	FrameAddress(BlockType block_type,
+	             uint8_t row,
+	             uint8_t column,
+	             uint16_t minor);
+
+	operator uint32_t() const { return address_; }
+	bool is_bottom_half_rows() const;
+	BlockType block_type() const;
+	uint8_t row() const;
+	uint8_t column() const;
+	uint16_t minor() const;
+
+       private:
+	uint32_t address_;
+};
+
+std::ostream& operator<<(std::ostream& o, const FrameAddress& addr);
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+template <>
+struct convert<spartan6::FrameAddress> {
+	static Node encode(const spartan6::FrameAddress& rhs);
+	static bool decode(const Node& node, spartan6::FrameAddress& lhs);
+};
+}  // namespace YAML
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_FRAME_ADDRESS_H_
diff --git a/lib/include/prjxray/xilinx/spartan6/global_clock_region.h b/lib/include/prjxray/xilinx/spartan6/global_clock_region.h
new file mode 100644
index 0000000..bb0ce93
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/global_clock_region.h
@@ -0,0 +1,93 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_GLOBAL_CLOCK_REGION_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_GLOBAL_CLOCK_REGION_H_
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+#include <memory>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/spartan6/configuration_row.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+// GlobalClockRegion represents all the resources associated with a single
+// global clock buffer (BUFG) tile.  In 7-Series FPGAs, there are two BUFG
+// tiles that divide the chip into top and bottom "halves". Each half may
+// contains any number of rows, buses, and columns.
+class GlobalClockRegion {
+       public:
+	GlobalClockRegion() = default;
+
+	// Construct a GlobalClockRegion from iterators that yield
+	// FrameAddresses which are known to be valid. The addresses may be
+	// noncontinguous and/or unordered but they must share the same row
+	// half address component.
+	template <typename T>
+	GlobalClockRegion(T first, T last);
+
+	// Returns true if the address falls within a valid range inside the
+	// global clock region. The row half address component is ignored as it
+	// is outside the context of a global clock region.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next numerically increasing address known within this
+	// global clock region. If the next address would fall outside this
+	// global clock region, no address is returned. If the next address
+	// would jump to a different block type, no address is returned as the
+	// same block type in other global clock regions come numerically
+	// before other block types.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<GlobalClockRegion>;
+
+	std::map<unsigned int, Row> rows_;
+};
+
+template <typename T>
+GlobalClockRegion::GlobalClockRegion(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return addr.is_bottom_half_rows() ==
+		           first->is_bottom_half_rows();
+	    }));
+
+	std::sort(first, last,
+	          [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		          return lhs.row() < rhs.row();
+	          });
+
+	for (auto row_first = first; row_first != last;) {
+		auto row_last = std::upper_bound(
+		    row_first, last, row_first->row(),
+		    [](const uint8_t& lhs, const FrameAddress& rhs) {
+			    return lhs < rhs.row();
+		    });
+
+		rows_.emplace(row_first->row(),
+		              std::move(Row(row_first, row_last)));
+		row_first = row_last;
+	}
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::spartan6::GlobalClockRegion> {
+	static Node encode(
+	    const prjxray::xilinx::spartan6::GlobalClockRegion& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::spartan6::GlobalClockRegion& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_GLOBAL_CLOCK_REGION_H_
diff --git a/lib/include/prjxray/xilinx/spartan6/part.h b/lib/include/prjxray/xilinx/spartan6/part.h
new file mode 100644
index 0000000..df11384
--- /dev/null
+++ b/lib/include/prjxray/xilinx/spartan6/part.h
@@ -0,0 +1,70 @@
+#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_PART_H_
+#define PRJXRAY_LIB_XILINX_SPARTAN6_PART_H_
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/spartan6/global_clock_region.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+class Part {
+       public:
+	constexpr static uint32_t kInvalidIdcode = 0;
+
+	static absl::optional<Part> FromFile(const std::string& path);
+
+	// Constructs an invalid part with a zero IDCODE. Required for YAML
+	// conversion but shouldn't be used otherwise.
+	Part() : idcode_(kInvalidIdcode) {}
+
+	template <typename T>
+	Part(uint32_t idcode, T collection)
+	    : Part(idcode, std::begin(collection), std::end(collection)) {}
+
+	template <typename T>
+	Part(uint32_t idcode, T first, T last);
+
+	uint32_t idcode() const { return idcode_; }
+
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<Part>;
+
+	uint32_t idcode_;
+	spartan6::GlobalClockRegion top_region_;
+	spartan6::GlobalClockRegion bottom_region_;
+};
+
+template <typename T>
+Part::Part(uint32_t idcode, T first, T last) : idcode_(idcode) {
+	top_region_ = spartan6::GlobalClockRegion(first, last);
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+template <>
+struct convert<spartan6::Part> {
+	static Node encode(const spartan6::Part& rhs);
+	static bool decode(const Node& node, spartan6::Part& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_SPARTAN6_PART_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/block_type.h b/lib/include/prjxray/xilinx/xc7series/block_type.h
new file mode 100644
index 0000000..400a19f
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/block_type.h
@@ -0,0 +1,34 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_BLOCK_TYPE_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_BLOCK_TYPE_H_
+
+#include <ostream>
+
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+enum class BlockType : unsigned int {
+	CLB_IO_CLK = 0x0,
+	BLOCK_RAM = 0x1,
+	CFG_CLB = 0x2,
+	/* reserved = 0x3, */
+};
+
+std::ostream& operator<<(std::ostream& o, BlockType value);
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::xc7series::BlockType> {
+	static Node encode(const prjxray::xilinx::xc7series::BlockType& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::xc7series::BlockType& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_BLOCK_TYPE_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/command.h b/lib/include/prjxray/xilinx/xc7series/command.h
new file mode 100644
index 0000000..2ff16e8
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/command.h
@@ -0,0 +1,34 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_COMMAND_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_COMMAND_H_
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+enum class Command : uint32_t {
+	NOP = 0x0,
+	WCFG = 0x1,
+	MFW = 0x2,
+	LFRM = 0x3,
+	RCFG = 0x4,
+	START = 0x5,
+	RCAP = 0x6,
+	RCRC = 0x7,
+	AGHIGH = 0x8,
+	SWITCH = 0x9,
+	GRESTORE = 0xA,
+	SHUTDOWN = 0xB,
+	GCAPTURE = 0xC,
+	DESYNC = 0xD,
+	IPROG = 0xF,
+	CRCC = 0x10,
+	LTIMER = 0x11,
+	BSPI_READ = 0x12,
+	FALL_EDGE = 0x13,
+};
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_COMMAND_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/configuration_bus.h b/lib/include/prjxray/xilinx/xc7series/configuration_bus.h
new file mode 100644
index 0000000..33e9619
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/configuration_bus.h
@@ -0,0 +1,93 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_BUS_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_BUS_H_
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+#include <memory>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/xc7series/configuration_column.h>
+#include <prjxray/xilinx/xc7series/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+// ConfigurationBus represents a bus for sending frames to a specific BlockType
+// within a Row.  An instance of ConfigurationBus will contain one or more
+// ConfigurationColumns.
+class ConfigurationBus {
+       public:
+	ConfigurationBus() = default;
+
+	// Constructs a ConfigurationBus from iterators yielding
+	// FrameAddresses.  The frame address need not be contiguous or sorted
+	// but they must all have the same block type, row half, and row
+	// address components.
+	template <typename T>
+	ConfigurationBus(T first, T last);
+
+	// Returns true if the provided address falls into a valid segment of
+	// the address range on this bus.  Only the column and minor components
+	// of the address are considered as all other components are outside
+	// the scope of a bus.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next valid address on the bus in numerically increasing
+	// order. If the next address would fall outside this bus, no object is
+	// returned.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<ConfigurationBus>;
+
+	std::map<unsigned int, ConfigurationColumn> configuration_columns_;
+};
+
+template <typename T>
+ConfigurationBus::ConfigurationBus(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return (addr.block_type() == first->block_type() &&
+		            addr.is_bottom_half_rows() ==
+		                first->is_bottom_half_rows() &&
+		            addr.row() == first->row());
+	    }));
+
+	std::sort(first, last,
+	          [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		          return lhs.column() < rhs.column();
+	          });
+
+	for (auto col_first = first; col_first != last;) {
+		auto col_last = std::upper_bound(
+		    col_first, last, col_first->column(),
+		    [](const unsigned int& lhs, const FrameAddress& rhs) {
+			    return lhs < rhs.column();
+		    });
+
+		configuration_columns_.emplace(
+		    col_first->column(),
+		    std::move(ConfigurationColumn(col_first, col_last)));
+		col_first = col_last;
+	}
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::xc7series::ConfigurationBus> {
+	static Node encode(
+	    const prjxray::xilinx::xc7series::ConfigurationBus& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::xc7series::ConfigurationBus& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_BUS_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/configuration_column.h b/lib/include/prjxray/xilinx/xc7series/configuration_column.h
new file mode 100644
index 0000000..b15415e
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/configuration_column.h
@@ -0,0 +1,78 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_COLUMN_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_COLUMN_H_
+
+#include <algorithm>
+#include <cassert>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/xc7series/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+// ConfigurationColumn represents an endpoint on a ConfigurationBus.
+class ConfigurationColumn {
+       public:
+	ConfigurationColumn() = default;
+	ConfigurationColumn(unsigned int frame_count)
+	    : frame_count_(frame_count) {}
+
+	// Returns a ConfigurationColumn that describes a continguous range of
+	// minor addresses that encompasses the given
+	// FrameAddresses.  The provided addresses must only
+	// differ only by their minor addresses.
+	template <typename T>
+	ConfigurationColumn(T first, T last);
+
+	// Returns true if the minor field of the address is within the valid
+	// range of this column.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next address in numerical order.  If the next address
+	// would be outside this column, return no object.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<ConfigurationColumn>;
+
+	unsigned int frame_count_;
+};
+
+template <typename T>
+ConfigurationColumn::ConfigurationColumn(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return (addr.block_type() == first->block_type() &&
+		            addr.is_bottom_half_rows() ==
+		                first->is_bottom_half_rows() &&
+		            addr.row() == first->row() &&
+		            addr.column() == first->column());
+	    }));
+
+	auto max_minor = std::max_element(
+	    first, last, [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		    return lhs.minor() < rhs.minor();
+	    });
+
+	frame_count_ = max_minor->minor() + 1;
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::xc7series::ConfigurationColumn> {
+	static Node encode(
+	    const prjxray::xilinx::xc7series::ConfigurationColumn& rhs);
+	static bool decode(
+	    const Node& node,
+	    prjxray::xilinx::xc7series::ConfigurationColumn& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_COLUMN_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/configuration_options_0_value.h b/lib/include/prjxray/xilinx/xc7series/configuration_options_0_value.h
new file mode 100644
index 0000000..0630737
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/configuration_options_0_value.h
@@ -0,0 +1,122 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_OPTIONS_0_VALUE_H
+#define PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_OPTIONS_0_VALUE_H
+
+#include <prjxray/bit_ops.h>
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/configuration_register.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+class ConfigurationOptions0Value {
+       public:
+	enum class StartupClockSource : uint32_t {
+		CCLK = 0x0,
+		User = 0x1,
+		JTAG = 0x2,
+	};
+
+	enum class SignalReleaseCycle : uint32_t {
+		Phase1 = 0x0,
+		Phase2 = 0x1,
+		Phase3 = 0x2,
+		Phase4 = 0x3,
+		Phase5 = 0x4,
+		Phase6 = 0x5,
+		TrackDone = 0x6,
+		Keep = 0x7,
+	};
+
+	enum class StallCycle : uint32_t {
+		Phase0 = 0x0,
+		Phase1 = 0x1,
+		Phase2 = 0x2,
+		Phase3 = 0x3,
+		Phase4 = 0x4,
+		Phase5 = 0x5,
+		Phase6 = 0x6,
+		NoWait = 0x7,
+	};
+
+	ConfigurationOptions0Value() : value_(0) {}
+
+	operator uint32_t() const { return value_; }
+
+	ConfigurationOptions0Value& SetUseDonePinAsPowerdownStatus(
+	    bool enabled) {
+		value_ = bit_field_set(value_, 27, 27, enabled ? 1 : 0);
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetAddPipelineStageForDoneIn(bool enabled) {
+		value_ = bit_field_set(value_, 25, 25, enabled ? 1 : 0);
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetDriveDoneHigh(bool enabled) {
+		value_ = bit_field_set(value_, 24, 24, enabled);
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetReadbackIsSingleShot(bool enabled) {
+		value_ = bit_field_set(value_, 23, 23, enabled);
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetCclkFrequency(uint32_t mhz) {
+		value_ = bit_field_set(value_, 22, 17, mhz);
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetStartupClockSource(
+	    StartupClockSource source) {
+		value_ = bit_field_set(value_, 16, 15,
+		                       static_cast<uint32_t>(source));
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetReleaseDonePinAtStartupCycle(
+	    SignalReleaseCycle cycle) {
+		value_ =
+		    bit_field_set(value_, 14, 12, static_cast<uint32_t>(cycle));
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetStallAtStartupCycleUntilDciMatch(
+	    StallCycle cycle) {
+		value_ =
+		    bit_field_set(value_, 11, 9, static_cast<uint32_t>(cycle));
+		return *this;
+	};
+
+	ConfigurationOptions0Value& SetStallAtStartupCycleUntilMmcmLock(
+	    StallCycle cycle) {
+		value_ =
+		    bit_field_set(value_, 8, 6, static_cast<uint32_t>(cycle));
+		return *this;
+	};
+
+	ConfigurationOptions0Value& SetReleaseGtsSignalAtStartupCycle(
+	    SignalReleaseCycle cycle) {
+		value_ =
+		    bit_field_set(value_, 5, 3, static_cast<uint32_t>(cycle));
+		return *this;
+	}
+
+	ConfigurationOptions0Value& SetReleaseGweSignalAtStartupCycle(
+	    SignalReleaseCycle cycle) {
+		value_ =
+		    bit_field_set(value_, 2, 0, static_cast<uint32_t>(cycle));
+		return *this;
+	}
+
+       private:
+	uint32_t value_;
+};  // namespace xc7series
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_OPTIONS_0_VALUE_H
diff --git a/lib/include/prjxray/xilinx/xc7series/configuration_row.h b/lib/include/prjxray/xilinx/xc7series/configuration_row.h
new file mode 100644
index 0000000..4cb7f56
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/configuration_row.h
@@ -0,0 +1,90 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_ROW_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_ROW_H_
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+#include <memory>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/xc7series/block_type.h>
+#include <prjxray/xilinx/xc7series/configuration_bus.h>
+#include <prjxray/xilinx/xc7series/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+class Row {
+       public:
+	Row() = default;
+
+	// Construct a row from a range of iterators that yield FrameAddresses.
+	// The addresses may be noncontinguous and/or unsorted but all must
+	// share the same row half and row components.
+	template <typename T>
+	Row(T first, T last);
+
+	// Returns true if the provided address falls within a valid range
+	// attributed to this row.  Only the block type, column, and minor
+	// address components are considerd as the remaining components are
+	// outside the scope of a row.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next numerically increasing address within the Row. If
+	// the next address would fall outside the Row, no object is returned.
+	// If the next address would cross from one block type to another, no
+	// object is returned as other rows of the same block type come before
+	// other block types numerically.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<Row>;
+
+	std::map<BlockType, ConfigurationBus> configuration_buses_;
+};
+
+template <typename T>
+Row::Row(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return (addr.is_bottom_half_rows() ==
+		                first->is_bottom_half_rows() &&
+		            addr.row() == first->row());
+	    }));
+
+	std::sort(first, last,
+	          [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		          return lhs.block_type() < rhs.block_type();
+	          });
+
+	for (auto bus_first = first; bus_first != last;) {
+		auto bus_last = std::upper_bound(
+		    bus_first, last, bus_first->block_type(),
+		    [](const BlockType& lhs, const FrameAddress& rhs) {
+			    return lhs < rhs.block_type();
+		    });
+
+		configuration_buses_.emplace(
+		    bus_first->block_type(),
+		    std::move(ConfigurationBus(bus_first, bus_last)));
+		bus_first = bus_last;
+	}
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::xc7series::Row> {
+	static Node encode(const prjxray::xilinx::xc7series::Row& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::xc7series::Row& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_ROW_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/crc.h b/lib/include/prjxray/xilinx/xc7series/crc.h
new file mode 100644
index 0000000..df6bcac
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/crc.h
@@ -0,0 +1,40 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_CRC_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_CRC_H_
+
+#include <cstdint>
+
+constexpr uint32_t kCrc32CastagnoliPolynomial = 0x82F63B78;
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+// 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.
+
+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;
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_CRC_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/ecc.h b/lib/include/prjxray/xilinx/xc7series/ecc.h
new file mode 100644
index 0000000..63513fd
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/ecc.h
@@ -0,0 +1,22 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_ECC_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_ECC_H_
+
+#include <cstdint>
+#include <vector>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+// Extend the current ECC code with one data word (32 bit) at a given
+// word index in the configuration frame and return the new ECC code.
+uint32_t icap_ecc(uint32_t idx, uint32_t data, uint32_t ecc);
+
+// Updates the ECC information in the frame.
+void updateECC(std::vector<uint32_t>& data);
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_ECC_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/frame_address.h b/lib/include/prjxray/xilinx/xc7series/frame_address.h
new file mode 100644
index 0000000..c15b87f
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/frame_address.h
@@ -0,0 +1,56 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_FRAME_ADDRESS_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_FRAME_ADDRESS_H_
+
+#include <cstdint>
+#include <ostream>
+
+#include <prjxray/xilinx/xc7series/block_type.h>
+#include <yaml-cpp/yaml.h>
+
+#ifdef _GNU_SOURCE
+#undef minor
+#endif
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+class FrameAddress {
+       public:
+	FrameAddress() : address_(0) {}
+
+	FrameAddress(uint32_t address) : address_(address){};
+
+	FrameAddress(BlockType block_type,
+	             bool is_bottom_half_rows,
+	             uint8_t row,
+	             uint16_t column,
+	             uint8_t minor);
+
+	operator uint32_t() const { return address_; }
+
+	BlockType block_type() const;
+	bool is_bottom_half_rows() const;
+	uint8_t row() const;
+	uint16_t column() const;
+	uint8_t minor() const;
+
+       private:
+	uint32_t address_;
+};
+
+std::ostream& operator<<(std::ostream& o, const FrameAddress& addr);
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::xc7series::FrameAddress> {
+	static Node encode(const prjxray::xilinx::xc7series::FrameAddress& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::xc7series::FrameAddress& lhs);
+};
+}  // namespace YAML
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_FRAME_ADDRESS_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/global_clock_region.h b/lib/include/prjxray/xilinx/xc7series/global_clock_region.h
new file mode 100644
index 0000000..8d791a2
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/global_clock_region.h
@@ -0,0 +1,93 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_GLOBAL_CLOCK_REGION_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_GLOBAL_CLOCK_REGION_H_
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+#include <memory>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/xc7series/configuration_row.h>
+#include <prjxray/xilinx/xc7series/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+// GlobalClockRegion represents all the resources associated with a single
+// global clock buffer (BUFG) tile.  In 7-Series FPGAs, there are two BUFG
+// tiles that divide the chip into top and bottom "halves". Each half may
+// contains any number of rows, buses, and columns.
+class GlobalClockRegion {
+       public:
+	GlobalClockRegion() = default;
+
+	// Construct a GlobalClockRegion from iterators that yield
+	// FrameAddresses which are known to be valid. The addresses may be
+	// noncontinguous and/or unordered but they must share the same row
+	// half address component.
+	template <typename T>
+	GlobalClockRegion(T first, T last);
+
+	// Returns true if the address falls within a valid range inside the
+	// global clock region. The row half address component is ignored as it
+	// is outside the context of a global clock region.
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	// Returns the next numerically increasing address known within this
+	// global clock region. If the next address would fall outside this
+	// global clock region, no address is returned. If the next address
+	// would jump to a different block type, no address is returned as the
+	// same block type in other global clock regions come numerically
+	// before other block types.
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<GlobalClockRegion>;
+
+	std::map<unsigned int, Row> rows_;
+};
+
+template <typename T>
+GlobalClockRegion::GlobalClockRegion(T first, T last) {
+	assert(
+	    std::all_of(first, last, [&](const typename T::value_type& addr) {
+		    return addr.is_bottom_half_rows() ==
+		           first->is_bottom_half_rows();
+	    }));
+
+	std::sort(first, last,
+	          [](const FrameAddress& lhs, const FrameAddress& rhs) {
+		          return lhs.row() < rhs.row();
+	          });
+
+	for (auto row_first = first; row_first != last;) {
+		auto row_last = std::upper_bound(
+		    row_first, last, row_first->row(),
+		    [](const uint8_t& lhs, const FrameAddress& rhs) {
+			    return lhs < rhs.row();
+		    });
+
+		rows_.emplace(row_first->row(),
+		              std::move(Row(row_first, row_last)));
+		row_first = row_last;
+	}
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::xc7series::GlobalClockRegion> {
+	static Node encode(
+	    const prjxray::xilinx::xc7series::GlobalClockRegion& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::xc7series::GlobalClockRegion& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_GLOBAL_CLOCK_REGION_H_
diff --git a/lib/include/prjxray/xilinx/xc7series/part.h b/lib/include/prjxray/xilinx/xc7series/part.h
new file mode 100644
index 0000000..90847e4
--- /dev/null
+++ b/lib/include/prjxray/xilinx/xc7series/part.h
@@ -0,0 +1,74 @@
+#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_PART_H_
+#define PRJXRAY_LIB_XILINX_XC7SERIES_PART_H_
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <absl/types/optional.h>
+#include <prjxray/xilinx/xc7series/global_clock_region.h>
+#include <yaml-cpp/yaml.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+class Part {
+       public:
+	constexpr static uint32_t kInvalidIdcode = 0;
+
+	static absl::optional<Part> FromFile(const std::string& path);
+
+	// Constructs an invalid part with a zero IDCODE. Required for YAML
+	// conversion but shouldn't be used otherwise.
+	Part() : idcode_(kInvalidIdcode) {}
+
+	template <typename T>
+	Part(uint32_t idcode, T collection)
+	    : Part(idcode, std::begin(collection), std::end(collection)) {}
+
+	template <typename T>
+	Part(uint32_t idcode, T first, T last);
+
+	uint32_t idcode() const { return idcode_; }
+
+	bool IsValidFrameAddress(FrameAddress address) const;
+
+	absl::optional<FrameAddress> GetNextFrameAddress(
+	    FrameAddress address) const;
+
+       private:
+	friend struct YAML::convert<Part>;
+
+	uint32_t idcode_;
+	GlobalClockRegion top_region_;
+	GlobalClockRegion bottom_region_;
+};
+
+template <typename T>
+Part::Part(uint32_t idcode, T first, T last) : idcode_(idcode) {
+	auto first_of_top =
+	    std::partition(first, last, [](const FrameAddress& addr) {
+		    return addr.is_bottom_half_rows();
+	    });
+
+	top_region_ = GlobalClockRegion(first_of_top, last);
+	bottom_region_ = GlobalClockRegion(first, first_of_top);
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+template <>
+struct convert<prjxray::xilinx::xc7series::Part> {
+	static Node encode(const prjxray::xilinx::xc7series::Part& rhs);
+	static bool decode(const Node& node,
+	                   prjxray::xilinx::xc7series::Part& lhs);
+};
+}  // namespace YAML
+
+#endif  // PRJXRAY_LIB_XILINX_XC7SERIES_PART_H_
diff --git a/lib/memory_mapped_file.cc b/lib/memory_mapped_file.cc
new file mode 100644
index 0000000..e6ada90
--- /dev/null
+++ b/lib/memory_mapped_file.cc
@@ -0,0 +1,51 @@
+#include <prjxray/memory_mapped_file.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace prjxray {
+
+std::unique_ptr<MemoryMappedFile> MemoryMappedFile::InitWithFile(
+    const std::string& path) {
+	int fd = open(path.c_str(), O_RDONLY, 0);
+	if (fd == -1)
+		return nullptr;
+
+	struct stat statbuf;
+	if (fstat(fd, &statbuf) < 0) {
+		close(fd);
+		return nullptr;
+	}
+
+	// mmap() will fail with EINVAL if length==0. If this file is
+	// zero-length, return an object (to indicate the file exists) but
+	// load it with a nullptr and zero length.
+	if (statbuf.st_size == 0) {
+		close(fd);
+		return std::unique_ptr<MemoryMappedFile>(
+		    new MemoryMappedFile(nullptr, 0));
+	}
+
+	void* file_map = mmap(NULL, statbuf.st_size, PROT_READ,
+	                      MAP_PRIVATE | MAP_POPULATE, fd, 0);
+
+	// If mmap() succeeded, the fd is no longer needed as the mapping will
+	// keep the file open.  If mmap() failed, the fd needs to be closed
+	// anyway.
+	close(fd);
+
+	if (file_map == MAP_FAILED)
+		return nullptr;
+
+	return std::unique_ptr<MemoryMappedFile>(
+	    new MemoryMappedFile(file_map, statbuf.st_size));
+}
+
+MemoryMappedFile::~MemoryMappedFile() {
+	munmap(data_, size_);
+}
+
+}  // namespace prjxray
diff --git a/lib/memory_mapped_file_test.cc b/lib/memory_mapped_file_test.cc
new file mode 100644
index 0000000..6a4477d
--- /dev/null
+++ b/lib/memory_mapped_file_test.cc
@@ -0,0 +1,21 @@
+#include <prjxray/memory_mapped_file.h>
+
+#include <gtest/gtest.h>
+
+TEST(MemoryMappedFileTest, NonExistantFile) {
+	EXPECT_FALSE(prjxray::MemoryMappedFile::InitWithFile("does_not_exist"));
+}
+
+TEST(MemoryMappedFileTest, ZeroLengthFileReturnObjectWithZeroLength) {
+	auto file = prjxray::MemoryMappedFile::InitWithFile("empty_file");
+	ASSERT_TRUE(file);
+	EXPECT_EQ(nullptr, file->data());
+	EXPECT_EQ(static_cast<size_t>(0), file->size());
+}
+
+TEST(MemoryMappedFileTest, ExistingFile) {
+	auto file = prjxray::MemoryMappedFile::InitWithFile("small_file");
+	ASSERT_TRUE(file);
+	EXPECT_EQ(static_cast<size_t>(4), file->size());
+	EXPECT_EQ(0, memcmp("foo\n", file->data(), 4));
+}
diff --git a/lib/segbits_file_reader.cc b/lib/segbits_file_reader.cc
new file mode 100644
index 0000000..b06b674
--- /dev/null
+++ b/lib/segbits_file_reader.cc
@@ -0,0 +1,58 @@
+#include <prjxray/segbits_file_reader.h>
+
+namespace prjxray {
+
+std::unique_ptr<SegbitsFileReader> SegbitsFileReader::InitWithFile(
+    const std::string& path) {
+	auto mapped_file = MemoryMappedFile::InitWithFile(path);
+	if (!mapped_file)
+		return nullptr;
+
+	return std::unique_ptr<SegbitsFileReader>(
+	    new SegbitsFileReader(std::move(mapped_file)));
+}
+
+SegbitsFileReader::iterator SegbitsFileReader::begin() {
+	return iterator(
+	    absl::string_view(static_cast<const char*>(mapped_file_->data()),
+	                      mapped_file_->size()));
+}
+
+SegbitsFileReader::iterator SegbitsFileReader::end() {
+	return iterator(absl::string_view());
+}
+
+SegbitsFileReader::value_type::value_type(const absl::string_view& view) {
+	size_t separator_start = view.find_first_of(" \t\n");
+	if (separator_start == absl::string_view::npos) {
+		tag_ = view;
+		bit_ = absl::string_view();
+		return;
+	}
+
+	size_t bit_start = view.find_first_not_of(" \t", separator_start);
+	size_t newline = view.find('\n', bit_start);
+	if (newline == absl::string_view::npos) {
+		tag_ = view.substr(0, separator_start);
+		bit_ = view.substr(bit_start);
+		return;
+	}
+
+	size_t bit_len = newline - bit_start;
+	tag_ = view.substr(0, separator_start);
+	bit_ = view.substr(bit_start, bit_len);
+	return;
+}
+
+SegbitsFileReader::iterator& SegbitsFileReader::iterator::operator++() {
+	size_t newline = view_.find('\n');
+	if (newline == absl::string_view::npos) {
+		view_ = absl::string_view();
+	}
+
+	view_.remove_prefix(newline + 1);
+	value_ = value_type(view_);
+	return *this;
+}
+
+}  // namespace prjxray
diff --git a/lib/segbits_file_reader_test.cc b/lib/segbits_file_reader_test.cc
new file mode 100644
index 0000000..3ac90e1
--- /dev/null
+++ b/lib/segbits_file_reader_test.cc
@@ -0,0 +1,83 @@
+#include <prjxray/segbits_file_reader.h>
+
+#include <map>
+#include <string>
+
+#include <gtest/gtest.h>
+
+TEST(SegbitsFileReaderTest, NonExistantFileReturnsNull) {
+	EXPECT_FALSE(
+	    prjxray::SegbitsFileReader::InitWithFile("does_not_exist"));
+}
+
+TEST(SegbitsFileReaderTest, ZeroLengthFileYieldsNoItems) {
+	auto segbits_reader =
+	    prjxray::SegbitsFileReader::InitWithFile("empty_file");
+	ASSERT_TRUE(segbits_reader);
+
+	EXPECT_EQ(segbits_reader->begin(), segbits_reader->end());
+}
+
+TEST(SegbitsFileReaderTest, FileWithOneEntry) {
+	auto segbits_reader =
+	    prjxray::SegbitsFileReader::InitWithFile("one_entry.segbits");
+	ASSERT_TRUE(segbits_reader);
+
+	auto begin_iter = segbits_reader->begin();
+	EXPECT_EQ(begin_iter->tag(), "CLBLL_L.SLICEL_X0.A5FF.ZINI");
+	EXPECT_EQ(begin_iter->bit(), "31_06");
+
+	EXPECT_EQ(++begin_iter, segbits_reader->end());
+}
+
+TEST(SegbitsFileReaderTest, FileWithOneEntryWithEmptyTag) {
+	auto segbits_reader = prjxray::SegbitsFileReader::InitWithFile(
+	    "one_entry_empty_tag.segbits");
+	ASSERT_TRUE(segbits_reader);
+
+	auto begin_iter = segbits_reader->begin();
+	EXPECT_EQ(begin_iter->tag(), "");
+	EXPECT_EQ(begin_iter->bit(), "31_06");
+
+	EXPECT_EQ(++begin_iter, segbits_reader->end());
+}
+
+TEST(SegbitsFileReaderTest, FileWithOneEntryMissingBit) {
+	auto segbits_reader = prjxray::SegbitsFileReader::InitWithFile(
+	    "one_entry_missing_bit.segbits");
+	ASSERT_TRUE(segbits_reader);
+
+	auto begin_iter = segbits_reader->begin();
+	EXPECT_EQ(begin_iter->tag(), "CLBLL_L.SLICEL_X0.A5FF.ZINI");
+	EXPECT_EQ(begin_iter->bit(), "");
+
+	EXPECT_EQ(++begin_iter, segbits_reader->end());
+}
+
+TEST(SegbitsFileReaderTest, FileWithOneEntryWithExtraWhitespace) {
+	auto segbits_reader = prjxray::SegbitsFileReader::InitWithFile(
+	    "one_entry_extra_whitespace.segbits");
+	ASSERT_TRUE(segbits_reader);
+
+	auto begin_iter = segbits_reader->begin();
+	EXPECT_EQ(begin_iter->tag(), "CLBLL_L.SLICEL_X0.A5FF.ZINI");
+	EXPECT_EQ(begin_iter->bit(), "31_06");
+
+	EXPECT_EQ(++begin_iter, segbits_reader->end());
+}
+
+TEST(SegbitsFileReaderTest, FileWithTwoEntries) {
+	auto segbits_reader =
+	    prjxray::SegbitsFileReader::InitWithFile("two_entries.segbits");
+	ASSERT_TRUE(segbits_reader);
+
+	auto iter = segbits_reader->begin();
+	EXPECT_EQ(iter->tag(), "CLBLL_L.SLICEL_X0.A5FF.ZINI");
+	EXPECT_EQ(iter->bit(), "31_06");
+
+	++iter;
+	EXPECT_EQ(iter->tag(), "CLBLL_L.SLICEL_X0.AFF.ZINI");
+	EXPECT_EQ(iter->bit(), "31_03");
+
+	EXPECT_EQ(++iter, segbits_reader->end());
+}
diff --git a/lib/test_data/configuration_test.bit b/lib/test_data/configuration_test.bit
new file mode 100644
index 0000000..6ef4f5e
--- /dev/null
+++ b/lib/test_data/configuration_test.bit
Binary files differ
diff --git a/lib/test_data/configuration_test.debug.bit b/lib/test_data/configuration_test.debug.bit
new file mode 100644
index 0000000..3dfd748
--- /dev/null
+++ b/lib/test_data/configuration_test.debug.bit
Binary files differ
diff --git a/lib/test_data/configuration_test.perframecrc.bit b/lib/test_data/configuration_test.perframecrc.bit
new file mode 100644
index 0000000..7179e2d
--- /dev/null
+++ b/lib/test_data/configuration_test.perframecrc.bit
Binary files differ
diff --git a/lib/test_data/configuration_test.yaml b/lib/test_data/configuration_test.yaml
new file mode 100644
index 0000000..2666355
--- /dev/null
+++ b/lib/test_data/configuration_test.yaml
@@ -0,0 +1,1680 @@
+!<xilinx/xc7series/part>
+idcode: 0x362c093
+configuration_ranges:
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 0
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 0
+      minor: 42
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 1
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 1
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 2
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 2
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 3
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 3
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 4
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 4
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 5
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 5
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 6
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 6
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 7
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 7
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 8
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 8
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 9
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 9
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 10
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 10
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 11
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 11
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 12
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 12
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 13
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 13
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 14
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 14
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 15
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 15
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 16
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 16
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 17
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 17
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 18
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 18
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 19
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 19
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 20
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 20
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 21
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 21
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 22
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 22
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 23
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 23
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 24
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 24
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 25
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 25
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 26
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 26
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 27
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 27
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 28
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 28
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 29
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 29
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 30
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 30
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 31
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 31
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 32
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 32
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 33
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 33
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 34
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 34
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 35
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 35
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 36
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 36
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 37
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 37
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 38
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 38
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 39
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 39
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 40
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 40
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 41
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 41
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 42
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 42
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 43
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 0
+      column: 43
+      minor: 42
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 0
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 0
+      minor: 42
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 1
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 1
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 2
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 2
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 3
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 3
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 4
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 4
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 5
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 5
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 6
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 6
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 7
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 7
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 8
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 8
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 9
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 9
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 10
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 10
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 11
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 11
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 12
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 12
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 13
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 13
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 14
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 14
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 15
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 15
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 16
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 16
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 17
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 17
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 18
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 18
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 19
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 19
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 20
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 20
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 21
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 21
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 22
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 22
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 23
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 23
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 24
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 24
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 25
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 25
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 26
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 26
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 27
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 27
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 28
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 28
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 29
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 29
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 30
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 30
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 31
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 31
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 32
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 32
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 33
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 33
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 34
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 34
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 35
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 35
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 36
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 36
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 37
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: top
+      row: 1
+      column: 37
+      minor: 32
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 0
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 0
+      minor: 42
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 1
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 1
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 2
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 2
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 3
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 3
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 4
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 4
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 5
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 5
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 6
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 6
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 7
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 7
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 8
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 8
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 9
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 9
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 10
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 10
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 11
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 11
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 12
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 12
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 13
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 13
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 14
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 14
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 15
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 15
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 16
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 16
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 17
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 17
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 18
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 18
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 19
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 19
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 20
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 20
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 21
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 21
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 22
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 22
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 23
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 23
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 24
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 24
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 25
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 25
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 26
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 26
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 27
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 27
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 28
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 28
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 29
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 29
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 30
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 30
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 31
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 31
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 32
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 32
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 33
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 33
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 34
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 34
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 35
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 35
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 36
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 36
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 37
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 37
+      minor: 28
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 38
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 38
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 39
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 39
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 40
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 40
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 41
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 41
+      minor: 36
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 42
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 42
+      minor: 30
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 43
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: CLB_IO_CLK
+      row_half: bottom
+      row: 0
+      column: 43
+      minor: 42
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: BLOCK_RAM
+      row_half: top
+      row: 0
+      column: 0
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: BLOCK_RAM
+      row_half: top
+      row: 0
+      column: 3
+      minor: 0
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: BLOCK_RAM
+      row_half: top
+      row: 1
+      column: 0
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: BLOCK_RAM
+      row_half: top
+      row: 1
+      column: 2
+      minor: 0
+  - !<xilinx/xc7series/configuration_frame_range>
+    begin: !<xilinx/xc7series/configuration_frame_address>
+      block_type: BLOCK_RAM
+      row_half: bottom
+      row: 0
+      column: 0
+      minor: 0
+    end: !<xilinx/xc7series/configuration_frame_address>
+      block_type: BLOCK_RAM
+      row_half: bottom
+      row: 0
+      column: 3
+      minor: 0
diff --git a/lib/test_data/empty_file b/lib/test_data/empty_file
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/test_data/empty_file
diff --git a/lib/test_data/one_entry.segbits b/lib/test_data/one_entry.segbits
new file mode 100644
index 0000000..eee8c45
--- /dev/null
+++ b/lib/test_data/one_entry.segbits
@@ -0,0 +1 @@
+CLBLL_L.SLICEL_X0.A5FF.ZINI 31_06
diff --git a/lib/test_data/one_entry_empty_tag.segbits b/lib/test_data/one_entry_empty_tag.segbits
new file mode 100644
index 0000000..cc99b3f
--- /dev/null
+++ b/lib/test_data/one_entry_empty_tag.segbits
@@ -0,0 +1 @@
+ 31_06
diff --git a/lib/test_data/one_entry_extra_whitespace.segbits b/lib/test_data/one_entry_extra_whitespace.segbits
new file mode 100644
index 0000000..12d1135
--- /dev/null
+++ b/lib/test_data/one_entry_extra_whitespace.segbits
@@ -0,0 +1 @@
+CLBLL_L.SLICEL_X0.A5FF.ZINI 	 31_06
diff --git a/lib/test_data/one_entry_missing_bit.segbits b/lib/test_data/one_entry_missing_bit.segbits
new file mode 100644
index 0000000..99a8a8e
--- /dev/null
+++ b/lib/test_data/one_entry_missing_bit.segbits
@@ -0,0 +1 @@
+CLBLL_L.SLICEL_X0.A5FF.ZINI
diff --git a/lib/test_data/small_file b/lib/test_data/small_file
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/lib/test_data/small_file
@@ -0,0 +1 @@
+foo
diff --git a/lib/test_data/two_entries.segbits b/lib/test_data/two_entries.segbits
new file mode 100644
index 0000000..cc6ca46
--- /dev/null
+++ b/lib/test_data/two_entries.segbits
@@ -0,0 +1,2 @@
+CLBLL_L.SLICEL_X0.A5FF.ZINI 31_06
+CLBLL_L.SLICEL_X0.AFF.ZINI 31_03
diff --git a/lib/xilinx/bitstream_writer.cc b/lib/xilinx/bitstream_writer.cc
new file mode 100644
index 0000000..809c06f
--- /dev/null
+++ b/lib/xilinx/bitstream_writer.cc
@@ -0,0 +1,100 @@
+#include <iostream>
+
+#include <prjxray/bit_ops.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_writer.h>
+
+namespace prjxray {
+namespace xilinx {
+
+template <>
+// Per UG380 pg 78: Bus Width Auto Detection
+typename BitstreamWriter<Spartan6>::header_t BitstreamWriter<Spartan6>::header_{
+    0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+    0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566};
+
+// Per UG470 pg 80: Bus Width Auto Detection
+template <>
+typename BitstreamWriter<Series7>::header_t BitstreamWriter<Series7>::header_{
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566};
+
+template <>
+typename BitstreamWriter<UltraScale>::header_t
+    BitstreamWriter<UltraScale>::header_{0xFFFFFFFF, 0x000000BB, 0x11220044,
+                                         0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566};
+
+template <>
+typename BitstreamWriter<UltraScalePlus>::header_t
+    BitstreamWriter<UltraScalePlus>::header_{
+        0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044,
+        0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566};
+
+uint32_t packet2header(
+    const ConfigurationPacket<Spartan6ConfigurationRegister>& packet) {
+	uint32_t ret = 0;
+
+	ret = bit_field_set(ret, 15, 13, packet.header_type());
+
+	switch (packet.header_type()) {
+		case NONE:
+			// Bitstreams are 0 padded sometimes, essentially making
+			// a type 0 frame Ignore the other fields for now
+			break;
+		case TYPE1: {
+			// Table 5-20: Type 1 Packet Header Format
+			ret = bit_field_set(ret, 12, 11, packet.opcode());
+			ret = bit_field_set(ret, 10, 5, packet.address());
+			ret = bit_field_set(ret, 4, 0, packet.data().length());
+			break;
+		}
+		case TYPE2: {
+			// Table 5-22: Type 2 Packet Header
+			ret = bit_field_set(ret, 12, 11, packet.opcode());
+			ret = bit_field_set(ret, 10, 5, packet.address());
+			break;
+		}
+		default:
+			break;
+	}
+
+	return ret;
+}
+
+uint32_t packet2header(
+    const ConfigurationPacket<Series7ConfigurationRegister>& packet) {
+	uint32_t ret = 0;
+
+	ret = bit_field_set(ret, 31, 29, packet.header_type());
+
+	switch (packet.header_type()) {
+		case NONE:
+			// Bitstreams are 0 padded sometimes, essentially making
+			// a type 0 frame Ignore the other fields for now
+			break;
+		case TYPE1: {
+			// Table 5-20: Type 1 Packet Header Format
+			ret = bit_field_set(ret, 28, 27, packet.opcode());
+			ret = bit_field_set(ret, 26, 13, packet.address());
+			ret = bit_field_set(ret, 10, 0, packet.data().length());
+			break;
+		}
+		case TYPE2: {
+			// Table 5-22: Type 2 Packet Header
+			// Note address is from previous type 1 header
+			ret = bit_field_set(ret, 28, 27, packet.opcode());
+			ret = bit_field_set(ret, 26, 0, packet.data().length());
+			break;
+		}
+		default:
+			break;
+	}
+
+	return ret;
+}
+
+}  // namespace xilinx
+}  // namespace prjxray
diff --git a/lib/xilinx/configuration.cc b/lib/xilinx/configuration.cc
new file mode 100644
index 0000000..d7d6626
--- /dev/null
+++ b/lib/xilinx/configuration.cc
@@ -0,0 +1,785 @@
+#include <fstream>
+#include <iostream>
+
+#include <absl/strings/str_cat.h>
+#include <absl/strings/str_split.h>
+#include <absl/time/clock.h>
+#include <absl/time/time.h>
+
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_writer.h>
+#include <prjxray/xilinx/configuration.h>
+#include <prjxray/xilinx/configuration_packet_with_payload.h>
+#include <prjxray/xilinx/nop_packet.h>
+#include <prjxray/xilinx/spartan6/command.h>
+#include <prjxray/xilinx/xc7series/command.h>
+#include <prjxray/xilinx/xc7series/configuration_options_0_value.h>
+
+namespace prjxray {
+namespace xilinx {
+
+template <>
+Configuration<Spartan6>::PacketData
+Configuration<Spartan6>::createType2ConfigurationPacketData(
+    const Frames<Spartan6>::Frames2Data& frames,
+    absl::optional<Spartan6::Part>& part) {
+	// Generate a single type 2 packet that writes everything at once.
+	PacketData packet_data;
+	for (auto& frame : frames) {
+		std::copy(frame.second.begin(), frame.second.end(),
+		          std::back_inserter(packet_data));
+	}
+
+	// Insert payload length
+	size_t packet_data_size = packet_data.size() - 2;
+	packet_data.insert(packet_data.begin(), packet_data_size & 0xFFFF);
+	packet_data.insert(packet_data.begin(),
+	                   (packet_data_size >> 16) & 0xFFFF);
+	return packet_data;
+}
+
+template <>
+void Configuration<Spartan6>::createConfigurationPackage(
+    Spartan6::ConfigurationPackage& out_packets,
+    const PacketData& packet_data,
+    absl::optional<Spartan6::Part>& part) {
+	using ArchType = Spartan6;
+	using ConfigurationRegister = ArchType::ConfRegType;
+	// Initialization sequence
+	//
+	// Reset CRC
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(spartan6::Command::RCRC)}));
+
+	// NOP
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+
+	// Frame length
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FLR, {0x0380}));
+
+	// Configuration Options 1
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::COR1, {0x3d08}));
+
+	// Configurations Options2
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::COR2, {0x9ee}));
+
+	// IDCODE
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<2, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::IDCODE,
+	        {part->idcode() >> 16, part->idcode()}));
+
+	// Control MASK
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0xcf}));
+
+	// Control options
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL, {0x81}));
+
+	// NOP packets
+	for (int i = 0; i < 17; i++) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+
+	// CCLK FREQ
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CCLK_FREQ, {0x3cc8}));
+
+	// PWRDN_REG
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::PWRDN_REG, {0x881}));
+
+	// EYE MASK
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::EYE_MASK, {0x0}));
+
+	// House Clean Option
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::HC_OPT_REG, {0x1f}));
+
+	// Configuration Watchdog Timer
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CWDT, {0xffff}));
+
+	// GWE cycle during wake-up from suspend
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::PU_GWE, {0x5}));
+
+	// GTS cycle during wake-up from suspend
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::PU_GTS, {0x4}));
+
+	// Reboot mode
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MODE_REG, {0x100}));
+
+	// General options 1
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::GENERAL1, {0x0}));
+
+	// General options 2
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::GENERAL2, {0x0}));
+
+	// General options 3
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::GENERAL3, {0x0}));
+
+	// General options 4
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::GENERAL4, {0x0}));
+
+	// General options 5
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::GENERAL5, {0x0}));
+
+	// SEU frequency, enable and status
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::SEU_OPT, {0x1be2}));
+
+	// Expected readback signature for SEU detection
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<2, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::EXP_SIGN, {0x0, 0x0}));
+
+	// NOP
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+
+	// FAR
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<2, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR_MAJ, {0x0, 0x0}));
+
+	// Write Configuration Data
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(spartan6::Command::WCFG)}));
+
+	// Frame data write
+	out_packets.emplace_back(new ConfigurationPacket<ConfigurationRegister>(
+	    TYPE2, ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::FDRI, {packet_data}));
+
+	// NOP packets
+	for (int i = 0; i < 24; i++) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+
+	// Finalization sequence
+	//
+	// Set/reset the IOB and CLB flip-flops
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(spartan6::Command::GRESTORE)}));
+
+	// Last Frame
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(spartan6::Command::LFRM)}));
+
+	// NOP packets
+	for (int i = 0; i < 4; i++) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+
+	// Set/reset the IOB and CLB flip-flops
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(spartan6::Command::GRESTORE)}));
+
+	// Startup sequence
+	//
+	// Start
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(spartan6::Command::START)}));
+
+	// Control MASK
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0xff}));
+
+	// Control options
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL, {0x81}));
+
+	// CRC
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<2, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CRC, {0x39, 0xe423}));
+
+	// Desync
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(spartan6::Command::DESYNC)}));
+
+	// NOP packets
+	for (int i = 0; i < 14; i++) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+}
+
+template <>
+void Configuration<Series7>::createConfigurationPackage(
+    Series7::ConfigurationPackage& out_packets,
+    const PacketData& packet_data,
+    absl::optional<Series7::Part>& part) {
+	using ArchType = Series7;
+	using ConfigurationRegister = ArchType::ConfRegType;
+	// Initialization sequence
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::TIMER, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::WBSTAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::NOP)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::UNKNOWN, {0x0}));
+
+	// Configuration Options 0
+	out_packets.emplace_back(new ConfigurationPacketWithPayload<
+	                         1, ConfigurationRegister>(
+	    ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::COR0,
+	    {xc7series::ConfigurationOptions0Value()
+	         .SetAddPipelineStageForDoneIn(true)
+	         .SetReleaseDonePinAtStartupCycle(
+	             xc7series::ConfigurationOptions0Value::SignalReleaseCycle::
+	                 Phase4)
+	         .SetStallAtStartupCycleUntilDciMatch(
+	             xc7series::ConfigurationOptions0Value::StallCycle::NoWait)
+	         .SetStallAtStartupCycleUntilMmcmLock(
+	             xc7series::ConfigurationOptions0Value::StallCycle::NoWait)
+	         .SetReleaseGtsSignalAtStartupCycle(
+	             xc7series::ConfigurationOptions0Value::SignalReleaseCycle::
+	                 Phase5)
+	         .SetReleaseGweSignalAtStartupCycle(
+	             xc7series::ConfigurationOptions0Value::SignalReleaseCycle::
+	                 Phase6)}));
+
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::COR1, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::IDCODE, {part->idcode()}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::SWITCH)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x401}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL0, {0x501}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL1, {0x0}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::WCFG)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+
+	// Frame data write
+	out_packets.emplace_back(new ConfigurationPacket<ConfigurationRegister>(
+	    TYPE1, ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::FDRI, {}));
+	out_packets.emplace_back(new ConfigurationPacket<ConfigurationRegister>(
+	    TYPE2, ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::FDRI, packet_data));
+
+	// Finalization sequence
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::GRESTORE)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::LFRM)}));
+	for (int ii = 0; ii < 100; ++ii) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::START)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x3be0000}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x501}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL0, {0x501}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::DESYNC)}));
+	for (int ii = 0; ii < 400; ++ii) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+}
+
+template <>
+void Configuration<UltraScale>::createConfigurationPackage(
+    UltraScale::ConfigurationPackage& out_packets,
+    const PacketData& packet_data,
+    absl::optional<UltraScale::Part>& part) {
+	using ArchType = UltraScale;
+	using ConfigurationRegister = ArchType::ConfRegType;
+	// Initialization sequence
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::TIMER, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::WBSTAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::NOP)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::UNKNOWN, {0x0}));
+
+	// Configuration Options 0
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::COR0, {0x38003fe5}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::COR1, {0x400000}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::IDCODE, {part->idcode()}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::SWITCH)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x1}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL0, {0x101}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL1, {0x0}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::WCFG)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+
+	// Frame data write
+	out_packets.emplace_back(new ConfigurationPacket<ConfigurationRegister>(
+	    TYPE1, ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::FDRI, {}));
+	out_packets.emplace_back(new ConfigurationPacket<ConfigurationRegister>(
+	    TYPE2, ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::FDRI, packet_data));
+
+	// Finalization sequence
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::GRESTORE)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::LFRM)}));
+	for (int ii = 0; ii < 100; ++ii) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::START)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x3be0000}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x101}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL0, {0x101}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::DESYNC)}));
+	for (int ii = 0; ii < 400; ++ii) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+}
+
+template <>
+void Configuration<UltraScalePlus>::createConfigurationPackage(
+    UltraScalePlus::ConfigurationPackage& out_packets,
+    const PacketData& packet_data,
+    absl::optional<UltraScalePlus::Part>& part) {
+	using ArchType = UltraScalePlus;
+	using ConfigurationRegister = ArchType::ConfRegType;
+	// Initialization sequence
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::TIMER, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::WBSTAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::NOP)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::UNKNOWN, {0x0}));
+
+	// Configuration Options 0
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::COR0, {0x38003fe5}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::COR1, {0x400000}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::IDCODE, {part->idcode()}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::SWITCH)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x1}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL0, {0x101}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL1, {0x0}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x0}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::WCFG)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+
+	// Frame data write
+	out_packets.emplace_back(new ConfigurationPacket<ConfigurationRegister>(
+	    TYPE1, ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::FDRI, {}));
+	out_packets.emplace_back(new ConfigurationPacket<ConfigurationRegister>(
+	    TYPE2, ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	    ConfigurationRegister::FDRI, packet_data));
+
+	// Finalization sequence
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::GRESTORE)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::LFRM)}));
+	for (int ii = 0; ii < 100; ++ii) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::START)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::FAR, {0x3be0000}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::MASK, {0x101}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CTL0, {0x101}));
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::RCRC)}));
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(new NopPacket<ConfigurationRegister>());
+	out_packets.emplace_back(
+	    new ConfigurationPacketWithPayload<1, ConfigurationRegister>(
+	        ConfigurationPacket<ConfigurationRegister>::Opcode::Write,
+	        ConfigurationRegister::CMD,
+	        {static_cast<uint32_t>(xc7series::Command::DESYNC)}));
+	for (int ii = 0; ii < 400; ++ii) {
+		out_packets.emplace_back(
+		    new NopPacket<ConfigurationRegister>());
+	}
+}
+}  // namespace xilinx
+}  // namespace prjxray
diff --git a/lib/xilinx/configuration_packet.cc b/lib/xilinx/configuration_packet.cc
new file mode 100644
index 0000000..14d66b0
--- /dev/null
+++ b/lib/xilinx/configuration_packet.cc
@@ -0,0 +1,220 @@
+#include <prjxray/xilinx/configuration_packet.h>
+
+#include <iomanip>
+#include <iostream>
+#include <ostream>
+
+#include <prjxray/bit_ops.h>
+
+namespace prjxray {
+namespace xilinx {
+
+template <>
+std::pair<absl::Span<uint32_t>,
+          absl::optional<ConfigurationPacket<Spartan6ConfigurationRegister>>>
+ConfigurationPacket<Spartan6ConfigurationRegister>::InitWithWords(
+    absl::Span<uint32_t> words,
+    const ConfigurationPacket<Spartan6ConfigurationRegister>* previous_packet) {
+	using ConfigurationRegister = Spartan6ConfigurationRegister;
+	// Need at least one 32-bit word to have a valid packet header.
+	if (words.size() < 1)
+		return {words, {}};
+
+	uint32_t header_type = bit_field_get(words[0], 15, 13);
+	switch (header_type) {
+		case NONE:
+			// Type 0 is emitted at the end of a configuration row
+			// when BITSTREAM.GENERAL.DEBUGBITSTREAM is set to YES.
+			// These seem to be padding that are interepreted as
+			// NOPs.  Since Type 0 packets don't exist according to
+			// UG470 and they seem to be zero-filled, just consume
+			// the bytes without generating a packet.
+			return {words.subspan(1),
+			        {{header_type,
+			          Opcode::NOP,
+			          ConfigurationRegister::CRC,
+			          {}}}};
+		case TYPE1: {
+			Opcode opcode = static_cast<Opcode>(
+			    bit_field_get(words[0], 12, 11));
+			ConfigurationRegister address =
+			    static_cast<ConfigurationRegister>(
+			        bit_field_get(words[0], 10, 5));
+			uint32_t data_word_count =
+			    bit_field_get(words[0], 4, 0);
+
+			// If the full packet has not been received, return as
+			// though no valid packet was found.
+			if (data_word_count > words.size() - 1) {
+				return {words, {}};
+			}
+
+			return {words.subspan(data_word_count + 1),
+			        {{header_type, opcode, address,
+			          words.subspan(1, data_word_count)}}};
+		}
+		case TYPE2: {
+			absl::optional<ConfigurationPacket> packet;
+			Opcode opcode = static_cast<Opcode>(
+			    bit_field_get(words[0], 12, 11));
+			ConfigurationRegister address =
+			    static_cast<ConfigurationRegister>(
+			        bit_field_get(words[0], 10, 5));
+			// Type 2 packets according to UG380 consist of
+			// a header word followed by 2 WCD (Word Count Data)
+			// words
+			uint32_t data_word_count = (words[1] << 16) | words[2];
+
+			// If the full packet has not been received, return as
+			// though no valid packet was found.
+			if (data_word_count > words.size() - 1) {
+				return {words, {}};
+			}
+
+			// Create a packet that contains as many data words
+			// as specified in the WCD packets, but omit them
+			// in the configuration packet along with the header
+			// FIXME Figure out why we need the extra 2 words
+			packet = ConfigurationPacket(
+			    header_type, opcode, address,
+			    words.subspan(3, data_word_count + 2));
+
+			return {words.subspan(data_word_count + 3), packet};
+		}
+		default:
+			return {{}, {}};
+	}
+}
+
+template <>
+std::pair<absl::Span<uint32_t>,
+          absl::optional<ConfigurationPacket<Series7ConfigurationRegister>>>
+ConfigurationPacket<Series7ConfigurationRegister>::InitWithWords(
+    absl::Span<uint32_t> words,
+    const ConfigurationPacket<Series7ConfigurationRegister>* previous_packet) {
+	using ConfigurationRegister = Series7ConfigurationRegister;
+	// Need at least one 32-bit word to have a valid packet header.
+	if (words.size() < 1)
+		return {words, {}};
+
+	uint32_t header_type = bit_field_get(words[0], 31, 29);
+	switch (header_type) {
+		case NONE:
+			// Type 0 is emitted at the end of a configuration row
+			// when BITSTREAM.GENERAL.DEBUGBITSTREAM is set to YES.
+			// These seem to be padding that are interepreted as
+			// NOPs.  Since Type 0 packets don't exist according to
+			// UG470 and they seem to be zero-filled, just consume
+			// the bytes without generating a packet.
+			return {words.subspan(1),
+			        {{header_type,
+			          Opcode::NOP,
+			          ConfigurationRegister::CRC,
+			          {}}}};
+		case TYPE1: {
+			Opcode opcode = static_cast<Opcode>(
+			    bit_field_get(words[0], 28, 27));
+			ConfigurationRegister address =
+			    static_cast<ConfigurationRegister>(
+			        bit_field_get(words[0], 26, 13));
+			uint32_t data_word_count =
+			    bit_field_get(words[0], 10, 0);
+
+			// If the full packet has not been received, return as
+			// though no valid packet was found.
+			if (data_word_count > words.size() - 1) {
+				return {words, {}};
+			}
+
+			return {words.subspan(data_word_count + 1),
+			        {{header_type, opcode, address,
+			          words.subspan(1, data_word_count)}}};
+		}
+		case TYPE2: {
+			absl::optional<ConfigurationPacket> packet;
+			Opcode opcode = static_cast<Opcode>(
+			    bit_field_get(words[0], 28, 27));
+			uint32_t data_word_count =
+			    bit_field_get(words[0], 26, 0);
+
+			// If the full packet has not been received, return as
+			// though no valid packet was found.
+			if (data_word_count > words.size() - 1) {
+				return {words, {}};
+			}
+
+			if (previous_packet) {
+				packet = ConfigurationPacket(
+				    header_type, opcode,
+				    previous_packet->address(),
+				    words.subspan(1, data_word_count));
+			}
+
+			return {words.subspan(data_word_count + 1), packet};
+		}
+		default:
+			return {{}, {}};
+	}
+}
+
+template <class ConfigRegType>
+std::ostream& operator<<(std::ostream& o,
+                         const ConfigurationPacket<ConfigRegType>& packet) {
+	if (packet.header_type() == 0x0) {
+		return o << "[Zero-pad]" << std::endl;
+	}
+
+	switch (packet.opcode()) {
+		case ConfigurationPacket<ConfigRegType>::Opcode::NOP:
+			o << "[NOP]" << std::endl;
+			break;
+		case ConfigurationPacket<ConfigRegType>::Opcode::Read:
+			o << "[Read Type=";
+			o << packet.header_type();
+			o << " Address=";
+			o << std::setw(2) << std::hex;
+			o << static_cast<int>(packet.address());
+			o << " Length=";
+			o << std::setw(10) << std::dec << packet.data().size();
+			o << " Reg=\"" << packet.address() << "\"";
+			o << "]" << std::endl;
+			break;
+		case ConfigurationPacket<ConfigRegType>::Opcode::Write:
+			o << "[Write Type=";
+			o << packet.header_type();
+			o << " Address=";
+			o << std::setw(2) << std::hex;
+			o << static_cast<int>(packet.address());
+			o << " Length=";
+			o << std::setw(10) << std::dec << packet.data().size();
+			o << " Reg=\"" << packet.address() << "\"";
+			o << "]" << std::endl;
+			o << "Data in hex:" << std::endl;
+
+			for (size_t ii = 0; ii < packet.data().size(); ++ii) {
+				o << std::setw(8) << std::hex;
+				o << packet.data()[ii] << " ";
+
+				if ((ii + 1) % 4 == 0) {
+					o << std::endl;
+				}
+			}
+			if (packet.data().size() % 4 != 0) {
+				o << std::endl;
+			}
+			break;
+		default:
+			o << "[Invalid Opcode]" << std::endl;
+	}
+
+	return o;
+}
+
+template std::ostream& operator<<(
+    std::ostream&,
+    const ConfigurationPacket<Spartan6ConfigurationRegister>&);
+template std::ostream& operator<<(
+    std::ostream&,
+    const ConfigurationPacket<Series7ConfigurationRegister>&);
+}  // namespace xilinx
+}  // namespace prjxray
diff --git a/lib/xilinx/configuration_register.cc b/lib/xilinx/configuration_register.cc
new file mode 100644
index 0000000..2e49c54
--- /dev/null
+++ b/lib/xilinx/configuration_register.cc
@@ -0,0 +1,137 @@
+#include <prjxray/xilinx/configuration_register.h>
+
+namespace prjxray {
+namespace xilinx {
+
+std::ostream& operator<<(std::ostream& o,
+                         const Spartan6ConfigurationRegister& value) {
+	switch (value) {
+		case Spartan6ConfigurationRegister::CRC:
+			return o << "CRC";
+		case Spartan6ConfigurationRegister::FAR_MAJ:
+			return o << "Frame Address Register Block and Major";
+		case Spartan6ConfigurationRegister::FAR_MIN:
+			return o << "Frame Address Register Minor";
+		case Spartan6ConfigurationRegister::FDRI:
+			return o << "Frame Data Input";
+		case Spartan6ConfigurationRegister::FDRO:
+			return o << "Frame Data Output";
+		case Spartan6ConfigurationRegister::CMD:
+			return o << "Command";
+		case Spartan6ConfigurationRegister::CTL:
+			return o << "Control";
+		case Spartan6ConfigurationRegister::MASK:
+			return o << "Control Mask";
+		case Spartan6ConfigurationRegister::STAT:
+			return o << "Status";
+		case Spartan6ConfigurationRegister::LOUT:
+			return o << "Legacy Output";
+		case Spartan6ConfigurationRegister::COR1:
+			return o << "Configuration Option 1";
+		case Spartan6ConfigurationRegister::COR2:
+			return o << "Configuration Option 2";
+		case Spartan6ConfigurationRegister::PWRDN_REG:
+			return o << "Power-down Option register";
+		case Spartan6ConfigurationRegister::FLR:
+			return o << "Frame Length register";
+		case Spartan6ConfigurationRegister::IDCODE:
+			return o << "Device ID";
+		case Spartan6ConfigurationRegister::CWDT:
+			return o << "Watchdog Timer";
+		case Spartan6ConfigurationRegister::HC_OPT_REG:
+			return o << "House Clean Option register";
+		case Spartan6ConfigurationRegister::CSBO:
+			return o << "CSB output for parallel daisy-chaining";
+		case Spartan6ConfigurationRegister::GENERAL1:
+			return o << "Power-up self test or loadable program "
+			            "address";
+		case Spartan6ConfigurationRegister::GENERAL2:
+			return o << "Power-up self test or loadable program "
+			         << "address and new SPI opcode";
+		case Spartan6ConfigurationRegister::GENERAL3:
+			return o << "Golden bitstream address";
+		case Spartan6ConfigurationRegister::GENERAL4:
+			return o
+			       << "Golden bitstream address and new SPI opcode";
+		case Spartan6ConfigurationRegister::GENERAL5:
+			return o
+			       << "User-defined register for fail-safe scheme";
+		case Spartan6ConfigurationRegister::MODE_REG:
+			return o << "Reboot mode";
+		case Spartan6ConfigurationRegister::PU_GWE:
+			return o << "GWE cycle during wake-up from suspend";
+		case Spartan6ConfigurationRegister::PU_GTS:
+			return o << "GTS cycle during wake-up from suspend";
+		case Spartan6ConfigurationRegister::MFWR:
+			return o << "Multi-frame write register";
+		case Spartan6ConfigurationRegister::CCLK_FREQ:
+			return o << "CCLK frequency for master mode";
+		case Spartan6ConfigurationRegister::SEU_OPT:
+			return o << "SEU frequency, enable and status";
+		case Spartan6ConfigurationRegister::EXP_SIGN:
+			return o << "Expected readback signature for SEU "
+			            "detection";
+		case Spartan6ConfigurationRegister::RDBK_SIGN:
+			return o << "Readback signature for readback command "
+			            "and SEU";
+		case Spartan6ConfigurationRegister::BOOTSTS:
+			return o << "Boot History Register";
+		case Spartan6ConfigurationRegister::EYE_MASK:
+			return o << "Mask pins for Multi-Pin Wake-Up";
+		case Spartan6ConfigurationRegister::CBC_REG:
+			return o << "Initial CBC Value Register";
+		default:
+			return o << "Unknown";
+	}
+}
+
+std::ostream& operator<<(std::ostream& o,
+                         const Series7ConfigurationRegister& value) {
+	switch (value) {
+		case Series7ConfigurationRegister::CRC:
+			return o << "CRC";
+		case Series7ConfigurationRegister::FAR:
+			return o << "Frame Address";
+		case Series7ConfigurationRegister::FDRI:
+			return o << "Frame Data Input";
+		case Series7ConfigurationRegister::FDRO:
+			return o << "Frame Data Output";
+		case Series7ConfigurationRegister::CMD:
+			return o << "Command";
+		case Series7ConfigurationRegister::CTL0:
+			return o << "Control 0";
+		case Series7ConfigurationRegister::MASK:
+			return o << "Mask for CTL0 and CTL1";
+		case Series7ConfigurationRegister::STAT:
+			return o << "Status";
+		case Series7ConfigurationRegister::LOUT:
+			return o << "Legacy Output";
+		case Series7ConfigurationRegister::COR0:
+			return o << "Configuration Option 0";
+		case Series7ConfigurationRegister::MFWR:
+			return o << "Multiple Frame Write";
+		case Series7ConfigurationRegister::CBC:
+			return o << "Initial CBC Value";
+		case Series7ConfigurationRegister::IDCODE:
+			return o << "Device ID";
+		case Series7ConfigurationRegister::AXSS:
+			return o << "User Access";
+		case Series7ConfigurationRegister::COR1:
+			return o << "Configuration Option 1";
+		case Series7ConfigurationRegister::WBSTAR:
+			return o << "Warm Boot Start Address";
+		case Series7ConfigurationRegister::TIMER:
+			return o << "Watchdog Timer";
+		case Series7ConfigurationRegister::BOOTSTS:
+			return o << "Boot History Status";
+		case Series7ConfigurationRegister::CTL1:
+			return o << "Control 1";
+		case Series7ConfigurationRegister::BSPI:
+			return o << "BPI/SPI Configuration Options";
+		default:
+			return o << "Unknown";
+	}
+}
+
+}  // namespace xilinx
+}  // namespace prjxray
diff --git a/lib/xilinx/frames.cc b/lib/xilinx/frames.cc
new file mode 100644
index 0000000..616c723
--- /dev/null
+++ b/lib/xilinx/frames.cc
@@ -0,0 +1,28 @@
+#include <prjxray/xilinx/frames.h>
+#include <prjxray/xilinx/xc7series/ecc.h>
+
+namespace prjxray {
+namespace xilinx {
+template <>
+void Frames<Series7>::updateECC(typename Frames<Series7>::FrameData& data) {
+	xc7series::updateECC(data);
+}
+
+template <>
+void Frames<UltraScale>::updateECC(
+    typename Frames<UltraScale>::FrameData& data) {
+	xc7series::updateECC(data);
+}
+
+template <>
+void Frames<UltraScalePlus>::updateECC(
+    typename Frames<UltraScalePlus>::FrameData& data) {
+	xc7series::updateECC(data);
+}
+
+// Spartan6 doesn't have ECC
+template <>
+void Frames<Spartan6>::updateECC(typename Frames<Spartan6>::FrameData& data) {}
+
+}  // namespace xilinx
+}  // namespace prjxray
diff --git a/lib/xilinx/spartan6/block_type.cc b/lib/xilinx/spartan6/block_type.cc
new file mode 100644
index 0000000..4405972
--- /dev/null
+++ b/lib/xilinx/spartan6/block_type.cc
@@ -0,0 +1,62 @@
+#include <prjxray/xilinx/spartan6/block_type.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+std::ostream& operator<<(std::ostream& o, BlockType value) {
+	switch (value) {
+		case BlockType::CLB_IOI_CLK:
+			o << "CLB/IOI/CLK";
+			break;
+		case BlockType::BLOCK_RAM:
+			o << "Block RAM";
+			break;
+		case BlockType::IOB:
+			o << "Config CLB";
+			break;
+	}
+
+	return o;
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+
+Node convert<prjxray::xilinx::spartan6::BlockType>::encode(
+    const prjxray::xilinx::spartan6::BlockType& rhs) {
+	switch (rhs) {
+		case prjxray::xilinx::spartan6::BlockType::CLB_IOI_CLK:
+			return Node("CLB_IOI_CLK");
+		case prjxray::xilinx::spartan6::BlockType::BLOCK_RAM:
+			return Node("BLOCK_RAM");
+		case prjxray::xilinx::spartan6::BlockType::IOB:
+			return Node("IOB");
+		default:
+			return Node(static_cast<unsigned int>(rhs));
+	}
+}
+
+bool YAML::convert<prjxray::xilinx::spartan6::BlockType>::decode(
+    const Node& node,
+    prjxray::xilinx::spartan6::BlockType& lhs) {
+	auto type_str = node.as<std::string>();
+
+	if (type_str == "CLB_IOI_CLK") {
+		lhs = prjxray::xilinx::spartan6::BlockType::CLB_IOI_CLK;
+		return true;
+	} else if (type_str == "BLOCK_RAM") {
+		lhs = prjxray::xilinx::spartan6::BlockType::BLOCK_RAM;
+		return true;
+	} else if (type_str == "IOB") {
+		lhs = prjxray::xilinx::spartan6::BlockType::IOB;
+		return true;
+	} else {
+		return false;
+	}
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/spartan6/configuration_bus.cc b/lib/xilinx/spartan6/configuration_bus.cc
new file mode 100644
index 0000000..920efd5
--- /dev/null
+++ b/lib/xilinx/spartan6/configuration_bus.cc
@@ -0,0 +1,76 @@
+#include <prjxray/xilinx/spartan6/configuration_bus.h>
+
+#include <iostream>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+bool ConfigurationBus::IsValidFrameAddress(FrameAddress address) const {
+	auto addr_column = configuration_columns_.find(address.column());
+	if (addr_column == configuration_columns_.end())
+		return false;
+
+	return addr_column->second.IsValidFrameAddress(address);
+}
+
+absl::optional<FrameAddress> ConfigurationBus::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Find the column for the current address.
+	auto addr_column = configuration_columns_.find(address.column());
+
+	// If the current address isn't in a known column, no way to know the
+	// next address.
+	if (addr_column == configuration_columns_.end())
+		return {};
+
+	// Ask the column for the next address.
+	absl::optional<FrameAddress> next_address =
+	    addr_column->second.GetNextFrameAddress(address);
+	if (next_address)
+		return next_address;
+
+	// The current column doesn't know what the next address is.  Assume
+	// that the next valid address is the beginning of the next column.
+	if (++addr_column != configuration_columns_.end()) {
+		auto next_address = FrameAddress(
+		    address.block_type(), address.row(), addr_column->first, 0);
+		if (addr_column->second.IsValidFrameAddress(next_address))
+			return next_address;
+	}
+
+	// Not in this bus.
+	return {};
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+namespace YAML {
+
+Node convert<spartan6::ConfigurationBus>::encode(
+    const spartan6::ConfigurationBus& rhs) {
+	Node node;
+	node.SetTag("xilinx/spartan6/configuration_bus");
+	node["configuration_columns"] = rhs.configuration_columns_;
+	return node;
+}
+
+bool convert<spartan6::ConfigurationBus>::decode(
+    const Node& node,
+    spartan6::ConfigurationBus& lhs) {
+	if (!node.Tag().empty() &&
+	    node.Tag() != "xilinx/spartan6/configuration_bus") {
+		return false;
+	}
+
+	lhs.configuration_columns_ =
+	    node["configuration_columns"]
+	        .as<std::map<unsigned int, spartan6::ConfigurationColumn>>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/spartan6/configuration_column.cc b/lib/xilinx/spartan6/configuration_column.cc
new file mode 100644
index 0000000..dcc8422
--- /dev/null
+++ b/lib/xilinx/spartan6/configuration_column.cc
@@ -0,0 +1,52 @@
+#include <prjxray/xilinx/spartan6/configuration_column.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+bool ConfigurationColumn::IsValidFrameAddress(FrameAddress address) const {
+	return address.minor() < frame_count_;
+}
+
+absl::optional<FrameAddress> ConfigurationColumn::GetNextFrameAddress(
+    FrameAddress address) const {
+	if (!IsValidFrameAddress(address))
+		return {};
+
+	if (static_cast<unsigned int>(address.minor() + 1) < frame_count_) {
+		return address + 1;
+	}
+
+	// Next address is not in this column.
+	return {};
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+namespace YAML {
+
+Node convert<spartan6::ConfigurationColumn>::encode(
+    const spartan6::ConfigurationColumn& rhs) {
+	Node node;
+	node.SetTag("xilinx/spartan6/configuration_column");
+	node["frame_count"] = rhs.frame_count_;
+	return node;
+}
+
+bool convert<spartan6::ConfigurationColumn>::decode(
+    const Node& node,
+    spartan6::ConfigurationColumn& lhs) {
+	if (!node.Tag().empty() &&
+	    node.Tag() != "xilinx/spartan6/configuration_column") {
+		return false;
+	}
+
+	lhs.frame_count_ = node["frame_count"].as<unsigned int>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/spartan6/configuration_row.cc b/lib/xilinx/spartan6/configuration_row.cc
new file mode 100644
index 0000000..bb347e7
--- /dev/null
+++ b/lib/xilinx/spartan6/configuration_row.cc
@@ -0,0 +1,62 @@
+#include <prjxray/xilinx/spartan6/configuration_row.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+bool Row::IsValidFrameAddress(FrameAddress address) const {
+	auto addr_bus = configuration_buses_.find(address.block_type());
+	if (addr_bus == configuration_buses_.end())
+		return false;
+	return addr_bus->second.IsValidFrameAddress(address);
+}
+
+absl::optional<FrameAddress> Row::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Find the bus for the current address.
+	auto addr_bus = configuration_buses_.find(address.block_type());
+
+	// If the current address isn't in a known bus, no way to know the next.
+	if (addr_bus == configuration_buses_.end())
+		return {};
+
+	// Ask the bus for the next address.
+	absl::optional<FrameAddress> next_address =
+	    addr_bus->second.GetNextFrameAddress(address);
+	if (next_address)
+		return next_address;
+
+	// The current bus doesn't know what the next address is. Rows come next
+	// in frame address numerical order so punt back to the caller to figure
+	// it out.
+	return {};
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+namespace YAML {
+
+Node convert<spartan6::Row>::encode(const spartan6::Row& rhs) {
+	Node node;
+	node.SetTag("xilinx/spartan6/row");
+	node["configuration_buses"] = rhs.configuration_buses_;
+	return node;
+}
+
+bool convert<spartan6::Row>::decode(const Node& node, spartan6::Row& lhs) {
+	if (!node.Tag().empty() && node.Tag() != "xilinx/spartan6/row") {
+		return false;
+	}
+
+	lhs.configuration_buses_ =
+	    node["configuration_buses"]
+	        .as<std::map<spartan6::BlockType,
+	                     spartan6::ConfigurationBus>>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/spartan6/frame_address.cc b/lib/xilinx/spartan6/frame_address.cc
new file mode 100644
index 0000000..ff5bcee
--- /dev/null
+++ b/lib/xilinx/spartan6/frame_address.cc
@@ -0,0 +1,92 @@
+#include <iomanip>
+
+#include <prjxray/bit_ops.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+// According to UG380 pg. 101 the Frame Address Register (FAR)
+// consists of two 16-bit registers (FAR MAJOR and FAR MINOR).
+// We construct the 32-bit frame address from these two.
+FrameAddress::FrameAddress(spartan6::BlockType block_type,
+                           uint8_t row,
+                           uint8_t column,
+                           uint16_t minor) {
+	address_ = bit_field_set(0, 31, 28, block_type);
+	address_ =
+	    bit_field_set(address_, 27, 24, row);  // high register, bit 8-11
+	address_ =
+	    bit_field_set(address_, 23, 16, column);  // high register, bits 0-7
+	address_ =
+	    bit_field_set(address_, 9, 0, minor);  // low register, bit 0-9
+}
+
+bool FrameAddress::is_bottom_half_rows() const {
+	return false;
+}
+
+spartan6::BlockType FrameAddress::block_type() const {
+	return static_cast<typename spartan6::BlockType>(
+	    bit_field_get(address_, 31, 28));
+}
+
+uint8_t FrameAddress::row() const {
+	return bit_field_get(address_, 27, 24);
+}
+
+uint8_t FrameAddress::column() const {
+	return bit_field_get(address_, 23, 16);
+}
+
+uint16_t FrameAddress::minor() const {
+	return bit_field_get(address_, 9, 0);
+}
+
+std::ostream& operator<<(std::ostream& o, const FrameAddress& addr) {
+	o << "[" << std::hex << std::showbase << std::setw(10)
+	  << static_cast<uint32_t>(addr) << "] "
+	  << " Row=" << std::setw(2) << std::dec
+	  << static_cast<unsigned int>(addr.row()) << "Column =" << std::setw(2)
+	  << std::dec << addr.column() << " Minor=" << std::setw(2) << std::dec
+	  << static_cast<unsigned int>(addr.minor())
+	  << " Type=" << addr.block_type();
+	return o;
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+Node convert<spartan6::FrameAddress>::encode(
+    const spartan6::FrameAddress& rhs) {
+	Node node;
+	node.SetTag("xilinx/spartan6/frame_address");
+	node["block_type"] = rhs.block_type();
+	node["row"] = static_cast<unsigned int>(rhs.row());
+	node["column"] = static_cast<unsigned int>(rhs.column());
+	node["minor"] = static_cast<unsigned int>(rhs.minor());
+	return node;
+}
+
+bool convert<spartan6::FrameAddress>::decode(const Node& node,
+                                             spartan6::FrameAddress& lhs) {
+	if (!(node.Tag() == "xilinx/spartan6/frame_address" ||
+	      node.Tag() == "xilinx/spartan6/configuration_frame_address") ||
+	    !node["block_type"] || !node["row"] || !node["column"] ||
+	    !node["minor"])
+		return false;
+
+	lhs = spartan6::FrameAddress(
+	    node["block_type"].as<spartan6::BlockType>(),
+	    node["row"].as<unsigned int>(), node["column"].as<unsigned int>(),
+	    node["minor"].as<unsigned int>());
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/spartan6/global_clock_region.cc b/lib/xilinx/spartan6/global_clock_region.cc
new file mode 100644
index 0000000..55e194e
--- /dev/null
+++ b/lib/xilinx/spartan6/global_clock_region.cc
@@ -0,0 +1,70 @@
+#include <prjxray/xilinx/spartan6/global_clock_region.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+bool GlobalClockRegion::IsValidFrameAddress(FrameAddress address) const {
+	auto addr_row = rows_.find(address.row());
+	if (addr_row == rows_.end())
+		return false;
+	return addr_row->second.IsValidFrameAddress(address);
+}
+
+absl::optional<FrameAddress> GlobalClockRegion::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Find the row for the current address.
+	auto addr_row = rows_.find(address.row());
+
+	// If the current address isn't in a known row, no way to know the next.
+	if (addr_row == rows_.end())
+		return {};
+
+	// Ask the row for the next address.
+	absl::optional<FrameAddress> next_address =
+	    addr_row->second.GetNextFrameAddress(address);
+	if (next_address)
+		return next_address;
+
+	// The current row doesn't know what the next address is.  Assume that
+	// the next valid address is the beginning of the next row.
+	if (++addr_row != rows_.end()) {
+		auto next_address =
+		    FrameAddress(address.block_type(), addr_row->first, 0, 0);
+		if (addr_row->second.IsValidFrameAddress(next_address))
+			return next_address;
+	}
+
+	// Must be in a different global clock region.
+	return {};
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+namespace YAML {
+
+Node convert<spartan6::GlobalClockRegion>::encode(
+    const spartan6::GlobalClockRegion& rhs) {
+	Node node;
+	node.SetTag("xilinx/spartan6/global_clock_region");
+	node["rows"] = rhs.rows_;
+	return node;
+}
+
+bool convert<spartan6::GlobalClockRegion>::decode(
+    const Node& node,
+    spartan6::GlobalClockRegion& lhs) {
+	if (!node.Tag().empty() &&
+	    node.Tag() != "xilinx/spartan6/global_clock_region") {
+		return false;
+	}
+
+	lhs.rows_ = node["rows"].as<std::map<unsigned int, spartan6::Row>>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/spartan6/part.cc b/lib/xilinx/spartan6/part.cc
new file mode 100644
index 0000000..b74aff5
--- /dev/null
+++ b/lib/xilinx/spartan6/part.cc
@@ -0,0 +1,115 @@
+#include <prjxray/xilinx/spartan6/part.h>
+
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+namespace prjxray {
+namespace xilinx {
+namespace spartan6 {
+
+absl::optional<Part> Part::FromFile(const std::string& path) {
+	try {
+		YAML::Node yaml = YAML::LoadFile(path);
+		return yaml.as<Part>();
+	} catch (YAML::Exception& e) {
+		return {};
+	}
+}
+
+bool Part::IsValidFrameAddress(FrameAddress address) const {
+	if (address.is_bottom_half_rows()) {
+		return bottom_region_.IsValidFrameAddress(address);
+	} else {
+		return top_region_.IsValidFrameAddress(address);
+	}
+}
+
+absl::optional<FrameAddress> Part::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Ask the current global clock region first.
+	absl::optional<FrameAddress> next_address =
+	    (address.is_bottom_half_rows()
+	         ? bottom_region_.GetNextFrameAddress(address)
+	         : top_region_.GetNextFrameAddress(address));
+	if (next_address)
+		return next_address;
+
+	// If the current address is in the top region, the bottom region is
+	// next numerically.
+	if (!address.is_bottom_half_rows()) {
+		next_address = FrameAddress(address.block_type(), 0, 0, 0);
+		if (bottom_region_.IsValidFrameAddress(*next_address))
+			return next_address;
+	}
+
+	// Block types are next numerically.
+	if (address.block_type() < spartan6::BlockType::BLOCK_RAM) {
+		next_address =
+		    FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0);
+		if (IsValidFrameAddress(*next_address))
+			return next_address;
+	}
+
+	if (address.block_type() < spartan6::BlockType::IOB) {
+		next_address = FrameAddress(spartan6::BlockType::IOB, 0, 0, 0);
+		if (IsValidFrameAddress(*next_address))
+			return next_address;
+	}
+
+	return {};
+}
+
+}  // namespace spartan6
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace spartan6 = prjxray::xilinx::spartan6;
+
+namespace YAML {
+
+Node convert<spartan6::Part>::encode(const spartan6::Part& rhs) {
+	Node node;
+	node.SetTag("xilinx/spartan6/part");
+
+	std::ostringstream idcode_str;
+	idcode_str << "0x" << std::hex << rhs.idcode_;
+	node["idcode"] = idcode_str.str();
+	node["global_clock_regions"]["top"] = rhs.top_region_;
+	node["global_clock_regions"]["bottom"] = rhs.bottom_region_;
+	return node;
+}
+
+bool convert<spartan6::Part>::decode(const Node& node, spartan6::Part& lhs) {
+	if (!node.Tag().empty() && node.Tag() != "xilinx/spartan6/part")
+		return false;
+
+	if (!node["global_clock_regions"] && !node["configuration_ranges"]) {
+		return false;
+	}
+
+	lhs.idcode_ = node["idcode"].as<uint32_t>();
+
+	if (node["global_clock_regions"]) {
+		lhs.top_region_ = node["global_clock_regions"]["top"]
+		                      .as<spartan6::GlobalClockRegion>();
+		lhs.bottom_region_ = node["global_clock_regions"]["bottom"]
+		                         .as<spartan6::GlobalClockRegion>();
+	} else if (node["configuration_ranges"]) {
+		std::vector<spartan6::FrameAddress> addresses;
+		for (auto range : node["configuration_ranges"]) {
+			auto begin =
+			    range["begin"].as<spartan6::FrameAddress>();
+			auto end = range["end"].as<spartan6::FrameAddress>();
+			for (uint32_t cur = begin; cur < end; ++cur) {
+				addresses.push_back(cur);
+			}
+		}
+
+		lhs = spartan6::Part(lhs.idcode_, addresses);
+	}
+
+	return true;
+};
+
+}  // namespace YAML
diff --git a/lib/xilinx/tests/spartan6/bitstream_reader_test.cc b/lib/xilinx/tests/spartan6/bitstream_reader_test.cc
new file mode 100644
index 0000000..4f807bd
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/bitstream_reader_test.cc
@@ -0,0 +1,109 @@
+#include <array>
+
+#include <absl/types/span.h>
+#include <gtest/gtest.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/configuration_register.h>
+
+#include <prjxray/big_endian_span.h>
+
+using namespace prjxray::xilinx;
+TEST(BitstreamReaderTest, InitWithEmptyBytesReturnsNull) {
+	absl::Span<uint8_t> bitstream;
+	auto reader = BitstreamReader<Spartan6>::InitWithBytes(bitstream);
+	EXPECT_FALSE(reader);
+}
+
+TEST(BitstreamReaderTest, InitWithOnlySyncReturnsObject) {
+	std::vector<uint8_t> bitstream{0xAA, 0x99, 0x55, 0x66};
+	absl::Span<std::vector<uint8_t>::value_type> bitstream_span(bitstream);
+	// auto config_packets =
+	//    bitstream_span.subspan(bitstream.end() - bitstream.begin());
+	// auto big_endian_reader =
+	// prjxray::make_big_endian_span<uint16_t>(bitstream_span);
+	// std::vector<uint16_t> words{big_endian_reader.begin(),
+	//                            big_endian_reader.end()};
+
+	// for (auto word: words) {
+	//   std::cout << "0x" << std::hex << word << std::endl;
+	//}
+	auto reader = BitstreamReader<Spartan6>::InitWithBytes(bitstream);
+	EXPECT_TRUE(reader);
+}
+
+TEST(BitstreamReaderTest, InitWithSyncAfterNonWordSizedPaddingReturnsObject) {
+	std::vector<uint8_t> bitstream{0xFF, 0xFE, 0xAA, 0x99, 0x55, 0x66};
+	auto reader = BitstreamReader<Spartan6>::InitWithBytes(bitstream);
+	EXPECT_TRUE(reader);
+}
+
+TEST(BitstreamReaderTest, InitWithSyncAfterWordSizedPaddingReturnsObject) {
+	std::vector<uint8_t> bitstream{0xFF, 0xFE, 0xFD, 0xFC,
+	                               0xAA, 0x99, 0x55, 0x66};
+	auto reader = BitstreamReader<Spartan6>::InitWithBytes(bitstream);
+	EXPECT_TRUE(reader);
+}
+
+TEST(BitstreamReaderTest, ParsesType1Packet) {
+	std::vector<uint8_t> bitstream{
+	    0xAA, 0x99, 0x55, 0x66,  // sync
+	    0x20, 0x00, 0x20, 0x00,  // NOP
+	};
+	auto reader = BitstreamReader<Spartan6>::InitWithBytes(bitstream);
+	ASSERT_TRUE(reader);
+	ASSERT_NE(reader->begin(), reader->end());
+
+	auto first_packet = reader->begin();
+	EXPECT_EQ(first_packet->opcode(),
+	          ConfigurationPacket<Spartan6>::Opcode::NOP);
+
+	auto second_packet = ++first_packet;
+	EXPECT_EQ(second_packet->opcode(),
+	          ConfigurationPacket<Spartan6>::Opcode::NOP);
+
+	EXPECT_EQ(++second_packet, reader->end());
+}
+
+TEST(BitstreamReaderTest, ParseType2PacketWithoutType1Fails) {
+	std::vector<uint8_t> bitstream{
+	    0xAA, 0x99, 0x55, 0x66,  // sync
+	    0x40, 0x00, 0x40, 0x00,  // Type 2 NOP
+	};
+	auto reader = BitstreamReader<Spartan6>::InitWithBytes(bitstream);
+	ASSERT_TRUE(reader);
+	EXPECT_EQ(reader->begin(), reader->end());
+}
+
+TEST(BitstreamReaderTest, ParsesType2AfterType1Packet) {
+	std::vector<uint8_t> bitstream{
+	    0xAA, 0x99,  // sync
+	    0x55, 0x66,  // sync
+	    0x28, 0x80,  // Type 1 Read zero bytes from FDRO
+	    0x50, 0x60,  // Type 2 Write of 8 16-bit words
+	    0x00, 0x00,  // WC1 bits 31:16
+	    0x00, 0x08,  // WC2 bits 15:0
+	    0x1,  0x2,  0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
+	    0x9,  0xA,  0xB, 0xC, 0xD, 0xE, 0xF, 0x10,
+	};
+	std::vector<uint32_t> data_words{0x0102, 0x0304, 0x0506, 0x0708,
+	                                 0x090A, 0x0B0C, 0x0D0E, 0x0F10};
+
+	auto reader = BitstreamReader<Spartan6>::InitWithBytes(bitstream);
+	ASSERT_TRUE(reader);
+	ASSERT_NE(reader->begin(), reader->end());
+
+	auto first_packet = reader->begin();
+	EXPECT_EQ(first_packet->opcode(),
+	          ConfigurationPacket<Spartan6>::Opcode::Read);
+	EXPECT_EQ(first_packet->address(), Spartan6::ConfRegType::FDRO);
+	EXPECT_EQ(first_packet->data(), absl::Span<uint32_t>());
+
+	auto third_packet = ++first_packet;
+	ASSERT_NE(third_packet, reader->end());
+	EXPECT_EQ(third_packet->opcode(),
+	          ConfigurationPacket<Spartan6>::Opcode::Write);
+	EXPECT_EQ(third_packet->address(), Spartan6::ConfRegType::FDRI);
+	(third_packet->data(), absl::Span<uint32_t>(data_words));
+	EXPECT_EQ(++first_packet, reader->end());
+}
diff --git a/lib/xilinx/tests/spartan6/bitstream_writer_test.cc b/lib/xilinx/tests/spartan6/bitstream_writer_test.cc
new file mode 100644
index 0000000..7331f59
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/bitstream_writer_test.cc
@@ -0,0 +1,183 @@
+#include <array>
+
+#include <gtest/gtest.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <prjxray/xilinx/bitstream_writer.h>
+
+#include <prjxray/bit_ops.h>
+
+using namespace prjxray::xilinx;
+
+constexpr uint32_t kType1NOP = prjxray::bit_field_set<uint32_t>(0, 15, 13, 0x1);
+
+extern const uint32_t MakeType1(const int opcode,
+                                const int address,
+                                const int word_count);
+
+extern const std::vector<uint32_t> MakeType2(const int opcode,
+                                             const int address,
+                                             const int word_count);
+
+void dump_packets(BitstreamWriter<Spartan6> writer, bool nl = true) {
+	int i = 0;
+	// for (uint32_t x : itr) {
+	for (auto itr = writer.begin(); itr != writer.end(); ++itr) {
+		if (nl) {
+			printf("% 3d: 0x0%08X\n", i, *itr);
+		} else {
+			printf("0x0%08X, ", *itr);
+		}
+		fflush(stdout);
+		++i;
+	}
+	if (!nl) {
+		printf("\n");
+	}
+}
+
+// Special all 0's
+void AddType0(
+    std::vector<std::unique_ptr<ConfigurationPacket<Spartan6::ConfRegType>>>&
+        packets) {
+	// InitWithWords doesn't like type 0
+	/*
+	static std::vector<uint32_t> words{0x00000000};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(word_span);
+	packets.push_back(*(packet.second));
+	*/
+	static std::vector<uint32_t> words{};
+	absl::Span<uint32_t> word_span(words);
+	// CRC is config value 0
+	packets.emplace_back(new ConfigurationPacket<Spartan6::ConfRegType>(
+	    0, ConfigurationPacket<Spartan6::ConfRegType>::NOP,
+	    Spartan6::ConfRegType::CRC, word_span));
+}
+
+void AddType1(
+    std::vector<std::unique_ptr<ConfigurationPacket<Spartan6::ConfRegType>>>&
+        packets) {
+	static std::vector<uint32_t> words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB};
+	absl::Span<uint32_t> word_span(words);
+	auto packet = ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(
+	    word_span);
+	packets.emplace_back(
+	    new ConfigurationPacket<Spartan6::ConfRegType>(*(packet.second)));
+}
+
+// Empty
+void AddType1E(
+    std::vector<std::unique_ptr<ConfigurationPacket<Spartan6::ConfRegType>>>&
+        packets) {
+	static std::vector<uint32_t> words{MakeType1(0x2, 0x3, 0)};
+	absl::Span<uint32_t> word_span(words);
+	auto packet = ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(
+	    word_span);
+	packets.emplace_back(
+	    new ConfigurationPacket<Spartan6::ConfRegType>(*(packet.second)));
+}
+
+void AddType2(Spartan6::ConfigurationPackage& packets) {
+	// Type 2 packet with data
+	{
+		static std::vector<uint32_t> words;
+		words = MakeType2(0x02, 0x3, 12);
+		std::vector<uint32_t> payload{1, 2, 3, 4,  5,  6,
+		                              7, 8, 9, 10, 11, 12};
+		words.insert(words.end(), payload.begin(), payload.end());
+		std::cout << words.size();
+		absl::Span<uint32_t> word_span(words);
+		auto packet =
+		    ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(
+		        word_span);
+		packets.emplace_back(
+		    new ConfigurationPacket<Spartan6::ConfRegType>(
+		        *(packet.second)));
+	}
+}
+
+// Empty packets should produce just the header
+TEST(BitstreamWriterTest, WriteHeader) {
+	std::vector<std::unique_ptr<ConfigurationPacket<Spartan6::ConfRegType>>>
+	    packets;
+
+	BitstreamWriter<Spartan6> writer(packets);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+
+	// Per UG380 pg 78: Bus Width Auto Detection
+	std::vector<uint32_t> ref_header{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+	                                 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+	                                 0xAA99, 0x5566};
+	EXPECT_EQ(words, ref_header);
+
+	// dump_packets(writer);
+}
+
+TEST(BitstreamWriterTest, WriteType0) {
+	std::vector<std::unique_ptr<ConfigurationPacket<Spartan6::ConfRegType>>>
+	    packets;
+	AddType0(packets);
+	BitstreamWriter<Spartan6> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+	                          0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566,
+	                          // Type 0
+	                          0x0000};
+	EXPECT_EQ(words, ref);
+}
+
+TEST(BitstreamWriterTest, WriteType1) {
+	Spartan6::ConfigurationPackage packets;
+	AddType1(packets);
+	BitstreamWriter<Spartan6> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{// Bus width + sync
+	                          0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+	                          0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566,
+	                          // Type 1
+	                          0x3062, 0x00AA, 0x00BB};
+	EXPECT_EQ(words, ref);
+}
+
+TEST(BitstreamWriterTest, WriteType2) {
+	Spartan6::ConfigurationPackage packets;
+	AddType2(packets);
+	BitstreamWriter<Spartan6> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{
+	    // Bus width + sync
+	    0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+	    0xAA99, 0x5566, 0x5060, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005,
+	    0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C};
+	EXPECT_EQ(words, ref);
+}
+
+TEST(BitstreamWriterTest, WriteMulti) {
+	Spartan6::ConfigurationPackage packets;
+	AddType1(packets);
+	AddType1E(packets);
+	AddType2(packets);
+	AddType1E(packets);
+	BitstreamWriter<Spartan6> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{// Bus width + sync
+	                          0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+	                          0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566,
+	                          // Type1
+	                          0x3062, 0x00AA, 0x00BB,
+	                          // Type1
+	                          0x3060,
+	                          // Type 1 + type 2 header
+	                          0x5060, 0x0001, 0x0002, 0x0003, 0x0004,
+	                          0x0005, 0x0006, 0x0007, 0x0008, 0x0009,
+	                          0x000A, 0x000B, 0x000C,
+	                          // Type 1
+	                          0x3060};
+	EXPECT_EQ(words, ref);
+}
diff --git a/lib/xilinx/tests/spartan6/block_type_test.cc b/lib/xilinx/tests/spartan6/block_type_test.cc
new file mode 100644
index 0000000..4796242
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/block_type_test.cc
@@ -0,0 +1,29 @@
+#include <prjxray/xilinx/spartan6/block_type.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(BlockTypeTest, YamlEncode) {
+	YAML::Node node;
+	node.push_back(spartan6::BlockType::CLB_IOI_CLK);
+	node.push_back(spartan6::BlockType::BLOCK_RAM);
+	node.push_back(spartan6::BlockType::IOB);
+
+	EXPECT_EQ(node[0].as<std::string>(), "CLB_IOI_CLK");
+	EXPECT_EQ(node[1].as<std::string>(), "BLOCK_RAM");
+	EXPECT_EQ(node[2].as<std::string>(), "IOB");
+}
+
+TEST(BlockTypeTest, YamlDecode) {
+	YAML::Node node;
+	node.push_back("IOB");
+	node.push_back("BLOCK_RAM");
+	node.push_back("CLB_IOI_CLK");
+
+	EXPECT_EQ(node[0].as<spartan6::BlockType>(), spartan6::BlockType::IOB);
+	EXPECT_EQ(node[1].as<spartan6::BlockType>(),
+	          spartan6::BlockType::BLOCK_RAM);
+	EXPECT_EQ(node[2].as<spartan6::BlockType>(),
+	          spartan6::BlockType::CLB_IOI_CLK);
+}
diff --git a/lib/xilinx/tests/spartan6/configuration_bus_test.cc b/lib/xilinx/tests/spartan6/configuration_bus_test.cc
new file mode 100644
index 0000000..61f1a49
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/configuration_bus_test.cc
@@ -0,0 +1,70 @@
+#include <prjxray/xilinx/spartan6/configuration_bus.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(ConfigurationBusTest, IsValidFrameAddress) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1));
+
+	spartan6::ConfigurationBus bus(addresses.begin(), addresses.end());
+
+	EXPECT_TRUE(bus.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)));
+	EXPECT_TRUE(bus.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1)));
+
+	EXPECT_FALSE(bus.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)));
+}
+
+TEST(ConfigurationBusTest, GetNextFrameAddressYieldNextAddressInBus) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1));
+
+	spartan6::ConfigurationBus bus(addresses.begin(), addresses.end());
+
+	auto next_address = bus.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address, spartan6::FrameAddress(
+	                             spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+
+	next_address = bus.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address, spartan6::FrameAddress(
+	                             spartan6::BlockType::BLOCK_RAM, 0, 1, 0));
+}
+
+TEST(ConfigurationBusTest, GetNextFrameAddressYieldNothingAtEndOfBus) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1));
+
+	spartan6::ConfigurationBus bus(addresses.begin(), addresses.end());
+
+	EXPECT_FALSE(bus.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1)));
+}
diff --git a/lib/xilinx/tests/spartan6/configuration_column_test.cc b/lib/xilinx/tests/spartan6/configuration_column_test.cc
new file mode 100644
index 0000000..8b2b48d
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/configuration_column_test.cc
@@ -0,0 +1,65 @@
+#include <prjxray/xilinx/spartan6/configuration_column.h>
+
+#include <gtest/gtest.h>
+#include <prjxray/xilinx/spartan6/block_type.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+using namespace prjxray::xilinx;
+
+TEST(ConfigurationColumnTest, IsValidFrameAddress) {
+	spartan6::ConfigurationColumn column(10);
+
+	// Inside this column.
+	EXPECT_TRUE(column.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 3)));
+	// Past this column's frame width.
+	EXPECT_FALSE(column.IsValidFrameAddress(spartan6::FrameAddress(
+	    spartan6::BlockType::CLB_IOI_CLK, 1, 2, 10)));
+}
+
+TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNextAddressInColumn) {
+	spartan6::ConfigurationColumn column(10);
+
+	auto next_address = column.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 3));
+	EXPECT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 4));
+}
+
+TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingAtEndOfColumn) {
+	spartan6::ConfigurationColumn column(10);
+
+	EXPECT_FALSE(column.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 9)));
+}
+
+TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingOutsideColumn) {
+	spartan6::ConfigurationColumn column(10);
+
+	// Just past last frame in column.
+	EXPECT_FALSE(column.GetNextFrameAddress(spartan6::FrameAddress(
+	    spartan6::BlockType::CLB_IOI_CLK, 1, 2, 10)));
+}
+
+TEST(ConfigurationColumnTest, YamlEncodeTest) {
+	spartan6::ConfigurationColumn column(10);
+
+	YAML::Node node(column);
+	EXPECT_TRUE(node["frame_count"]);
+	EXPECT_EQ(node["frame_count"].as<int>(), 10);
+}
+
+TEST(ConfigurationColumnTest, YAMLDecodeTest) {
+	YAML::Node node;
+	node.SetTag("xilinx/spartan6/configuration_column");
+	node["frame_count"] = 10;
+
+	auto column = node.as<spartan6::ConfigurationColumn>();
+	EXPECT_TRUE(column.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 8)));
+	EXPECT_FALSE(column.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 9)));
+}
diff --git a/lib/xilinx/tests/spartan6/configuration_packet_test.cc b/lib/xilinx/tests/spartan6/configuration_packet_test.cc
new file mode 100644
index 0000000..1a13267
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/configuration_packet_test.cc
@@ -0,0 +1,100 @@
+#include <functional>
+
+#include <absl/meta/type_traits.h>
+#include <gtest/gtest.h>
+#include <prjxray/bit_ops.h>
+
+#include <prjxray/xilinx/architectures.h>
+
+using namespace prjxray::xilinx;
+
+constexpr uint32_t kType1NOP = prjxray::bit_field_set<uint32_t>(0, 15, 13, 0x1);
+
+const uint32_t MakeType1(const int opcode,
+                         const int address,
+                         const int word_count) {
+	return prjxray::bit_field_set<uint32_t>(
+	    prjxray::bit_field_set<uint32_t>(
+	        prjxray::bit_field_set<uint32_t>(
+	            prjxray::bit_field_set<uint32_t>(0x0, 15, 13, 0x1), 12, 11,
+	            opcode),
+	        10, 5, address),
+	    4, 0, word_count);
+}
+
+const std::vector<uint32_t> MakeType2(const int opcode,
+                                      const int address,
+                                      const int word_count) {
+	uint32_t header = prjxray::bit_field_set<uint32_t>(
+	    prjxray::bit_field_set<uint32_t>(
+	        prjxray::bit_field_set<uint32_t>(
+	            prjxray::bit_field_set<uint32_t>(0x0, 15, 13, 0x2), 12, 11,
+	            opcode),
+	        10, 5, address),
+	    4, 0, 0);
+	uint32_t wcr1 = (word_count >> 16) & 0xFFFF;
+	uint32_t wcr2 = (word_count & 0xFFFF);
+	return std::vector<uint32_t>{header, wcr1, wcr2};
+}
+
+TEST(ConfigPacket, InitWithZeroBytes) {
+	auto packet =
+	    ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords({});
+
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	EXPECT_FALSE(packet.second);
+}
+
+TEST(ConfigPacket, InitWithType1Nop) {
+	std::vector<uint32_t> words{kType1NOP};
+	absl::Span<uint32_t> word_span(words);
+	auto packet = ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(
+	    word_span);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Spartan6::ConfRegType>::Opcode::NOP);
+	EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::CRC);
+	EXPECT_EQ(packet.second->data(), absl::Span<uint32_t>());
+}
+
+TEST(ConfigPacket, InitWithType1Read) {
+	std::vector<uint32_t> words{MakeType1(0x1, 0x3, 2), 0xAA, 0xBB};
+	absl::Span<uint32_t> word_span(words);
+	auto packet = ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(
+	    word_span);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Read);
+	EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::FDRI);
+	EXPECT_EQ(packet.second->data(), word_span.subspan(1));
+}
+
+TEST(ConfigPacket, InitWithType1Write) {
+	std::vector<uint32_t> words{MakeType1(0x2, 0x4, 2), 0xAA, 0xBB};
+	absl::Span<uint32_t> word_span(words);
+	auto packet = ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(
+	    word_span);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write);
+	EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::FDRO);
+	EXPECT_EQ(packet.second->data(), word_span.subspan(1));
+}
+
+TEST(ConfigPacket, InitWithType2WithPreviousPacket) {
+	std::vector<uint32_t> words{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+	std::vector<uint32_t> type2 = MakeType2(0x01, 0x03, 12);
+	words.insert(words.begin(), type2.begin(), type2.end());
+	absl::Span<uint32_t> word_span(words);
+	auto packet = ConfigurationPacket<Spartan6::ConfRegType>::InitWithWords(
+	    word_span);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Read);
+	EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::FDRI);
+	EXPECT_EQ(packet.second->data(), word_span.subspan(3));
+}
diff --git a/lib/xilinx/tests/spartan6/configuration_test.cc b/lib/xilinx/tests/spartan6/configuration_test.cc
new file mode 100644
index 0000000..c8ee77f
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/configuration_test.cc
@@ -0,0 +1,186 @@
+#include <cstdint>
+#include <iostream>
+#include <vector>
+
+#include <absl/types/span.h>
+#include <gtest/gtest.h>
+#include <prjxray/memory_mapped_file.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/configuration.h>
+#include <prjxray/xilinx/spartan6/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+using namespace prjxray::xilinx;
+
+TEST(ConfigurationTest, ConstructFromPacketsWithSingleFrame) {
+	std::vector<spartan6::FrameAddress> test_part_addresses;
+	test_part_addresses.push_back(0x0A);
+	test_part_addresses.push_back(0x0B);
+
+	spartan6::Part test_part(0x1234, test_part_addresses);
+
+	std::vector<uint32_t> idcode{0x1234};
+	std::vector<uint32_t> cmd{0x0001};
+	std::vector<uint32_t> frame_address{0x345};
+	std::vector<uint32_t> frame(65, 0xAA);
+
+	std::vector<ConfigurationPacket<typename Spartan6::ConfRegType>>
+	    packets{
+	        {
+	            static_cast<unsigned int>(0x1),
+	            ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	            Spartan6::ConfRegType::IDCODE,
+	            absl::MakeSpan(idcode),
+	        },
+	        {
+	            static_cast<unsigned int>(0x1),
+	            ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	            Spartan6::ConfRegType::FAR_MIN,
+	            absl::MakeSpan(frame_address),
+	        },
+	        {
+	            static_cast<unsigned int>(0x1),
+	            ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	            Spartan6::ConfRegType::CMD,
+	            absl::MakeSpan(cmd),
+	        },
+	        {
+	            static_cast<unsigned int>(0x1),
+	            ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	            Spartan6::ConfRegType::FDRI,
+	            absl::MakeSpan(frame),
+	        },
+	    };
+
+	auto test_config =
+	    Configuration<Spartan6>::InitWithPackets(test_part, packets);
+	ASSERT_TRUE(test_config);
+
+	EXPECT_EQ(test_config->part().idcode(), static_cast<uint32_t>(0x1234));
+	EXPECT_EQ(test_config->frames().size(), static_cast<size_t>(1));
+	EXPECT_EQ(test_config->frames().at(0x345), frame);
+}
+
+TEST(ConfigurationTest, ConstructFromPacketsWithAutoincrement) {
+	std::vector<spartan6::FrameAddress> test_part_addresses;
+	for (int ii = 0x310; ii < 0x320; ++ii) {
+		test_part_addresses.push_back(ii);
+	}
+
+	for (int ii = 0x330; ii < 0x331; ++ii) {
+		test_part_addresses.push_back(ii);
+	}
+
+	spartan6::Part test_part(0x1234, test_part_addresses);
+
+	std::vector<uint32_t> idcode{0x1234};
+	std::vector<uint32_t> cmd{0x0001};
+	std::vector<uint32_t> frame_address{0x31f};
+	std::vector<uint32_t> frame(65 * 2, 0xAA);
+	std::fill_n(frame.begin() + 65, 65, 0xBB);
+
+	std::vector<ConfigurationPacket<Spartan6::ConfRegType>> packets{
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::IDCODE,
+	        absl::MakeSpan(idcode),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::FAR_MIN,
+	        absl::MakeSpan(frame_address),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::CMD,
+	        absl::MakeSpan(cmd),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::FDRI,
+	        absl::MakeSpan(frame),
+	    },
+	};
+
+	auto test_config =
+	    Configuration<Spartan6>::InitWithPackets(test_part, packets);
+	ASSERT_TRUE(test_config);
+
+	absl::Span<uint32_t> frame_span(frame);
+	EXPECT_EQ(test_config->part().idcode(), static_cast<uint32_t>(0x1234));
+	EXPECT_EQ(test_config->frames().size(), static_cast<size_t>(2));
+	EXPECT_EQ(test_config->frames().at(0x31f),
+	          std::vector<uint32_t>(65, 0xAA));
+	// TODO This test fails with a C++ exception because the address
+	// of next frame is 0x320 instead of 0x330 as defined in the test_part
+	// EXPECT_EQ(test_config->frames().at(0x330),
+	//          std::vector<uint32_t>(65, 0xBB));
+}
+
+TEST(ConfigurationTest, DISABLED_CheckForPaddingAfterIOBFrame) {
+	std::vector<spartan6::FrameAddress> test_part_addresses = {
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0),
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 1, 0, 0),
+	    spartan6::FrameAddress(spartan6::BlockType::IOB, 2, 0, 0)};
+
+	auto test_part = absl::optional<spartan6::Part>(
+	    spartan6::Part(0x1234, test_part_addresses));
+
+	Frames<Spartan6> frames;
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(0), std::vector<uint32_t>(65, 0xAA)));
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(1), std::vector<uint32_t>(65, 0xBB)));
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(2), std::vector<uint32_t>(65, 0xCC)));
+	ASSERT_EQ(frames.getFrames().size(), 3);
+
+	Configuration<Spartan6>::PacketData packet_data =
+	    Configuration<Spartan6>::createType2ConfigurationPacketData(
+	        frames.getFrames(), test_part);
+	// createType2ConfigurationPacketData should add a 16-bit pad word after
+	// after the IOB frame
+	EXPECT_EQ(packet_data.size(), 3 * 65 + 1);
+
+	std::vector<uint32_t> idcode{0x1234};
+	std::vector<uint32_t> cmd{0x0001};
+	std::vector<uint32_t> frame_address{0x0};
+
+	std::vector<ConfigurationPacket<Spartan6::ConfRegType>> packets{
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::IDCODE,
+	        absl::MakeSpan(idcode),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::FAR,
+	        absl::MakeSpan(frame_address),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::CMD,
+	        absl::MakeSpan(cmd),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Spartan6::ConfRegType>::Opcode::Write,
+	        Spartan6::ConfRegType::FDRI,
+	        absl::MakeSpan(packet_data),
+	    },
+	};
+
+	auto test_config =
+	    Configuration<Spartan6>::InitWithPackets(*test_part, packets);
+	ASSERT_EQ(test_config->frames().size(), 5);
+	for (auto& frame : test_config->frames()) {
+		EXPECT_EQ(frame.second, frames.getFrames().at(frame.first));
+	}
+}
diff --git a/lib/xilinx/tests/spartan6/frame_address_test.cc b/lib/xilinx/tests/spartan6/frame_address_test.cc
new file mode 100644
index 0000000..4633c20
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/frame_address_test.cc
@@ -0,0 +1,33 @@
+#include <prjxray/xilinx/spartan6/frame_address.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(FrameAddressTest, YamlEncode) {
+	spartan6::FrameAddress address(spartan6::BlockType::BLOCK_RAM, 10, 0,
+	                               5);
+
+	YAML::Node node(address);
+
+	EXPECT_EQ(node.Tag(), "xilinx/spartan6/frame_address");
+	EXPECT_EQ(node["block_type"].as<std::string>(), "BLOCK_RAM");
+	EXPECT_EQ(node["row"].as<std::string>(), "10");
+	EXPECT_EQ(node["column"].as<std::string>(), "0");
+	EXPECT_EQ(node["minor"].as<std::string>(), "5");
+}
+
+TEST(FrameAddressTest, YamlDecode) {
+	YAML::Node node;
+	node.SetTag("xilinx/spartan6/frame_address");
+	node["block_type"] = "BLOCK_RAM";
+	node["row"] = "0";
+	node["column"] = "5";
+	node["minor"] = "11";
+
+	spartan6::FrameAddress address = node.as<spartan6::FrameAddress>();
+	EXPECT_EQ(address.block_type(), spartan6::BlockType::BLOCK_RAM);
+	EXPECT_EQ(address.row(), 0);
+	EXPECT_EQ(address.column(), 5);
+	EXPECT_EQ(address.minor(), 11);
+}
diff --git a/lib/xilinx/tests/spartan6/frames_test.cc b/lib/xilinx/tests/spartan6/frames_test.cc
new file mode 100644
index 0000000..eec0750
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/frames_test.cc
@@ -0,0 +1,48 @@
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <prjxray/xilinx/frames.h>
+#include <prjxray/xilinx/spartan6/part.h>
+
+using namespace prjxray::xilinx;
+TEST(FramesTest, FillInMissingFrames) {
+	std::vector<spartan6::FrameAddress> test_part_addresses = {
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0),
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1),
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2),
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 3),
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 4)};
+
+	spartan6::Part test_part(0x1234, test_part_addresses);
+
+	Frames<Spartan6> frames;
+	frames.getFrames().emplace(std::make_pair(
+	    spartan6::FrameAddress(2), std::vector<uint32_t>(65, 0xCC)));
+	frames.getFrames().emplace(std::make_pair(
+	    spartan6::FrameAddress(3), std::vector<uint32_t>(65, 0xDD)));
+	frames.getFrames().emplace(std::make_pair(
+	    spartan6::FrameAddress(4), std::vector<uint32_t>(65, 0xEE)));
+
+	ASSERT_EQ(frames.getFrames().size(), 3);
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[2]),
+	          std::vector<uint32_t>(65, 0xCC));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[3]),
+	          std::vector<uint32_t>(65, 0xDD));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[4]),
+	          std::vector<uint32_t>(65, 0xEE));
+
+	frames.addMissingFrames(test_part);
+
+	ASSERT_EQ(frames.getFrames().size(), 5);
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[0]),
+	          std::vector<uint32_t>(65, 0));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[1]),
+	          std::vector<uint32_t>(65, 0));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[2]),
+	          std::vector<uint32_t>(65, 0xCC));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[3]),
+	          std::vector<uint32_t>(65, 0xDD));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[4]),
+	          std::vector<uint32_t>(65, 0xEE));
+}
diff --git a/lib/xilinx/tests/spartan6/global_clock_region_test.cc b/lib/xilinx/tests/spartan6/global_clock_region_test.cc
new file mode 100644
index 0000000..b38e11a
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/global_clock_region_test.cc
@@ -0,0 +1,132 @@
+#include <prjxray/xilinx/spartan6/global_clock_region.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(GlobalClockRegionTest, IsValidFrameAddress) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1));
+
+	spartan6::GlobalClockRegion global_clock_region(addresses.begin(),
+	                                                addresses.end());
+
+	EXPECT_TRUE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)));
+	EXPECT_TRUE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)));
+	EXPECT_TRUE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)));
+	EXPECT_TRUE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)));
+
+	EXPECT_FALSE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2)));
+	EXPECT_FALSE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 2, 0)));
+	EXPECT_FALSE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 2, 0, 0)));
+	EXPECT_FALSE(global_clock_region.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::IOB, 0, 0, 2)));
+}
+
+TEST(GlobalClockRegionTest,
+     GetNextFrameAddressYieldNextAddressInGlobalClockRegion) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1));
+
+	spartan6::GlobalClockRegion global_clock_region(addresses.begin(),
+	                                                addresses.end());
+
+	auto next_address = global_clock_region.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+
+	next_address = global_clock_region.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+
+	next_address = global_clock_region.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+
+	next_address = global_clock_region.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address, spartan6::FrameAddress(
+	                             spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+}
+
+TEST(GlobalClockRegionTest,
+     GetNextFrameAddressYieldNothingAtEndOfGlobalClockRegion) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1));
+
+	spartan6::GlobalClockRegion global_clock_region(addresses.begin(),
+	                                                addresses.end());
+
+	EXPECT_FALSE(global_clock_region.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)));
+	EXPECT_FALSE(global_clock_region.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)));
+}
diff --git a/lib/xilinx/tests/spartan6/part_test.cc b/lib/xilinx/tests/spartan6/part_test.cc
new file mode 100644
index 0000000..83939d2
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/part_test.cc
@@ -0,0 +1,146 @@
+#include <prjxray/xilinx/spartan6/part.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(PartTest, IsValidFrameAddress) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+
+	spartan6::Part part(0x1234, addresses.begin(), addresses.end());
+
+	EXPECT_TRUE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)));
+	EXPECT_TRUE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)));
+	EXPECT_TRUE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)));
+	EXPECT_TRUE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)));
+	EXPECT_TRUE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)));
+
+	EXPECT_FALSE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2)));
+	EXPECT_FALSE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 2, 0)));
+	EXPECT_FALSE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 2, 0, 0)));
+	EXPECT_FALSE(part.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::IOB, 0, 0, 2)));
+}
+
+TEST(PartTest, GetNextFrameAddressYieldNextAddressInPart) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+
+	spartan6::Part part(0x1234, addresses.begin(), addresses.end());
+
+	auto next_address = part.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+
+	next_address = part.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+
+	next_address = part.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+
+	next_address = part.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address, spartan6::FrameAddress(
+	                             spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+
+	next_address = part.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+}
+
+TEST(PartTest, GetNextFrameAddressYieldNothingAtEndOfPart) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+
+	spartan6::Part part(0x1234, addresses.begin(), addresses.end());
+
+	EXPECT_FALSE(part.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)));
+}
diff --git a/lib/xilinx/tests/spartan6/row_test.cc b/lib/xilinx/tests/spartan6/row_test.cc
new file mode 100644
index 0000000..735bc1d
--- /dev/null
+++ b/lib/xilinx/tests/spartan6/row_test.cc
@@ -0,0 +1,114 @@
+#include <prjxray/xilinx/spartan6/configuration_row.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(RowTest, IsValidFrameAddress) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+
+	spartan6::Row row(addresses.begin(), addresses.end());
+
+	EXPECT_TRUE(row.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)));
+	EXPECT_TRUE(row.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)));
+	EXPECT_TRUE(row.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)));
+
+	EXPECT_FALSE(row.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2)));
+	EXPECT_FALSE(row.IsValidFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 2, 0)));
+}
+
+TEST(RowTest, GetNextFrameAddressYieldNextAddressInRow) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+
+	spartan6::Row row(addresses.begin(), addresses.end());
+
+	auto next_address = row.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+
+	next_address = row.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(
+	    *next_address,
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+
+	// Rows have unique behavior for GetNextFrameAddress() at the end of a
+	// bus. Since the addresses need to be returned in numerically
+	// increasing order, all of the rows need to be returned before moving
+	// to a different bus.  That means that Row::GetNextFrameAddress() needs
+	// to return no object at the end of a bus and let the caller use that
+	// as a signal to try the next row.
+	next_address = row.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	EXPECT_FALSE(next_address);
+
+	next_address = row.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address, spartan6::FrameAddress(
+	                             spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+
+	next_address = row.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+	EXPECT_FALSE(next_address);
+}
+
+TEST(RowTest, GetNextFrameAddressYieldNothingAtEndOfRow) {
+	std::vector<spartan6::FrameAddress> addresses;
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1));
+	addresses.push_back(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2));
+
+	spartan6::Row row(addresses.begin(), addresses.end());
+
+	EXPECT_FALSE(row.GetNextFrameAddress(
+	    spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)));
+}
diff --git a/lib/xilinx/tests/xc7series/bitstream_reader_test.cc b/lib/xilinx/tests/xc7series/bitstream_reader_test.cc
new file mode 100644
index 0000000..df576ca
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/bitstream_reader_test.cc
@@ -0,0 +1,91 @@
+#include <array>
+
+#include <gtest/gtest.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/configuration_register.h>
+
+using namespace prjxray::xilinx;
+
+TEST(BitstreamReaderTest, InitWithEmptyBytesReturnsNull) {
+	absl::Span<uint8_t> bitstream;
+	auto reader = BitstreamReader<Series7>::InitWithBytes(bitstream);
+	EXPECT_FALSE(reader);
+}
+
+TEST(BitstreamReaderTest, InitWithOnlySyncReturnsObject) {
+	std::vector<uint8_t> bitstream{0xAA, 0x99, 0x55, 0x66};
+	auto reader = BitstreamReader<Series7>::InitWithBytes(bitstream);
+	EXPECT_TRUE(reader);
+}
+
+TEST(BitstreamReaderTest, InitWithSyncAfterNonWordSizedPaddingReturnsObject) {
+	std::vector<uint8_t> bitstream{0xFF, 0xFE, 0xAA, 0x99, 0x55, 0x66};
+	auto reader = BitstreamReader<Series7>::InitWithBytes(bitstream);
+	EXPECT_TRUE(reader);
+}
+
+TEST(BitstreamReaderTest, InitWithSyncAfterWordSizedPaddingReturnsObject) {
+	std::vector<uint8_t> bitstream{0xFF, 0xFE, 0xFD, 0xFC,
+	                               0xAA, 0x99, 0x55, 0x66};
+	auto reader = BitstreamReader<Series7>::InitWithBytes(bitstream);
+	EXPECT_TRUE(reader);
+}
+
+TEST(BitstreamReaderTest, ParsesType1Packet) {
+	std::vector<uint8_t> bitstream{
+	    0xAA, 0x99, 0x55, 0x66,  // sync
+	    0x20, 0x00, 0x00, 0x00,  // NOP
+	};
+	auto reader = BitstreamReader<Series7>::InitWithBytes(bitstream);
+	ASSERT_TRUE(reader);
+	ASSERT_NE(reader->begin(), reader->end());
+
+	auto first_packet = reader->begin();
+	EXPECT_EQ(first_packet->opcode(),
+	          ConfigurationPacket<Series7::ConfRegType>::Opcode::NOP);
+
+	EXPECT_EQ(++first_packet, reader->end());
+}
+
+TEST(BitstreamReaderTest, ParseType2PacketWithoutType1Fails) {
+	std::vector<uint8_t> bitstream{
+	    0xAA, 0x99, 0x55, 0x66,  // sync
+	    0x40, 0x00, 0x00, 0x00,  // Type 2 NOP
+	};
+	auto reader = BitstreamReader<Series7>::InitWithBytes(bitstream);
+	ASSERT_TRUE(reader);
+	EXPECT_EQ(reader->begin(), reader->end());
+}
+
+TEST(BitstreamReaderTest, ParsesType2AfterType1Packet) {
+	std::vector<uint8_t> bitstream{
+	    0xAA, 0x99, 0x55, 0x66,  // sync
+	    0x28, 0x00, 0x60, 0x00,  // Type 1 Read zero bytes from 6
+	    0x48, 0x00, 0x00, 0x04,  // Type 2 write of 4 words
+	    0x1,  0x2,  0x3,  0x4,  0x5, 0x6, 0x7, 0x8,
+	    0x9,  0xA,  0xB,  0xC,  0xD, 0xE, 0xF, 0x10,
+	};
+	std::vector<uint32_t> data_words{0x01020304, 0x05060708, 0x090A0B0C,
+	                                 0x0D0E0F10};
+
+	auto reader = BitstreamReader<Series7>::InitWithBytes(bitstream);
+	ASSERT_TRUE(reader);
+	ASSERT_NE(reader->begin(), reader->end());
+
+	auto first_packet = reader->begin();
+	EXPECT_EQ(first_packet->opcode(),
+	          ConfigurationPacket<Series7::ConfRegType>::Opcode::Read);
+	EXPECT_EQ(first_packet->address(), Series7::ConfRegType::FDRO);
+	EXPECT_EQ(first_packet->data(), absl::Span<uint32_t>());
+
+	auto second_packet = ++first_packet;
+	ASSERT_NE(second_packet, reader->end());
+	EXPECT_EQ(second_packet->opcode(),
+	          ConfigurationPacket<Series7::ConfRegType>::Opcode::Read);
+	EXPECT_EQ(second_packet->address(), Series7::ConfRegType::FDRO);
+	EXPECT_EQ(first_packet->data(), absl::Span<uint32_t>(data_words));
+
+	EXPECT_EQ(++first_packet, reader->end());
+}
diff --git a/lib/xilinx/tests/xc7series/bitstream_writer_test.cc b/lib/xilinx/tests/xc7series/bitstream_writer_test.cc
new file mode 100644
index 0000000..031540d
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/bitstream_writer_test.cc
@@ -0,0 +1,222 @@
+#include <array>
+
+#include <gtest/gtest.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <prjxray/xilinx/bitstream_writer.h>
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/configuration_register.h>
+
+#include <prjxray/bit_ops.h>
+
+using namespace prjxray::xilinx;
+
+constexpr uint32_t kType1NOP = prjxray::bit_field_set<uint32_t>(0, 31, 29, 0x1);
+
+constexpr uint32_t MakeType1(const int opcode,
+                             const int address,
+                             const int word_count) {
+	return prjxray::bit_field_set<uint32_t>(
+	    prjxray::bit_field_set<uint32_t>(
+	        prjxray::bit_field_set<uint32_t>(
+	            prjxray::bit_field_set<uint32_t>(0x0, 31, 29, 0x1), 28, 27,
+	            opcode),
+	        26, 13, address),
+	    10, 0, word_count);
+}
+
+constexpr uint32_t MakeType2(const int opcode, const int word_count) {
+	return prjxray::bit_field_set<uint32_t>(
+	    prjxray::bit_field_set<uint32_t>(
+	        prjxray::bit_field_set<uint32_t>(0x0, 31, 29, 0x2), 28, 27,
+	        opcode),
+	    26, 0, word_count);
+}
+
+void dump_packets(BitstreamWriter<Series7> writer, bool nl = true) {
+	int i = 0;
+	// for (uint32_t x : itr) {
+	for (auto itr = writer.begin(); itr != writer.end(); ++itr) {
+		if (nl) {
+			printf("% 3d: 0x0%08X\n", i, *itr);
+		} else {
+			printf("0x0%08X, ", *itr);
+		}
+		fflush(stdout);
+		++i;
+	}
+	if (!nl) {
+		printf("\n");
+	}
+}
+
+// Special all 0's
+void AddType0(
+    std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>&
+        packets) {
+	// InitWithWords doesn't like type 0
+	/*
+	static std::vector<uint32_t> words{0x00000000};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	ConfigurationPacket<Series7::ConfRegType>::InitWithWords(word_span);
+	packets.push_back(*(packet.second));
+	*/
+	static std::vector<uint32_t> words{};
+	absl::Span<uint32_t> word_span(words);
+	// CRC is config value 0
+	packets.emplace_back(new ConfigurationPacket<Series7::ConfRegType>(
+	    0, ConfigurationPacket<Series7::ConfRegType>::NOP,
+	    Series7::ConfRegType::CRC, word_span));
+}
+
+void AddType1(
+    std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>&
+        packets) {
+	static std::vector<uint32_t> words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(word_span);
+	packets.emplace_back(
+	    new ConfigurationPacket<Series7::ConfRegType>(*(packet.second)));
+}
+
+// Empty
+void AddType1E(
+    std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>&
+        packets) {
+	static std::vector<uint32_t> words{MakeType1(0x2, 0x3, 0)};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(word_span);
+	packets.emplace_back(
+	    new ConfigurationPacket<Series7::ConfRegType>(*(packet.second)));
+}
+
+void AddType2(
+    std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>&
+        packets) {
+	// Type 1 packet with address
+	// Without this InitWithWords will fail on type 2
+	ConfigurationPacket<Series7::ConfRegType>* packet1;
+	{
+		static std::vector<uint32_t> words{MakeType1(0x2, 0x3, 0)};
+		absl::Span<uint32_t> word_span(words);
+		auto packet1_pair =
+		    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(
+		        word_span);
+		packets.emplace_back(
+		    new ConfigurationPacket<Series7::ConfRegType>(
+		        *(packet1_pair.second)));
+		packet1 = packets[0].get();
+	}
+	// Type 2 packet with data
+	{
+		static std::vector<uint32_t> words{
+		    MakeType2(0x01, 12), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+		absl::Span<uint32_t> word_span(words);
+		auto packet =
+		    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(
+		        word_span, packet1);
+		packets.emplace_back(
+		    new ConfigurationPacket<Series7::ConfRegType>(
+		        *(packet.second)));
+	}
+}
+
+// Empty packets should produce just the header
+TEST(BitstreamWriterTest, WriteHeader) {
+	std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>
+	    packets;
+
+	BitstreamWriter<Series7> writer(packets);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+
+	// Per UG470 pg 80: Bus Width Auto Detection
+	std::vector<uint32_t> ref_header{
+	    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+	    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044,
+	    0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566};
+	EXPECT_EQ(words, ref_header);
+
+	// dump_packets(writer);
+}
+
+TEST(BitstreamWriterTest, WriteType0) {
+	std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>
+	    packets;
+	AddType0(packets);
+	BitstreamWriter<Series7> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{
+	    // Bus width + sync
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566,
+	    // Type 0
+	    0x00000000};
+	EXPECT_EQ(words, ref);
+}
+TEST(BitstreamWriterTest, WriteType1) {
+	std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>
+	    packets;
+	AddType1(packets);
+	BitstreamWriter<Series7> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{
+	    // Bus width + sync
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566,
+	    // Type 1
+	    0x030006002, 0x0000000AA, 0x0000000BB};
+	EXPECT_EQ(words, ref);
+}
+
+TEST(BitstreamWriterTest, WriteType2) {
+	std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>
+	    packets;
+	AddType2(packets);
+	BitstreamWriter<Series7> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{
+	    // Bus width + sync
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566,
+	    // Type 1 + type 2 header
+	    0x030006000, 0x04800000C, 0x000000001, 0x000000002, 0x000000003,
+	    0x000000004, 0x000000005, 0x000000006, 0x000000007, 0x000000008,
+	    0x000000009, 0x00000000A, 0x00000000B, 0x00000000C};
+	EXPECT_EQ(words, ref);
+}
+
+TEST(BitstreamWriterTest, WriteMulti) {
+	std::vector<std::unique_ptr<ConfigurationPacket<Series7::ConfRegType>>>
+	    packets;
+	AddType1(packets);
+	AddType1E(packets);
+	AddType2(packets);
+	AddType1E(packets);
+	BitstreamWriter<Series7> writer(packets);
+	// dump_packets(writer, false);
+	std::vector<uint32_t> words(writer.begin(), writer.end());
+	std::vector<uint32_t> ref{
+	    // Bus width + sync
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044,
+	    0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566,
+	    // Type1
+	    0x030006002, 0x0000000AA, 0x0000000BB,
+	    // Type1
+	    0x030006000,
+	    // Type 1 + type 2 header
+	    0x030006000, 0x04800000C, 0x000000001, 0x000000002, 0x000000003,
+	    0x000000004, 0x000000005, 0x000000006, 0x000000007, 0x000000008,
+	    0x000000009, 0x00000000A, 0x00000000B, 0x00000000C,
+	    // Type 1
+	    0x030006000};
+	EXPECT_EQ(words, ref);
+}
diff --git a/lib/xilinx/tests/xc7series/block_type_test.cc b/lib/xilinx/tests/xc7series/block_type_test.cc
new file mode 100644
index 0000000..6fc8ecc
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/block_type_test.cc
@@ -0,0 +1,30 @@
+#include <prjxray/xilinx/xc7series/block_type.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(BlockTypeTest, YamlEncode) {
+	YAML::Node node;
+	node.push_back(xc7series::BlockType::CLB_IO_CLK);
+	node.push_back(xc7series::BlockType::BLOCK_RAM);
+	node.push_back(xc7series::BlockType::CFG_CLB);
+
+	EXPECT_EQ(node[0].as<std::string>(), "CLB_IO_CLK");
+	EXPECT_EQ(node[1].as<std::string>(), "BLOCK_RAM");
+	EXPECT_EQ(node[2].as<std::string>(), "CFG_CLB");
+}
+
+TEST(BlockTypeTest, YamlDecode) {
+	YAML::Node node;
+	node.push_back("CFG_CLB");
+	node.push_back("BLOCK_RAM");
+	node.push_back("CLB_IO_CLK");
+
+	EXPECT_EQ(node[0].as<xc7series::BlockType>(),
+	          xc7series::BlockType::CFG_CLB);
+	EXPECT_EQ(node[1].as<xc7series::BlockType>(),
+	          xc7series::BlockType::BLOCK_RAM);
+	EXPECT_EQ(node[2].as<xc7series::BlockType>(),
+	          xc7series::BlockType::CLB_IO_CLK);
+}
diff --git a/lib/xilinx/tests/xc7series/configuration_bus_test.cc b/lib/xilinx/tests/xc7series/configuration_bus_test.cc
new file mode 100644
index 0000000..c665d9d
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/configuration_bus_test.cc
@@ -0,0 +1,72 @@
+#include <prjxray/xilinx/xc7series/configuration_bus.h>
+
+#include <gtest/gtest.h>
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+TEST(ConfigurationBusTest, IsValidFrameAddress) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 1));
+
+	xc7series::ConfigurationBus bus(addresses.begin(), addresses.end());
+
+	EXPECT_TRUE(bus.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0)));
+	EXPECT_TRUE(bus.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 1)));
+
+	EXPECT_FALSE(bus.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2)));
+}
+
+TEST(ConfigurationBusTest, GetNextFrameAddressYieldNextAddressInBus) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 1));
+
+	xc7series::ConfigurationBus bus(addresses.begin(), addresses.end());
+
+	auto next_address = bus.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::BLOCK_RAM,
+	                                  false, 0, 0, 1));
+
+	next_address = bus.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::BLOCK_RAM,
+	                                  false, 0, 1, 0));
+}
+
+TEST(ConfigurationBusTest, GetNextFrameAddressYieldNothingAtEndOfBus) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 1));
+
+	xc7series::ConfigurationBus bus(addresses.begin(), addresses.end());
+
+	EXPECT_FALSE(bus.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 1, 1)));
+}
diff --git a/lib/xilinx/tests/xc7series/configuration_column_test.cc b/lib/xilinx/tests/xc7series/configuration_column_test.cc
new file mode 100644
index 0000000..49f4920
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/configuration_column_test.cc
@@ -0,0 +1,65 @@
+#include <prjxray/xilinx/xc7series/configuration_column.h>
+
+#include <gtest/gtest.h>
+#include <prjxray/xilinx/xc7series/block_type.h>
+#include <prjxray/xilinx/xc7series/frame_address.h>
+#include <yaml-cpp/yaml.h>
+
+using namespace prjxray::xilinx;
+
+TEST(ConfigurationColumnTest, IsValidFrameAddress) {
+	xc7series::ConfigurationColumn column(10);
+
+	// Inside this column.
+	EXPECT_TRUE(column.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 2, 3)));
+	// Past this column's frame width.
+	EXPECT_FALSE(column.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 2, 10)));
+}
+
+TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNextAddressInColumn) {
+	xc7series::ConfigurationColumn column(10);
+
+	auto next_address = column.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 2, 3));
+	EXPECT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 1, 2, 4));
+}
+
+TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingAtEndOfColumn) {
+	xc7series::ConfigurationColumn column(10);
+
+	EXPECT_FALSE(column.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 2, 9)));
+}
+
+TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingOutsideColumn) {
+	xc7series::ConfigurationColumn column(10);
+
+	// Just past last frame in column.
+	EXPECT_FALSE(column.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 2, 10)));
+}
+
+TEST(ConfigurationColumnTest, YamlEncodeTest) {
+	xc7series::ConfigurationColumn column(10);
+
+	YAML::Node node(column);
+	EXPECT_TRUE(node["frame_count"]);
+	EXPECT_EQ(node["frame_count"].as<int>(), 10);
+}
+
+TEST(ConfigurationColumnTest, YAMLDecodeTest) {
+	YAML::Node node;
+	node.SetTag("xilinx/xc7series/configuration_column");
+	node["frame_count"] = 10;
+
+	auto column = node.as<xc7series::ConfigurationColumn>();
+	EXPECT_TRUE(column.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 2, 8)));
+	EXPECT_FALSE(column.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 2, 9)));
+}
diff --git a/lib/xilinx/tests/xc7series/configuration_packet_test.cc b/lib/xilinx/tests/xc7series/configuration_packet_test.cc
new file mode 100644
index 0000000..1658f14
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/configuration_packet_test.cc
@@ -0,0 +1,105 @@
+#include <functional>
+
+#include <absl/meta/type_traits.h>
+#include <gtest/gtest.h>
+#include <prjxray/bit_ops.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/configuration_packet.h>
+
+using namespace prjxray::xilinx;
+
+constexpr uint32_t kType1NOP = prjxray::bit_field_set<uint32_t>(0, 31, 29, 0x1);
+
+constexpr uint32_t MakeType1(const int opcode,
+                             const int address,
+                             const int word_count) {
+	return prjxray::bit_field_set<uint32_t>(
+	    prjxray::bit_field_set<uint32_t>(
+	        prjxray::bit_field_set<uint32_t>(
+	            prjxray::bit_field_set<uint32_t>(0x0, 31, 29, 0x1), 28, 27,
+	            opcode),
+	        26, 13, address),
+	    10, 0, word_count);
+}
+
+constexpr uint32_t MakeType2(const int opcode, const int word_count) {
+	return prjxray::bit_field_set<uint32_t>(
+	    prjxray::bit_field_set<uint32_t>(
+	        prjxray::bit_field_set<uint32_t>(0x0, 31, 29, 0x2), 28, 27,
+	        opcode),
+	    26, 0, word_count);
+}
+
+TEST(ConfigPacket, InitWithZeroBytes) {
+	auto packet =
+	    ConfigurationPacket<Series7::ConfRegType>::InitWithWords({});
+
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	EXPECT_FALSE(packet.second);
+}
+
+TEST(ConfigPacket, InitWithType1Nop) {
+	std::vector<uint32_t> words{kType1NOP};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(word_span);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Series7::ConfRegType>::Opcode::NOP);
+	EXPECT_EQ(packet.second->address(), Series7::ConfRegType::CRC);
+	EXPECT_EQ(packet.second->data(), absl::Span<uint32_t>());
+}
+
+TEST(ConfigPacket, InitWithType1Read) {
+	std::vector<uint32_t> words{MakeType1(0x1, 0x2, 2), 0xAA, 0xBB};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(word_span);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Series7::ConfRegType>::Opcode::Read);
+	EXPECT_EQ(packet.second->address(), Series7::ConfRegType::FDRI);
+	EXPECT_EQ(packet.second->data(), word_span.subspan(1));
+}
+
+TEST(ConfigPacket, InitWithType1Write) {
+	std::vector<uint32_t> words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(word_span);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Series7::ConfRegType>::Opcode::Write);
+	EXPECT_EQ(packet.second->address(), Series7::ConfRegType::FDRO);
+	EXPECT_EQ(packet.second->data(), word_span.subspan(1));
+}
+
+TEST(ConfigPacket, InitWithType2WithoutPreviousPacketFails) {
+	std::vector<uint32_t> words{MakeType2(0x01, 12)};
+	absl::Span<uint32_t> word_span(words);
+	auto packet =
+	    ConfigurationPacket<Series7::ConfRegType>::InitWithWords(word_span);
+	EXPECT_EQ(packet.first, words);
+	EXPECT_FALSE(packet.second);
+}
+
+TEST(ConfigPacket, InitWithType2WithPreviousPacket) {
+	ConfigurationPacket<Series7::ConfRegType> previous_packet(
+	    static_cast<unsigned int>(0x1),
+	    ConfigurationPacket<Series7::ConfRegType>::Opcode::Read,
+	    Series7::ConfRegType::MFWR, absl::Span<uint32_t>());
+	std::vector<uint32_t> words{
+	    MakeType2(0x01, 12), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+	absl::Span<uint32_t> word_span(words);
+	auto packet = ConfigurationPacket<Series7::ConfRegType>::InitWithWords(
+	    word_span, &previous_packet);
+	EXPECT_EQ(packet.first, absl::Span<uint32_t>());
+	ASSERT_TRUE(packet.second);
+	EXPECT_EQ(packet.second->opcode(),
+	          ConfigurationPacket<Series7::ConfRegType>::Opcode::Read);
+	EXPECT_EQ(packet.second->address(), Series7::ConfRegType::MFWR);
+	EXPECT_EQ(packet.second->data(), word_span.subspan(1));
+}
diff --git a/lib/xilinx/tests/xc7series/configuration_test.cc b/lib/xilinx/tests/xc7series/configuration_test.cc
new file mode 100644
index 0000000..0c27d58
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/configuration_test.cc
@@ -0,0 +1,311 @@
+#include <cstdint>
+#include <iostream>
+#include <vector>
+
+#include <absl/types/span.h>
+#include <gtest/gtest.h>
+#include <prjxray/memory_mapped_file.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <prjxray/xilinx/configuration.h>
+#include <prjxray/xilinx/configuration_packet.h>
+#include <prjxray/xilinx/configuration_register.h>
+#include <prjxray/xilinx/frames.h>
+#include <yaml-cpp/yaml.h>
+
+using namespace prjxray::xilinx;
+
+TEST(ConfigurationTest, ConstructFromPacketsWithSingleFrame) {
+	std::vector<Series7::FrameAddress> test_part_addresses;
+	test_part_addresses.push_back(0x4567);
+	test_part_addresses.push_back(0x4568);
+
+	xc7series::Part test_part(0x1234, test_part_addresses);
+
+	std::vector<uint32_t> idcode{0x1234};
+	std::vector<uint32_t> cmd{0x0001};
+	std::vector<uint32_t> frame_address{0x4567};
+	std::vector<uint32_t> frame(101, 0xAA);
+
+	std::vector<ConfigurationPacket<Series7::ConfRegType>> packets{
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::IDCODE,
+	        absl::MakeSpan(idcode),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::FAR,
+	        absl::MakeSpan(frame_address),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::CMD,
+	        absl::MakeSpan(cmd),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::FDRI,
+	        absl::MakeSpan(frame),
+	    },
+	};
+
+	auto test_config =
+	    Configuration<Series7>::InitWithPackets(test_part, packets);
+	ASSERT_TRUE(test_config);
+
+	EXPECT_EQ(test_config->part().idcode(), static_cast<uint32_t>(0x1234));
+	EXPECT_EQ(test_config->frames().size(), static_cast<size_t>(1));
+	EXPECT_EQ(test_config->frames().at(0x4567), frame);
+}
+
+TEST(ConfigurationTest, ConstructFromPacketsWithAutoincrement) {
+	std::vector<xc7series::FrameAddress> test_part_addresses;
+	for (int ii = 0x4560; ii < 0x4570; ++ii) {
+		test_part_addresses.push_back(ii);
+	}
+	for (int ii = 0x4580; ii < 0x4590; ++ii) {
+		test_part_addresses.push_back(ii);
+	}
+
+	xc7series::Part test_part(0x1234, test_part_addresses);
+
+	std::vector<uint32_t> idcode{0x1234};
+	std::vector<uint32_t> cmd{0x0001};
+	std::vector<uint32_t> frame_address{0x456F};
+	std::vector<uint32_t> frame(202, 0xAA);
+	std::fill_n(frame.begin() + 101, 101, 0xBB);
+
+	std::vector<ConfigurationPacket<Series7::ConfRegType>> packets{
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::IDCODE,
+	        absl::MakeSpan(idcode),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::FAR,
+	        absl::MakeSpan(frame_address),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::CMD,
+	        absl::MakeSpan(cmd),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::FDRI,
+	        absl::MakeSpan(frame),
+	    },
+	};
+
+	auto test_config =
+	    Configuration<Series7>::InitWithPackets(test_part, packets);
+	ASSERT_TRUE(test_config);
+
+	absl::Span<uint32_t> frame_span(frame);
+	EXPECT_EQ(test_config->part().idcode(), static_cast<uint32_t>(0x1234));
+	EXPECT_EQ(test_config->frames().size(), static_cast<size_t>(2));
+	EXPECT_EQ(test_config->frames().at(0x456F),
+	          std::vector<uint32_t>(101, 0xAA));
+	EXPECT_EQ(test_config->frames().at(0x4580),
+	          std::vector<uint32_t>(101, 0xBB));
+}
+
+TEST(ConfigurationTest,
+     DebugAndPerFrameCrcBitstreamsProduceEqualConfigurations) {
+	auto part = xc7series::Part::FromFile("configuration_test.yaml");
+	ASSERT_TRUE(part);
+
+	auto debug_bitstream = prjxray::MemoryMappedFile::InitWithFile(
+	    "configuration_test.debug.bit");
+	ASSERT_TRUE(debug_bitstream);
+
+	auto debug_reader = BitstreamReader<Series7>::InitWithBytes(
+	    debug_bitstream->as_bytes());
+	ASSERT_TRUE(debug_reader);
+
+	auto debug_configuration =
+	    Configuration<Series7>::InitWithPackets(*part, *debug_reader);
+	ASSERT_TRUE(debug_configuration);
+
+	auto perframecrc_bitstream = prjxray::MemoryMappedFile::InitWithFile(
+	    "configuration_test.perframecrc.bit");
+	ASSERT_TRUE(perframecrc_bitstream);
+
+	auto perframecrc_reader = BitstreamReader<Series7>::InitWithBytes(
+	    perframecrc_bitstream->as_bytes());
+	ASSERT_TRUE(perframecrc_reader);
+
+	auto perframecrc_configuration =
+	    Configuration<Series7>::InitWithPackets(*part, *perframecrc_reader);
+	ASSERT_TRUE(perframecrc_configuration);
+
+	for (auto debug_frame : debug_configuration->frames()) {
+		auto perframecrc_frame =
+		    perframecrc_configuration->frames().find(debug_frame.first);
+		if (perframecrc_frame ==
+		    perframecrc_configuration->frames().end()) {
+			ADD_FAILURE() << debug_frame.first
+			              << ": missing in perframecrc bitstream";
+			continue;
+		}
+
+		for (int ii = 0; ii < 101; ++ii) {
+			EXPECT_EQ(perframecrc_frame->second[ii],
+			          debug_frame.second[ii])
+			    << debug_frame.first << ": word " << ii;
+		}
+	}
+
+	for (auto perframecrc_frame : perframecrc_configuration->frames()) {
+		auto debug_frame =
+		    debug_configuration->frames().find(perframecrc_frame.first);
+		if (debug_frame == debug_configuration->frames().end()) {
+			ADD_FAILURE() << perframecrc_frame.first
+			              << ": unexpectedly present in "
+			                 "perframecrc bitstream";
+		}
+	}
+}
+
+TEST(ConfigurationTest, DebugAndNormalBitstreamsProduceEqualConfigurations) {
+	auto part = xc7series::Part::FromFile("configuration_test.yaml");
+	ASSERT_TRUE(part);
+
+	auto debug_bitstream = prjxray::MemoryMappedFile::InitWithFile(
+	    "configuration_test.debug.bit");
+	ASSERT_TRUE(debug_bitstream);
+
+	auto debug_reader = BitstreamReader<Series7>::InitWithBytes(
+	    debug_bitstream->as_bytes());
+	ASSERT_TRUE(debug_reader);
+
+	auto debug_configuration =
+	    Configuration<Series7>::InitWithPackets(*part, *debug_reader);
+	ASSERT_TRUE(debug_configuration);
+
+	auto normal_bitstream =
+	    prjxray::MemoryMappedFile::InitWithFile("configuration_test.bit");
+	ASSERT_TRUE(normal_bitstream);
+
+	auto normal_reader = BitstreamReader<Series7>::InitWithBytes(
+	    normal_bitstream->as_bytes());
+	ASSERT_TRUE(normal_reader);
+
+	auto normal_configuration =
+	    Configuration<Series7>::InitWithPackets(*part, *normal_reader);
+	ASSERT_TRUE(normal_configuration);
+
+	for (auto debug_frame : debug_configuration->frames()) {
+		auto normal_frame =
+		    normal_configuration->frames().find(debug_frame.first);
+		if (normal_frame == normal_configuration->frames().end()) {
+			ADD_FAILURE() << debug_frame.first
+			              << ": missing in normal bitstream";
+			continue;
+		}
+
+		for (int ii = 0; ii < 101; ++ii) {
+			EXPECT_EQ(normal_frame->second[ii],
+			          debug_frame.second[ii])
+			    << debug_frame.first << ": word " << ii;
+		}
+	}
+
+	for (auto normal_frame : normal_configuration->frames()) {
+		auto debug_frame =
+		    debug_configuration->frames().find(normal_frame.first);
+		if (debug_frame == debug_configuration->frames().end()) {
+			ADD_FAILURE()
+			    << normal_frame.first
+			    << ": unexpectedly present in normal bitstream";
+		}
+	}
+}
+
+TEST(ConfigurationTest, CheckForPaddingFrames) {
+	std::vector<xc7series::FrameAddress> test_part_addresses = {
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, false, 0,
+	                            0, 0),
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, true, 0,
+	                            0, 0),
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, true, 1,
+	                            0, 0),
+	    xc7series::FrameAddress(xc7series::BlockType::BLOCK_RAM, false, 0,
+	                            0, 0),
+	    xc7series::FrameAddress(xc7series::BlockType::BLOCK_RAM, false, 1,
+	                            0, 0)};
+
+	auto test_part = absl::optional<xc7series::Part>(
+	    xc7series::Part(0x1234, test_part_addresses));
+
+	Frames<Series7> frames;
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(0), std::vector<uint32_t>(101, 0xAA)));
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(1), std::vector<uint32_t>(101, 0xBB)));
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(2), std::vector<uint32_t>(101, 0xCC)));
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(3), std::vector<uint32_t>(101, 0xDD)));
+	frames.getFrames().emplace(std::make_pair(
+	    test_part_addresses.at(4), std::vector<uint32_t>(101, 0xEE)));
+	ASSERT_EQ(frames.getFrames().size(), 5);
+
+	Configuration<Series7>::PacketData packet_data =
+	    Configuration<Series7>::createType2ConfigurationPacketData(
+	        frames.getFrames(), test_part);
+	// createType2ConfigurationPacketData should detect 4
+	// row/half/block_type switches thus add 4*2 padding frames  moreover 2
+	// extra padding frames are added at the end of the creation of the data
+	// overall this gives us: 5(real frames) + 4*2 + 2 = 15 frames, which is
+	// 15 * 101 = 1515 words
+	EXPECT_EQ(packet_data.size(), 15 * 101);
+
+	std::vector<uint32_t> idcode{0x1234};
+	std::vector<uint32_t> cmd{0x0001};
+	std::vector<uint32_t> frame_address{0x0};
+
+	std::vector<ConfigurationPacket<Series7::ConfRegType>> packets{
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::IDCODE,
+	        absl::MakeSpan(idcode),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::FAR,
+	        absl::MakeSpan(frame_address),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::CMD,
+	        absl::MakeSpan(cmd),
+	    },
+	    {
+	        static_cast<unsigned int>(0x1),
+	        ConfigurationPacket<Series7::ConfRegType>::Opcode::Write,
+	        Series7::ConfRegType::FDRI,
+	        absl::MakeSpan(packet_data),
+	    },
+	};
+
+	auto test_config =
+	    Configuration<Series7>::InitWithPackets(*test_part, packets);
+	ASSERT_EQ(test_config->frames().size(), 5);
+	for (auto& frame : test_config->frames()) {
+		EXPECT_EQ(frame.second, frames.getFrames().at(frame.first));
+	}
+}
diff --git a/lib/xilinx/tests/xc7series/crc_test.cc b/lib/xilinx/tests/xc7series/crc_test.cc
new file mode 100644
index 0000000..6a71135
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/crc_test.cc
@@ -0,0 +1,16 @@
+#include <prjxray/xilinx/xc7series/crc.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(IcapCrcTest, SimpleTests) {
+	// CRC for Zero Data
+	EXPECT_EQ(xc7series::icap_crc(0, 0, 0), 0x0L);
+	// Polynomial (single bit operation)
+	EXPECT_EQ(xc7series::icap_crc(1 << 4, 0, 0), 0x82F63B78);
+	// All Reg/Data bits
+	EXPECT_EQ(xc7series::icap_crc(~0, ~0, 0), 0xBF86D4DF);
+	// All CRC bits
+	EXPECT_EQ(xc7series::icap_crc(0, 0, ~0), 0xC631E365);
+}
diff --git a/lib/xilinx/tests/xc7series/ecc_test.cc b/lib/xilinx/tests/xc7series/ecc_test.cc
new file mode 100644
index 0000000..eb3b4da
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/ecc_test.cc
@@ -0,0 +1,20 @@
+#include <prjxray/xilinx/xc7series/ecc.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(IcapEccTest, SimpleTests) {
+	// ECC for Zero Data
+	EXPECT_EQ(xc7series::icap_ecc(0, 0, 0), (uint32_t)0x0);
+	// 0x1320 - 0x13FF (avoid lower)
+	EXPECT_EQ(xc7series::icap_ecc(0, 1, 0), (uint32_t)0x1320);
+	// 0x1420 - 0x17FF (avoid 0x400)
+	EXPECT_EQ(xc7series::icap_ecc(0x7, 1, 0), (uint32_t)0x1420);
+	// 0x1820 - 0x1FFF (avoid 0x800)
+	EXPECT_EQ(xc7series::icap_ecc(0x26, 1, 0), (uint32_t)0x1820);
+	// Masked ECC Value
+	EXPECT_EQ(xc7series::icap_ecc(0x32, ~0, 0), (uint32_t)0x000019AC);
+	// Final ECC Parity
+	EXPECT_EQ(xc7series::icap_ecc(0x64, 0, 1), (uint32_t)0x00001001);
+}
diff --git a/lib/xilinx/tests/xc7series/frame_address_test.cc b/lib/xilinx/tests/xc7series/frame_address_test.cc
new file mode 100644
index 0000000..70c1f59
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/frame_address_test.cc
@@ -0,0 +1,36 @@
+#include <prjxray/xilinx/xc7series/frame_address.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(FrameAddressTest, YamlEncode) {
+	xc7series::FrameAddress address(xc7series::BlockType::BLOCK_RAM, false,
+	                                10, 0, 5);
+
+	YAML::Node node(address);
+
+	EXPECT_EQ(node.Tag(), "xilinx/xc7series/frame_address");
+	EXPECT_EQ(node["block_type"].as<std::string>(), "BLOCK_RAM");
+	EXPECT_EQ(node["row_half"].as<std::string>(), "top");
+	EXPECT_EQ(node["row"].as<std::string>(), "10");
+	EXPECT_EQ(node["column"].as<std::string>(), "0");
+	EXPECT_EQ(node["minor"].as<std::string>(), "5");
+}
+
+TEST(FrameAddressTest, YamlDecode) {
+	YAML::Node node;
+	node.SetTag("xilinx/xc7series/frame_address");
+	node["block_type"] = "BLOCK_RAM";
+	node["row_half"] = "bottom";
+	node["row"] = "0";
+	node["column"] = "5";
+	node["minor"] = "11";
+
+	xc7series::FrameAddress address = node.as<xc7series::FrameAddress>();
+	EXPECT_EQ(address.block_type(), xc7series::BlockType::BLOCK_RAM);
+	EXPECT_TRUE(address.is_bottom_half_rows());
+	EXPECT_EQ(address.row(), 0);
+	EXPECT_EQ(address.column(), 5);
+	EXPECT_EQ(address.minor(), 11);
+}
diff --git a/lib/xilinx/tests/xc7series/frames_test.cc b/lib/xilinx/tests/xc7series/frames_test.cc
new file mode 100644
index 0000000..3cf4525
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/frames_test.cc
@@ -0,0 +1,55 @@
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/frames.h>
+#include <prjxray/xilinx/xc7series/part.h>
+
+using namespace prjxray::xilinx;
+
+TEST(FramesTest, FillInMissingFrames) {
+	std::vector<xc7series::FrameAddress> test_part_addresses = {
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, false, 0,
+	                            0, 0),
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, false, 0,
+	                            0, 1),
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, false, 0,
+	                            0, 2),
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, false, 0,
+	                            0, 3),
+	    xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK, false, 0,
+	                            0, 4)};
+
+	xc7series::Part test_part(0x1234, test_part_addresses);
+
+	Frames<Series7> frames;
+	frames.getFrames().emplace(std::make_pair(
+	    xc7series::FrameAddress(2), std::vector<uint32_t>(101, 0xCC)));
+	frames.getFrames().emplace(std::make_pair(
+	    xc7series::FrameAddress(3), std::vector<uint32_t>(101, 0xDD)));
+	frames.getFrames().emplace(std::make_pair(
+	    xc7series::FrameAddress(4), std::vector<uint32_t>(101, 0xEE)));
+
+	ASSERT_EQ(frames.getFrames().size(), 3);
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[2]),
+	          std::vector<uint32_t>(101, 0xCC));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[3]),
+	          std::vector<uint32_t>(101, 0xDD));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[4]),
+	          std::vector<uint32_t>(101, 0xEE));
+
+	frames.addMissingFrames(test_part);
+
+	ASSERT_EQ(frames.getFrames().size(), 5);
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[0]),
+	          std::vector<uint32_t>(101, 0));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[1]),
+	          std::vector<uint32_t>(101, 0));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[2]),
+	          std::vector<uint32_t>(101, 0xCC));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[3]),
+	          std::vector<uint32_t>(101, 0xDD));
+	EXPECT_EQ(frames.getFrames().at(test_part_addresses[4]),
+	          std::vector<uint32_t>(101, 0xEE));
+}
diff --git a/lib/xilinx/tests/xc7series/global_clock_region_test.cc b/lib/xilinx/tests/xc7series/global_clock_region_test.cc
new file mode 100644
index 0000000..4597893
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/global_clock_region_test.cc
@@ -0,0 +1,147 @@
+#include <prjxray/xilinx/xc7series/global_clock_region.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(GlobalClockRegionTest, IsValidFrameAddress) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1));
+
+	xc7series::GlobalClockRegion global_clock_region(addresses.begin(),
+	                                                 addresses.end());
+
+	EXPECT_TRUE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0)));
+	EXPECT_TRUE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0)));
+	EXPECT_TRUE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2)));
+	EXPECT_TRUE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0)));
+
+	EXPECT_FALSE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 2)));
+	EXPECT_FALSE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 0, 2, 0)));
+	EXPECT_FALSE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 2, 0, 0)));
+	EXPECT_FALSE(
+	    global_clock_region.IsValidFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CFG_CLB, false, 0, 0, 2)));
+}
+
+TEST(GlobalClockRegionTest,
+     GetNextFrameAddressYieldNextAddressInGlobalClockRegion) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1));
+
+	xc7series::GlobalClockRegion global_clock_region(addresses.begin(),
+	                                                 addresses.end());
+
+	auto next_address =
+	    global_clock_region.GetNextFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 0, 0, 1));
+
+	next_address =
+	    global_clock_region.GetNextFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 0, 1, 0));
+
+	next_address =
+	    global_clock_region.GetNextFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 1, 0, 0));
+
+	next_address =
+	    global_clock_region.GetNextFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::BLOCK_RAM,
+	                                  false, 0, 0, 2));
+}
+
+TEST(GlobalClockRegionTest,
+     GetNextFrameAddressYieldNothingAtEndOfGlobalClockRegion) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1));
+
+	xc7series::GlobalClockRegion global_clock_region(addresses.begin(),
+	                                                 addresses.end());
+
+	EXPECT_FALSE(
+	    global_clock_region.GetNextFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1)));
+	EXPECT_FALSE(
+	    global_clock_region.GetNextFrameAddress(xc7series::FrameAddress(
+	        xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2)));
+}
diff --git a/lib/xilinx/tests/xc7series/part_test.cc b/lib/xilinx/tests/xc7series/part_test.cc
new file mode 100644
index 0000000..16b7e99
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/part_test.cc
@@ -0,0 +1,149 @@
+#include <prjxray/xilinx/xc7series/part.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(PartTest, IsValidFrameAddress) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 1));
+
+	xc7series::Part part(0x1234, addresses.begin(), addresses.end());
+
+	EXPECT_TRUE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0)));
+	EXPECT_TRUE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0)));
+	EXPECT_TRUE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2)));
+	EXPECT_TRUE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0)));
+	EXPECT_TRUE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 0)));
+
+	EXPECT_FALSE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 2)));
+	EXPECT_FALSE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 2, 0)));
+	EXPECT_FALSE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 2, 0, 0)));
+	EXPECT_FALSE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CFG_CLB, false, 0, 0, 2)));
+	EXPECT_FALSE(part.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 1, 0)));
+}
+
+TEST(PartTest, GetNextFrameAddressYieldNextAddressInPart) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 1));
+
+	xc7series::Part part(0x1234, addresses.begin(), addresses.end());
+
+	auto next_address = part.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 0, 0, 1));
+
+	next_address = part.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 0, 1, 0));
+
+	next_address = part.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 1, 0, 0));
+
+	next_address = part.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  true, 0, 0, 0));
+
+	next_address = part.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::BLOCK_RAM,
+	                                  false, 0, 0, 0));
+}
+
+TEST(PartTest, GetNextFrameAddressYieldNothingAtEndOfPart) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 1, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, true, 0, 0, 1));
+
+	xc7series::Part part(0x1234, addresses.begin(), addresses.end());
+
+	EXPECT_FALSE(part.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2)));
+}
diff --git a/lib/xilinx/tests/xc7series/row_test.cc b/lib/xilinx/tests/xc7series/row_test.cc
new file mode 100644
index 0000000..e8f7318
--- /dev/null
+++ b/lib/xilinx/tests/xc7series/row_test.cc
@@ -0,0 +1,115 @@
+#include <prjxray/xilinx/xc7series/configuration_row.h>
+
+#include <gtest/gtest.h>
+
+using namespace prjxray::xilinx;
+
+TEST(RowTest, IsValidFrameAddress) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+
+	xc7series::Row row(addresses.begin(), addresses.end());
+
+	EXPECT_TRUE(row.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0)));
+	EXPECT_TRUE(row.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0)));
+	EXPECT_TRUE(row.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2)));
+
+	EXPECT_FALSE(row.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 2)));
+	EXPECT_FALSE(row.IsValidFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 2, 0)));
+}
+
+TEST(RowTest, GetNextFrameAddressYieldNextAddressInRow) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+
+	xc7series::Row row(addresses.begin(), addresses.end());
+
+	auto next_address = row.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 0, 0, 1));
+
+	next_address = row.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::CLB_IO_CLK,
+	                                  false, 0, 1, 0));
+
+	// Rows have unique behavior for GetNextFrameAddress() at the end of a
+	// bus. Since the addresses need to be returned in numerically
+	// increasing order, all of the rows need to be returned before moving
+	// to a different bus.  That means that Row::GetNextFrameAddress() needs
+	// to return no object at the end of a bus and let the caller use that
+	// as a signal to try the next row.
+	next_address = row.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	EXPECT_FALSE(next_address);
+
+	next_address = row.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	ASSERT_TRUE(next_address);
+	EXPECT_EQ(*next_address,
+	          xc7series::FrameAddress(xc7series::BlockType::BLOCK_RAM,
+	                                  false, 0, 0, 2));
+
+	next_address = row.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+	EXPECT_FALSE(next_address);
+}
+
+TEST(RowTest, GetNextFrameAddressYieldNothingAtEndOfRow) {
+	std::vector<xc7series::FrameAddress> addresses;
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::CLB_IO_CLK, false, 0, 1, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 0));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 1));
+	addresses.push_back(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2));
+
+	xc7series::Row row(addresses.begin(), addresses.end());
+
+	EXPECT_FALSE(row.GetNextFrameAddress(xc7series::FrameAddress(
+	    xc7series::BlockType::BLOCK_RAM, false, 0, 0, 2)));
+}
diff --git a/lib/xilinx/xc7series/block_type.cc b/lib/xilinx/xc7series/block_type.cc
new file mode 100644
index 0000000..d74537f
--- /dev/null
+++ b/lib/xilinx/xc7series/block_type.cc
@@ -0,0 +1,62 @@
+#include <prjxray/xilinx/xc7series/block_type.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+std::ostream& operator<<(std::ostream& o, BlockType value) {
+	switch (value) {
+		case BlockType::CLB_IO_CLK:
+			o << "CLB/IO/CLK";
+			break;
+		case BlockType::BLOCK_RAM:
+			o << "Block RAM";
+			break;
+		case BlockType::CFG_CLB:
+			o << "Config CLB";
+			break;
+	}
+
+	return o;
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+
+Node convert<prjxray::xilinx::xc7series::BlockType>::encode(
+    const prjxray::xilinx::xc7series::BlockType& rhs) {
+	switch (rhs) {
+		case prjxray::xilinx::xc7series::BlockType::CLB_IO_CLK:
+			return Node("CLB_IO_CLK");
+		case prjxray::xilinx::xc7series::BlockType::BLOCK_RAM:
+			return Node("BLOCK_RAM");
+		case prjxray::xilinx::xc7series::BlockType::CFG_CLB:
+			return Node("CFG_CLB");
+		default:
+			return Node(static_cast<unsigned int>(rhs));
+	}
+}
+
+bool YAML::convert<prjxray::xilinx::xc7series::BlockType>::decode(
+    const Node& node,
+    prjxray::xilinx::xc7series::BlockType& lhs) {
+	auto type_str = node.as<std::string>();
+
+	if (type_str == "CLB_IO_CLK") {
+		lhs = prjxray::xilinx::xc7series::BlockType::CLB_IO_CLK;
+		return true;
+	} else if (type_str == "BLOCK_RAM") {
+		lhs = prjxray::xilinx::xc7series::BlockType::BLOCK_RAM;
+		return true;
+	} else if (type_str == "CFG_CLB") {
+		lhs = prjxray::xilinx::xc7series::BlockType::CFG_CLB;
+		return true;
+	} else {
+		return false;
+	}
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/xc7series/configuration_bus.cc b/lib/xilinx/xc7series/configuration_bus.cc
new file mode 100644
index 0000000..e0a2105
--- /dev/null
+++ b/lib/xilinx/xc7series/configuration_bus.cc
@@ -0,0 +1,77 @@
+#include <prjxray/xilinx/xc7series/configuration_bus.h>
+
+#include <iostream>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+bool ConfigurationBus::IsValidFrameAddress(FrameAddress address) const {
+	auto addr_column = configuration_columns_.find(address.column());
+	if (addr_column == configuration_columns_.end())
+		return false;
+
+	return addr_column->second.IsValidFrameAddress(address);
+}
+
+absl::optional<FrameAddress> ConfigurationBus::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Find the column for the current address.
+	auto addr_column = configuration_columns_.find(address.column());
+
+	// If the current address isn't in a known column, no way to know the
+	// next address.
+	if (addr_column == configuration_columns_.end())
+		return {};
+
+	// Ask the column for the next address.
+	absl::optional<FrameAddress> next_address =
+	    addr_column->second.GetNextFrameAddress(address);
+	if (next_address)
+		return next_address;
+
+	// The current column doesn't know what the next address is.  Assume
+	// that the next valid address is the beginning of the next column.
+	if (++addr_column != configuration_columns_.end()) {
+		auto next_address = FrameAddress(
+		    address.block_type(), address.is_bottom_half_rows(),
+		    address.row(), addr_column->first, 0);
+		if (addr_column->second.IsValidFrameAddress(next_address))
+			return next_address;
+	}
+
+	// Not in this bus.
+	return {};
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+namespace YAML {
+
+Node convert<xc7series::ConfigurationBus>::encode(
+    const xc7series::ConfigurationBus& rhs) {
+	Node node;
+	node.SetTag("xilinx/xc7series/configuration_bus");
+	node["configuration_columns"] = rhs.configuration_columns_;
+	return node;
+}
+
+bool convert<xc7series::ConfigurationBus>::decode(
+    const Node& node,
+    xc7series::ConfigurationBus& lhs) {
+	if (!node.Tag().empty() &&
+	    node.Tag() != "xilinx/xc7series/configuration_bus") {
+		return false;
+	}
+
+	lhs.configuration_columns_ =
+	    node["configuration_columns"]
+	        .as<std::map<unsigned int, xc7series::ConfigurationColumn>>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/xc7series/configuration_column.cc b/lib/xilinx/xc7series/configuration_column.cc
new file mode 100644
index 0000000..f979383
--- /dev/null
+++ b/lib/xilinx/xc7series/configuration_column.cc
@@ -0,0 +1,52 @@
+#include <prjxray/xilinx/xc7series/configuration_column.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+bool ConfigurationColumn::IsValidFrameAddress(FrameAddress address) const {
+	return address.minor() < frame_count_;
+}
+
+absl::optional<FrameAddress> ConfigurationColumn::GetNextFrameAddress(
+    FrameAddress address) const {
+	if (!IsValidFrameAddress(address))
+		return {};
+
+	if (static_cast<unsigned int>(address.minor() + 1) < frame_count_) {
+		return address + 1;
+	}
+
+	// Next address is not in this column.
+	return {};
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+namespace YAML {
+
+Node convert<xc7series::ConfigurationColumn>::encode(
+    const xc7series::ConfigurationColumn& rhs) {
+	Node node;
+	node.SetTag("xilinx/xc7series/configuration_column");
+	node["frame_count"] = rhs.frame_count_;
+	return node;
+}
+
+bool convert<xc7series::ConfigurationColumn>::decode(
+    const Node& node,
+    xc7series::ConfigurationColumn& lhs) {
+	if (!node.Tag().empty() &&
+	    node.Tag() != "xilinx/xc7series/configuration_column") {
+		return false;
+	}
+
+	lhs.frame_count_ = node["frame_count"].as<unsigned int>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/xc7series/configuration_row.cc b/lib/xilinx/xc7series/configuration_row.cc
new file mode 100644
index 0000000..39ef9f4
--- /dev/null
+++ b/lib/xilinx/xc7series/configuration_row.cc
@@ -0,0 +1,62 @@
+#include <prjxray/xilinx/xc7series/configuration_row.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+bool Row::IsValidFrameAddress(FrameAddress address) const {
+	auto addr_bus = configuration_buses_.find(address.block_type());
+	if (addr_bus == configuration_buses_.end())
+		return false;
+	return addr_bus->second.IsValidFrameAddress(address);
+}
+
+absl::optional<FrameAddress> Row::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Find the bus for the current address.
+	auto addr_bus = configuration_buses_.find(address.block_type());
+
+	// If the current address isn't in a known bus, no way to know the next.
+	if (addr_bus == configuration_buses_.end())
+		return {};
+
+	// Ask the bus for the next address.
+	absl::optional<FrameAddress> next_address =
+	    addr_bus->second.GetNextFrameAddress(address);
+	if (next_address)
+		return next_address;
+
+	// The current bus doesn't know what the next address is. Rows come next
+	// in frame address numerical order so punt back to the caller to figure
+	// it out.
+	return {};
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+namespace YAML {
+
+Node convert<xc7series::Row>::encode(const xc7series::Row& rhs) {
+	Node node;
+	node.SetTag("xilinx/xc7series/row");
+	node["configuration_buses"] = rhs.configuration_buses_;
+	return node;
+}
+
+bool convert<xc7series::Row>::decode(const Node& node, xc7series::Row& lhs) {
+	if (!node.Tag().empty() && node.Tag() != "xilinx/xc7series/row") {
+		return false;
+	}
+
+	lhs.configuration_buses_ =
+	    node["configuration_buses"]
+	        .as<std::map<xc7series::BlockType,
+	                     xc7series::ConfigurationBus>>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/xc7series/ecc.cc b/lib/xilinx/xc7series/ecc.cc
new file mode 100644
index 0000000..a3a5cb2
--- /dev/null
+++ b/lib/xilinx/xc7series/ecc.cc
@@ -0,0 +1,60 @@
+#include <prjxray/xilinx/xc7series/ecc.h>
+#include <cassert>
+#include <cstdio>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+constexpr size_t kECCFrameNumber = 0x32;
+
+uint32_t icap_ecc(uint32_t idx, uint32_t data, uint32_t ecc) {
+	uint32_t val = idx * 32;  // bit offset
+
+	if (idx > 0x25)  // avoid 0x800
+		val += 0x1360;
+	else if (idx > 0x6)  // avoid 0x400
+		val += 0x1340;
+	else  // avoid lower
+		val += 0x1320;
+
+	if (idx == 0x32)  // mask ECC
+		data &= 0xFFFFE000;
+
+	for (int i = 0; i < 32; i++) {
+		if (data & 1)
+			ecc ^= val + i;
+
+		data >>= 1;
+	}
+
+	if (idx == 0x64) {  // last index
+		uint32_t v = ecc & 0xFFF;
+		v ^= v >> 8;
+		v ^= v >> 4;
+		v ^= v >> 2;
+		v ^= v >> 1;
+		ecc ^= (v & 1) << 12;  // parity
+	}
+
+	return ecc;
+}
+
+static uint32_t calculateECC(const std::vector<uint32_t>& data) {
+	uint32_t ecc = 0;
+	for (size_t ii = 0; ii < data.size(); ++ii) {
+		ecc = xc7series::icap_ecc(ii, data[ii], ecc);
+	}
+	return ecc;
+}
+
+void updateECC(std::vector<uint32_t>& data) {
+	assert(data.size() >= kECCFrameNumber);
+	// Replace the old ECC with the new.
+	data[kECCFrameNumber] &= 0xFFFFE000;
+	data[kECCFrameNumber] |= (calculateECC(data) & 0x1FFF);
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
diff --git a/lib/xilinx/xc7series/frame_address.cc b/lib/xilinx/xc7series/frame_address.cc
new file mode 100644
index 0000000..a4ae258
--- /dev/null
+++ b/lib/xilinx/xc7series/frame_address.cc
@@ -0,0 +1,99 @@
+#include <prjxray/xilinx/xc7series/frame_address.h>
+
+#include <iomanip>
+
+#include <prjxray/bit_ops.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+FrameAddress::FrameAddress(BlockType block_type,
+                           bool is_bottom_half_rows,
+                           uint8_t row,
+                           uint16_t column,
+                           uint8_t minor) {
+	address_ = bit_field_set(0, 25, 23, block_type);
+	address_ = bit_field_set(address_, 22, 22, is_bottom_half_rows);
+	address_ = bit_field_set(address_, 21, 17, row);
+	address_ = bit_field_set(address_, 16, 7, column);
+	address_ = bit_field_set(address_, 6, 0, minor);
+}
+
+BlockType FrameAddress::block_type() const {
+	return static_cast<BlockType>(bit_field_get(address_, 25, 23));
+}
+
+bool FrameAddress::is_bottom_half_rows() const {
+	return bit_field_get(address_, 22, 22);
+}
+
+uint8_t FrameAddress::row() const {
+	return bit_field_get(address_, 21, 17);
+}
+
+uint16_t FrameAddress::column() const {
+	return bit_field_get(address_, 16, 7);
+}
+
+uint8_t FrameAddress::minor() const {
+	return bit_field_get(address_, 6, 0);
+}
+
+std::ostream& operator<<(std::ostream& o, const FrameAddress& addr) {
+	o << "[" << std::hex << std::showbase << std::setw(10)
+	  << static_cast<uint32_t>(addr) << "] "
+	  << (addr.is_bottom_half_rows() ? "BOTTOM" : "TOP")
+	  << " Row=" << std::setw(2) << std::dec
+	  << static_cast<unsigned int>(addr.row()) << " Column=" << std::setw(2)
+	  << std::dec << addr.column() << " Minor=" << std::setw(2) << std::dec
+	  << static_cast<unsigned int>(addr.minor())
+	  << " Type=" << addr.block_type();
+	return o;
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace YAML {
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+Node convert<xc7series::FrameAddress>::encode(
+    const xc7series::FrameAddress& rhs) {
+	Node node;
+	node.SetTag("xilinx/xc7series/frame_address");
+	node["block_type"] = rhs.block_type();
+	node["row_half"] = (rhs.is_bottom_half_rows() ? "bottom" : "top");
+	node["row"] = static_cast<unsigned int>(rhs.row());
+	node["column"] = static_cast<unsigned int>(rhs.column());
+	node["minor"] = static_cast<unsigned int>(rhs.minor());
+	return node;
+}
+
+bool convert<xc7series::FrameAddress>::decode(const Node& node,
+                                              xc7series::FrameAddress& lhs) {
+	if (!(node.Tag() == "xilinx/xc7series/frame_address" ||
+	      node.Tag() == "xilinx/xc7series/configuration_frame_address") ||
+	    !node["block_type"] || !node["row_half"] || !node["row"] ||
+	    !node["column"] || !node["minor"])
+		return false;
+
+	bool row_half;
+	if (node["row_half"].as<std::string>() == "top") {
+		row_half = false;
+	} else if (node["row_half"].as<std::string>() == "bottom") {
+		row_half = true;
+	} else {
+		return false;
+	}
+
+	lhs = prjxray::xilinx::xc7series::FrameAddress(
+	    node["block_type"].as<xc7series::BlockType>(), row_half,
+	    node["row"].as<unsigned int>(), node["column"].as<unsigned int>(),
+	    node["minor"].as<unsigned int>());
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/xc7series/global_clock_region.cc b/lib/xilinx/xc7series/global_clock_region.cc
new file mode 100644
index 0000000..638cb46
--- /dev/null
+++ b/lib/xilinx/xc7series/global_clock_region.cc
@@ -0,0 +1,71 @@
+#include <prjxray/xilinx/xc7series/global_clock_region.h>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+bool GlobalClockRegion::IsValidFrameAddress(FrameAddress address) const {
+	auto addr_row = rows_.find(address.row());
+	if (addr_row == rows_.end())
+		return false;
+	return addr_row->second.IsValidFrameAddress(address);
+}
+
+absl::optional<FrameAddress> GlobalClockRegion::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Find the row for the current address.
+	auto addr_row = rows_.find(address.row());
+
+	// If the current address isn't in a known row, no way to know the next.
+	if (addr_row == rows_.end())
+		return {};
+
+	// Ask the row for the next address.
+	absl::optional<FrameAddress> next_address =
+	    addr_row->second.GetNextFrameAddress(address);
+	if (next_address)
+		return next_address;
+
+	// The current row doesn't know what the next address is.  Assume that
+	// the next valid address is the beginning of the next row.
+	if (++addr_row != rows_.end()) {
+		auto next_address = FrameAddress(address.block_type(),
+		                                 address.is_bottom_half_rows(),
+		                                 addr_row->first, 0, 0);
+		if (addr_row->second.IsValidFrameAddress(next_address))
+			return next_address;
+	}
+
+	// Must be in a different global clock region.
+	return {};
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+namespace YAML {
+
+Node convert<xc7series::GlobalClockRegion>::encode(
+    const xc7series::GlobalClockRegion& rhs) {
+	Node node;
+	node.SetTag("xilinx/xc7series/global_clock_region");
+	node["rows"] = rhs.rows_;
+	return node;
+}
+
+bool convert<xc7series::GlobalClockRegion>::decode(
+    const Node& node,
+    xc7series::GlobalClockRegion& lhs) {
+	if (!node.Tag().empty() &&
+	    node.Tag() != "xilinx/xc7series/global_clock_region") {
+		return false;
+	}
+
+	lhs.rows_ = node["rows"].as<std::map<unsigned int, xc7series::Row>>();
+	return true;
+}
+
+}  // namespace YAML
diff --git a/lib/xilinx/xc7series/part.cc b/lib/xilinx/xc7series/part.cc
new file mode 100644
index 0000000..6bfd139
--- /dev/null
+++ b/lib/xilinx/xc7series/part.cc
@@ -0,0 +1,115 @@
+#include <prjxray/xilinx/xc7series/part.h>
+
+#include <iomanip>
+#include <sstream>
+
+namespace prjxray {
+namespace xilinx {
+namespace xc7series {
+
+absl::optional<Part> Part::FromFile(const std::string& path) {
+	try {
+		YAML::Node yaml = YAML::LoadFile(path);
+		return yaml.as<Part>();
+	} catch (YAML::Exception& e) {
+		return {};
+	}
+}
+
+bool Part::IsValidFrameAddress(FrameAddress address) const {
+	if (address.is_bottom_half_rows()) {
+		return bottom_region_.IsValidFrameAddress(address);
+	} else {
+		return top_region_.IsValidFrameAddress(address);
+	}
+}
+
+absl::optional<FrameAddress> Part::GetNextFrameAddress(
+    FrameAddress address) const {
+	// Ask the current global clock region first.
+	absl::optional<FrameAddress> next_address =
+	    (address.is_bottom_half_rows()
+	         ? bottom_region_.GetNextFrameAddress(address)
+	         : top_region_.GetNextFrameAddress(address));
+	if (next_address)
+		return next_address;
+
+	// If the current address is in the top region, the bottom region is
+	// next numerically.
+	if (!address.is_bottom_half_rows()) {
+		next_address =
+		    FrameAddress(address.block_type(), true, 0, 0, 0);
+		if (bottom_region_.IsValidFrameAddress(*next_address))
+			return next_address;
+	}
+
+	// Block types are next numerically.
+	if (address.block_type() < BlockType::BLOCK_RAM) {
+		next_address =
+		    FrameAddress(BlockType::BLOCK_RAM, false, 0, 0, 0);
+		if (IsValidFrameAddress(*next_address))
+			return next_address;
+	}
+
+	if (address.block_type() < BlockType::CFG_CLB) {
+		next_address = FrameAddress(BlockType::CFG_CLB, false, 0, 0, 0);
+		if (IsValidFrameAddress(*next_address))
+			return next_address;
+	}
+
+	return {};
+}
+
+}  // namespace xc7series
+}  // namespace xilinx
+}  // namespace prjxray
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+namespace YAML {
+
+Node convert<xc7series::Part>::encode(const xc7series::Part& rhs) {
+	Node node;
+	node.SetTag("xilinx/xc7series/part");
+
+	std::ostringstream idcode_str;
+	idcode_str << "0x" << std::hex << rhs.idcode_;
+	node["idcode"] = idcode_str.str();
+	node["global_clock_regions"]["top"] = rhs.top_region_;
+	node["global_clock_regions"]["bottom"] = rhs.bottom_region_;
+	return node;
+}
+
+bool convert<xc7series::Part>::decode(const Node& node, xc7series::Part& lhs) {
+	if (!node.Tag().empty() && node.Tag() != "xilinx/xc7series/part")
+		return false;
+
+	if (!node["global_clock_regions"] && !node["configuration_ranges"]) {
+		return false;
+	}
+
+	lhs.idcode_ = node["idcode"].as<uint32_t>();
+
+	if (node["global_clock_regions"]) {
+		lhs.top_region_ = node["global_clock_regions"]["top"]
+		                      .as<xc7series::GlobalClockRegion>();
+		lhs.bottom_region_ = node["global_clock_regions"]["bottom"]
+		                         .as<xc7series::GlobalClockRegion>();
+	} else if (node["configuration_ranges"]) {
+		std::vector<xc7series::FrameAddress> addresses;
+		for (auto range : node["configuration_ranges"]) {
+			auto begin =
+			    range["begin"].as<xc7series::FrameAddress>();
+			auto end = range["end"].as<xc7series::FrameAddress>();
+			for (uint32_t cur = begin; cur < end; ++cur) {
+				addresses.push_back(cur);
+			}
+		}
+
+		lhs = xc7series::Part(lhs.idcode_, addresses);
+	}
+
+	return true;
+};
+
+}  // namespace YAML
diff --git a/third_party/abseil-cpp b/third_party/abseil-cpp
new file mode 160000
index 0000000..a2e6ade
--- /dev/null
+++ b/third_party/abseil-cpp
@@ -0,0 +1 @@
+Subproject commit a2e6adecc294dc4cd98cc285a9134ce58e0f2ad0
diff --git a/third_party/cctz b/third_party/cctz
new file mode 160000
index 0000000..a26bc5f
--- /dev/null
+++ b/third_party/cctz
@@ -0,0 +1 @@
+Subproject commit a26bc5f285a736a05cb974e063bfa26b33f07c78
diff --git a/third_party/gflags b/third_party/gflags
new file mode 160000
index 0000000..2e227c3
--- /dev/null
+++ b/third_party/gflags
@@ -0,0 +1 @@
+Subproject commit 2e227c3daae2ea8899f49858a23f3d318ea39b57
diff --git a/third_party/googletest b/third_party/googletest
new file mode 160000
index 0000000..8b4817e
--- /dev/null
+++ b/third_party/googletest
@@ -0,0 +1 @@
+Subproject commit 8b4817e3df3746a20502a84580f661ac448821be
diff --git a/third_party/python-sdf-timing b/third_party/python-sdf-timing
new file mode 160000
index 0000000..f6e72dd
--- /dev/null
+++ b/third_party/python-sdf-timing
@@ -0,0 +1 @@
+Subproject commit f6e72dd645010f7c38c57176e635caac699f427b
diff --git a/third_party/sanitizers-cmake b/third_party/sanitizers-cmake
new file mode 160000
index 0000000..99e159e
--- /dev/null
+++ b/third_party/sanitizers-cmake
@@ -0,0 +1 @@
+Subproject commit 99e159ec9bc8dd362b08d18436bd40ff0648417b
diff --git a/third_party/yaml-cpp b/third_party/yaml-cpp
new file mode 160000
index 0000000..587b24e
--- /dev/null
+++ b/third_party/yaml-cpp
@@ -0,0 +1 @@
+Subproject commit 587b24e2eedea1afa21d79419008ca5f7bda3bf4
diff --git a/third_party/yosys b/third_party/yosys
new file mode 160000
index 0000000..2bda51a
--- /dev/null
+++ b/third_party/yosys
@@ -0,0 +1 @@
+Subproject commit 2bda51ac34d6f542d1d6477eecede1d6527c10b3
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000..2ebadf1
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,35 @@
+add_executable(bitread bitread.cc)
+target_link_libraries(bitread absl::optional absl::strings gflags libprjxray)
+
+add_executable(segmatch segmatch.cc)
+target_link_libraries(segmatch gflags absl::strings)
+
+add_executable(bittool bittool.cc)
+target_link_libraries(bittool libprjxray gflags absl::strings absl::span)
+
+add_executable(frame_address_decoder frame_address_decoder.cc)
+target_link_libraries(frame_address_decoder libprjxray)
+
+add_executable(gen_part_base_yaml gen_part_base_yaml.cc)
+target_link_libraries(gen_part_base_yaml
+	absl::optional
+	absl::span
+	libprjxray
+	yaml-cpp
+	gflags
+)
+add_executable(xc7patch xc7patch.cc)
+target_link_libraries(xc7patch
+	absl::strings
+	absl::time
+	gflags
+	libprjxray
+)
+
+add_executable(xc7frames2bit xc7frames2bit.cc)
+target_link_libraries(xc7frames2bit
+	absl::strings
+	absl::time
+	gflags
+	libprjxray
+)
diff --git a/tools/bitread.cc b/tools/bitread.cc
new file mode 100644
index 0000000..9cc7d06
--- /dev/null
+++ b/tools/bitread.cc
@@ -0,0 +1,323 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <iostream>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <absl/strings/numbers.h>
+#include <absl/strings/str_cat.h>
+#include <absl/strings/str_split.h>
+#include <gflags/gflags.h>
+#include <prjxray/memory_mapped_file.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <prjxray/xilinx/configuration.h>
+
+DEFINE_bool(c, false, "output '*' for repeating patterns");
+DEFINE_bool(C, false, "do not ignore the checksum in each frame");
+DEFINE_int32(f,
+             -1,
+             "only dump the specified frame (might be used more than once)");
+DEFINE_string(F,
+              "",
+              "<first_frame_address>:<last_frame_address> only dump frame in "
+              "the specified range");
+DEFINE_string(o, "", "write machine-readable output file with config frames");
+DEFINE_bool(p, false, "output a binary netpgm image");
+DEFINE_bool(x,
+            false,
+            "use format 'bit_%%08x_%%03d_%%02d_t%%d_h%%d_r%%d_c%%d_m%%d'\n"
+            "The fields have the following meaning:\n"
+            "  - complete 32 bit hex frame id\n"
+            "  - word index with that frame (decimal)\n"
+            "  - bit index with that word (decimal)\n"
+            "  - decoded frame type from frame id\n"
+            "  - decoded top/botttom from frame id (top=0)\n"
+            "  - decoded row address from frame id\n"
+            "  - decoded column address from frame id\n"
+            "  - decoded minor address from frame id\n");
+DEFINE_bool(y, false, "use format 'bit_%%08x_%%03d_%%02d'");
+DEFINE_bool(z, false, "skip zero frames (frames with all bits cleared) in o");
+DEFINE_string(part_file, "", "YAML file describing a Xilinx part");
+DEFINE_string(architecture,
+              "Series7",
+              "Architecture of the provided bitstream");
+
+namespace xilinx = prjxray::xilinx;
+
+std::set<uint32_t> frames;
+uint32_t frame_range_begin = 0, frame_range_end = 0;
+
+std::vector<uint32_t> zero_frame(101);
+
+struct BitReader {
+	BitReader(const std::vector<uint8_t>& bytes) : bytes_(bytes) {}
+
+	const std::vector<uint8_t>& bytes_;
+
+	template <typename T>
+	int operator()(T& arg) {
+		using ArchType = std::decay_t<decltype(arg)>;
+		auto reader =
+		    xilinx::BitstreamReader<ArchType>::InitWithBytes(bytes_);
+		if (!reader) {
+			std::cerr << "Input doesn't look like a bitstream"
+			          << std::endl;
+			return 1;
+		}
+
+		std::cout << "Config size: " << reader->words().size()
+		          << " words" << std::endl;
+
+		auto part = ArchType::Part::FromFile(FLAGS_part_file);
+		if (!part) {
+			std::cerr << "Part file not found or invalid"
+			          << std::endl;
+			return 1;
+		}
+		auto config = xilinx::Configuration<ArchType>::InitWithPackets(
+		    *part, *reader);
+		if (!config) {
+			std::cerr
+			    << "Bitstream does not appear to be for this part"
+			    << std::endl;
+			return 1;
+		}
+
+		std::cout << "Number of configuration frames: "
+		          << config->frames().size() << std::endl;
+
+		FILE* f = stdout;
+
+		if (!FLAGS_o.empty()) {
+			f = fopen(FLAGS_o.c_str(), "w");
+
+			if (f == nullptr) {
+				printf(
+				    "Can't open output file '%s' for "
+				    "writing!\n",
+				    FLAGS_o.c_str());
+				return 1;
+			}
+		} else {
+			fprintf(f, "\n");
+		}
+
+		std::vector<std::vector<bool>> pgmdata;
+		std::vector<int> pgmsep;
+
+		int word_length = sizeof(typename ArchType::WordType) * 8;
+		for (auto& it : config->frames()) {
+			if (FLAGS_z && it.second == zero_frame)
+				continue;
+
+			if (!frames.empty() && !frames.count(it.first))
+				continue;
+
+			if (frame_range_begin != frame_range_end &&
+			    (it.first < frame_range_begin ||
+			     frame_range_end <= it.first))
+				continue;
+
+			if (FLAGS_o.empty())
+				printf(
+				    "Frame 0x%08x (Type=%d Top=%d Row=%d "
+				    "Column=%d "
+				    "Minor=%d):\n",
+				    static_cast<uint32_t>(it.first),
+				    static_cast<unsigned int>(
+				        it.first.block_type()),
+				    it.first.is_bottom_half_rows() ? 1 : 0,
+				    it.first.row(), it.first.column(),
+				    it.first.minor());
+
+			if (FLAGS_p) {
+				if (it.first.minor() == 0 && !pgmdata.empty())
+					pgmsep.push_back(pgmdata.size());
+
+				pgmdata.push_back(std::vector<bool>());
+
+				for (size_t i = 0; i < it.second.size(); i++)
+					for (int k = 0; k < word_length; k++)
+						pgmdata.back().push_back(
+						    (it.second.at(i) &
+						     (1 << k)) != 0);
+			} else if (FLAGS_x || FLAGS_y) {
+				for (int i = 0; i < (int)it.second.size();
+				     i++) {
+					for (int k = 0; k < word_length; k++) {
+						if (((i != 50 || k > 12 ||
+						      FLAGS_C) ||
+						     std::is_same<
+						         ArchType,
+						         prjxray::xilinx::
+						             Spartan6>::
+						         value) &&
+						    ((it.second.at(i) &
+						      (1 << k)) != 0)) {
+							if (FLAGS_x)
+								fprintf(
+								    f,
+								    "bit_%08x_%"
+								    "03d_%"
+								    "02d_t%d_h%"
+								    "d_r%d_c%"
+								    "d_m%d\n",
+								    static_cast<
+								        uint32_t>(
+								        it.first),
+								    i, k,
+								    static_cast<
+								        unsigned int>(
+								        it.first
+								            .block_type()),
+								    it.first.is_bottom_half_rows()
+								        ? 1
+								        : 0,
+								    it.first
+								        .row(),
+								    it.first
+								        .column(),
+								    it.first
+								        .minor());
+							else
+								fprintf(
+								    f,
+								    "bit_%08x_%"
+								    "03d_"
+								    "%02d\n",
+								    static_cast<
+								        uint32_t>(
+								        it.first),
+								    i, k);
+						}
+					}
+				}
+				if (FLAGS_o.empty())
+					fprintf(f, "\n");
+			} else {
+				if (!FLAGS_o.empty())
+					fprintf(
+					    f, ".frame 0x%08x\n",
+					    static_cast<uint32_t>(it.first));
+
+				for (size_t i = 0; i < it.second.size(); i++)
+					if (std::is_same<ArchType,
+					                 prjxray::xilinx::
+					                     Spartan6>::value) {
+						fprintf(
+						    f, "%08x%s",
+						    it.second.at(i) &
+						        0xffffffff,
+						    (i % 6) == 5 ? "\n" : " ");
+					} else {
+						fprintf(
+						    f, "%08x%s",
+						    it.second.at(i) &
+						        ((i != 50 || FLAGS_C)
+						             ? 0xffffffff
+						             : 0xffffe000),
+						    (i % 6) == 5 ? "\n" : " ");
+					}
+
+				fprintf(f, "\n\n");
+			}
+		}
+
+		if (FLAGS_p) {
+			int width = pgmdata.size() + pgmsep.size();
+			int height = ArchType::words_per_frame * word_length;
+			fprintf(f, "P5 %d %d 15\n", width, height);
+
+			for (int y = 0,
+			         bit = ArchType::words_per_frame * word_length -
+			               1;
+			     y < height; y++, bit--) {
+				for (int x = 0, frame = 0, sep = 0; x < width;
+				     x++, frame++) {
+					if (sep < int(pgmsep.size()) &&
+					    frame == pgmsep.at(sep)) {
+						fputc(8, f);
+						x++, sep++;
+					}
+
+					if (bit >=
+					    (int)pgmdata.at(frame).size()) {
+						fputc(0, f);
+						continue;
+					}
+
+					fputc(
+					    pgmdata.at(frame).at(bit) ? 15 : 0,
+					    f);
+				}
+
+				if (bit % word_length == 0 && y) {
+					for (int x = 0; x < width; x++)
+						fputc(8, f);
+					y++;
+				}
+			}
+		}
+
+		if (!FLAGS_o.empty())
+			fclose(f);
+
+		printf("DONE\n");
+		return 0;
+	}
+};
+
+int main(int argc, char** argv) {
+	gflags::SetUsageMessage(
+	    absl::StrCat("Usage: ", argv[0], " [options] [bitfile]"));
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+	if (FLAGS_f >= 0) {
+		frames.insert(FLAGS_f);
+	}
+
+	if (!FLAGS_F.empty()) {
+		std::pair<std::string, std::string> p =
+		    absl::StrSplit(FLAGS_F, ":");
+		frame_range_begin = strtol(p.first.c_str(), nullptr, 0);
+		frame_range_end = strtol(p.second.c_str(), nullptr, 0) + 1;
+	}
+
+	std::vector<uint8_t> in_bytes;
+	if (argc == 2) {
+		auto in_file_name = argv[1];
+		auto in_file =
+		    prjxray::MemoryMappedFile::InitWithFile(in_file_name);
+		if (!in_file) {
+			std::cerr << "Can't open input file '" << in_file_name
+			          << "' for reading!" << std::endl;
+			return 1;
+		}
+
+		std::cout << "Bitstream size: " << in_file->size() << " bytes"
+		          << std::endl;
+
+		in_bytes = std::vector<uint8_t>(
+		    static_cast<uint8_t*>(in_file->data()),
+		    static_cast<uint8_t*>(in_file->data()) + in_file->size());
+	} else {
+		while (1) {
+			int c = getchar();
+			if (c == EOF)
+				break;
+			in_bytes.push_back(c);
+		}
+
+		std::cout << "Bitstream size: " << in_bytes.size() << " bytes"
+		          << std::endl;
+	}
+
+	xilinx::Architecture::Container arch_container =
+	    xilinx::ArchitectureFactory::create_architecture(
+	        FLAGS_architecture);
+	return absl::visit(BitReader(in_bytes), arch_container);
+}
diff --git a/tools/bittool.cc b/tools/bittool.cc
new file mode 100644
index 0000000..66d8a48
--- /dev/null
+++ b/tools/bittool.cc
@@ -0,0 +1,258 @@
+#include <algorithm>
+#include <iostream>
+#include <typeinfo>
+
+#include <absl/strings/str_cat.h>
+#include <absl/types/span.h>
+#include <gflags/gflags.h>
+#include <prjxray/memory_mapped_file.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+
+namespace xilinx = prjxray::xilinx;
+
+struct Action {
+	std::string name;
+	std::function<int(int, char* [])> handler;
+};
+
+struct ConfigPacketsLister {
+	ConfigPacketsLister(const absl::Span<uint8_t>& bytes) : bytes_(bytes) {}
+
+	const absl::Span<uint8_t>& bytes_;
+
+	template <typename T>
+	int operator()(T& arg) {
+		using ArchType = std::decay_t<decltype(arg)>;
+		auto reader =
+		    xilinx::BitstreamReader<ArchType>::InitWithBytes(bytes_);
+		if (!reader) {
+			std::cerr << "Input doesn't look like a bitstream"
+			          << std::endl;
+			return 1;
+		}
+
+		for (auto packet : *reader) {
+			std::cout << packet;
+		}
+
+		return 0;
+	}
+};
+
+int ListConfigPackets(int argc, char* argv[]) {
+	std::string architecture("Series7");
+	if (argc < 1) {
+		std::cerr << "ERROR: no input specified" << std::endl;
+		std::cerr << "Usage: " << argv[0]
+		          << "list_config_packets <bit_file>" << std::endl;
+		return 1;
+	}
+	if (argc == 2) {
+		architecture = argv[1];
+	}
+
+	auto in_file_name = argv[0];
+	auto in_file = prjxray::MemoryMappedFile::InitWithFile(in_file_name);
+	if (!in_file) {
+		std::cerr << "Unable to open bit file: " << in_file_name
+		          << std::endl;
+		return 1;
+	}
+	auto in_bytes = absl::Span<uint8_t>(
+	    static_cast<uint8_t*>(in_file->data()), in_file->size());
+	xilinx::Architecture::Container arch_container =
+	    xilinx::ArchitectureFactory::create_architecture(architecture);
+	return absl::visit(ConfigPacketsLister(in_bytes), arch_container);
+}
+
+struct DebugFrameAddressesDumper {
+	DebugFrameAddressesDumper(const absl::Span<uint8_t>& bytes)
+	    : bytes_(bytes) {}
+
+	const absl::Span<uint8_t>& bytes_;
+
+	template <typename T>
+	int operator()(T& arg) {
+		using ArchType = std::decay_t<decltype(arg)>;
+		auto reader =
+		    xilinx::BitstreamReader<ArchType>::InitWithBytes(bytes_);
+		if (!reader) {
+			std::cerr << "Input doesn't look like a bitstream"
+			          << std::endl;
+			return 1;
+		}
+
+		bool found_one_lout = false;
+		for (auto packet : *reader) {
+			if ((packet.opcode() !=
+			     xilinx::ConfigurationPacket<
+			         typename ArchType::ConfRegType>::Opcode::
+			         Write) ||
+			    (packet.address() != ArchType::ConfRegType::LOUT)) {
+				continue;
+			}
+
+			if (packet.data().size() != 1) {
+				std::cerr << "Write to FAR with word_count != 1"
+				          << std::endl;
+				continue;
+			}
+
+			found_one_lout = true;
+			std::cout << std::dec << packet.data()[0] << std::endl;
+		}
+
+		if (!found_one_lout) {
+			std::cerr
+			    << "No LOUT writes found.  Was "
+			    << "BITSTREAM.GENERAL.DEBUGBITSTREAM set to YES?"
+			    << std::endl;
+			return 1;
+		}
+
+		return 0;
+	}
+};
+
+int DumpDebugbitstreamFrameAddresses(int argc, char* argv[]) {
+	std::string architecture("Series7");
+	if (argc < 1) {
+		std::cerr << "ERROR: no input specified" << std::endl;
+		std::cerr << "Usage: " << argv[0]
+		          << "dump_debugbitstream_frame_addresses <bit_file>"
+		          << std::endl;
+		return 1;
+	}
+	if (argc == 2) {
+		architecture = argv[1];
+	}
+
+	auto in_file_name = argv[0];
+	auto in_file = prjxray::MemoryMappedFile::InitWithFile(in_file_name);
+	if (!in_file) {
+		std::cerr << "Unable to open bit file: " << in_file_name
+		          << std::endl;
+		return 1;
+	}
+
+	auto in_bytes = absl::Span<uint8_t>(
+	    static_cast<uint8_t*>(in_file->data()), in_file->size());
+	xilinx::Architecture::Container arch_container =
+	    xilinx::ArchitectureFactory::create_architecture(architecture);
+	return absl::visit(DebugFrameAddressesDumper(in_bytes), arch_container);
+}
+
+struct DeviceIdGetter {
+	DeviceIdGetter(const absl::Span<uint8_t>& bytes) : bytes_(bytes) {}
+
+	const absl::Span<uint8_t>& bytes_;
+
+	template <typename T>
+	int operator()(T& arg) {
+		using ArchType = std::decay_t<decltype(arg)>;
+		auto reader =
+		    xilinx::BitstreamReader<ArchType>::InitWithBytes(bytes_);
+		if (!reader) {
+			std::cerr << "Input doesn't look like a bitstream"
+			          << std::endl;
+			return 1;
+		}
+		auto idcode_packet = std::find_if(
+		    reader->begin(), reader->end(),
+		    [](const xilinx::ConfigurationPacket<
+		        typename ArchType::ConfRegType>& packet) {
+			    return (packet.opcode() ==
+			            xilinx::ConfigurationPacket<
+			                typename ArchType::ConfRegType>::
+			                Opcode::Write) &&
+			           (packet.address() ==
+			            ArchType::ConfRegType::IDCODE);
+		    });
+		if (idcode_packet != reader->end()) {
+			if (std::is_same<ArchType, xilinx::Spartan6>::value) {
+				if (idcode_packet->data().size() != 2) {
+					std::cerr << "Write to IDCODE with "
+					             "word_count != 2"
+					          << std::endl;
+					return 1;
+				}
+				std::cout << "0x" << std::hex
+				          << ((idcode_packet->data()[0] << 16) |
+				              idcode_packet->data()[1])
+				          << std::endl;
+			} else {
+				if (idcode_packet->data().size() != 1) {
+					std::cerr << "Write to IDCODE with "
+					             "word_count != 1"
+					          << std::endl;
+					return 1;
+				}
+				std::cout << "0x" << std::hex
+				          << idcode_packet->data()[0]
+				          << std::endl;
+			}
+		}
+		return 0;
+	}
+};
+
+int GetDeviceId(int argc, char* argv[]) {
+	std::string architecture("Series7");
+	if (argc < 1) {
+		std::cerr << "ERROR: no input specified" << std::endl;
+		std::cerr << "Usage: " << argv[0]
+		          << "get_device_id <bit_file> [<architecture>]"
+		          << std::endl;
+		return 1;
+	}
+	if (argc == 2) {
+		architecture = argv[1];
+	}
+
+	auto in_file_name = argv[0];
+	auto in_file = prjxray::MemoryMappedFile::InitWithFile(in_file_name);
+	if (!in_file) {
+		std::cerr << "Unable to open bit file: " << in_file_name
+		          << std::endl;
+		return 1;
+	}
+	auto in_bytes = absl::Span<uint8_t>(
+	    static_cast<uint8_t*>(in_file->data()), in_file->size());
+
+	xilinx::Architecture::Container arch_container =
+	    xilinx::ArchitectureFactory::create_architecture(architecture);
+	return absl::visit(DeviceIdGetter(in_bytes), arch_container);
+}
+
+int main(int argc, char* argv[]) {
+	Action actions[] = {
+	    {"list_config_packets", ListConfigPackets},
+	    {"dump_debugbitstream_frame_addresses",
+	     DumpDebugbitstreamFrameAddresses},
+	    {"get_device_id", GetDeviceId},
+	};
+
+	gflags::SetUsageMessage(
+	    absl::StrCat("Usage: ", argv[0], " [options] [bitfile]"));
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+	if (argc < 2) {
+		std::cerr << "ERROR: no command specified" << std::endl;
+		std::cerr << "Usage: " << argv[0]
+		          << "<command> <command_options>" << std::endl;
+		return 1;
+	}
+
+	auto requested_action_str = argv[1];
+	auto requested_action = std::find_if(
+	    std::begin(actions), std::end(actions),
+	    [&](const Action& t) { return t.name == requested_action_str; });
+	if (requested_action == std::end(actions)) {
+		std::cerr << "Unknown action: " << requested_action_str
+		          << std::endl;
+		return 1;
+	}
+
+	return requested_action->handler(argc - 2, argv + 2);
+}
diff --git a/tools/frame_address_decoder.cc b/tools/frame_address_decoder.cc
new file mode 100644
index 0000000..b3c21e9
--- /dev/null
+++ b/tools/frame_address_decoder.cc
@@ -0,0 +1,40 @@
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+
+#include <prjxray/xilinx/xc7series/frame_address.h>
+
+namespace xc7series = prjxray::xilinx::xc7series;
+
+void frame_address_decode(std::istream* input_stream) {
+	for (uint32_t frame_address_raw;
+	     (*input_stream) >> std::setbase(0) >> frame_address_raw;) {
+		xc7series::FrameAddress frame_address(frame_address_raw);
+		std::cout << "[" << std::hex << std::showbase << std::setw(10)
+		          << frame_address_raw << "] "
+		          << (frame_address.is_bottom_half_rows() ? "BOTTOM"
+		                                                  : "TOP")
+		          << " Row=" << std::setw(2) << std::dec
+		          << static_cast<unsigned int>(frame_address.row())
+		          << " Column=" << std::setw(2) << std::dec
+		          << frame_address.column() << " Minor=" << std::setw(2)
+		          << std::dec
+		          << static_cast<unsigned int>(frame_address.minor())
+		          << " Type=" << frame_address.block_type()
+		          << std::endl;
+	}
+}
+
+int main(int argc, char* argv[]) {
+	if (argc > 1) {
+		std::ifstream file_stream(argv[1]);
+		if (file_stream) {
+			frame_address_decode(&file_stream);
+			return 0;
+		}
+	}
+
+	frame_address_decode(&std::cin);
+
+	return 0;
+}
diff --git a/tools/gen_part_base_yaml.cc b/tools/gen_part_base_yaml.cc
new file mode 100644
index 0000000..e5f965f
--- /dev/null
+++ b/tools/gen_part_base_yaml.cc
@@ -0,0 +1,98 @@
+#include <libgen.h>
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <absl/strings/str_cat.h>
+#include <absl/types/optional.h>
+#include <absl/types/span.h>
+#include <gflags/gflags.h>
+#include <prjxray/memory_mapped_file.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <yaml-cpp/yaml.h>
+
+DEFINE_bool(f, false, "Use FAR registers instead of LOUT ones");
+
+namespace xilinx = prjxray::xilinx;
+
+int main(int argc, char* argv[]) {
+	gflags::SetUsageMessage(
+	    absl::StrCat("Usage: ", argv[0], " [bitfile] [options]"));
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+	if (argc < 2) {
+		std::cerr << "ERROR: no input specified" << std::endl;
+		std::cerr << "Usage: " << basename(argv[0]) << " <bit_file>"
+		          << std::endl;
+		return 1;
+	}
+
+	auto in_file_name = argv[1];
+	auto in_file = prjxray::MemoryMappedFile::InitWithFile(in_file_name);
+	if (!in_file) {
+		std::cerr << "Unable to open bit file: " << in_file_name
+		          << std::endl;
+		return 1;
+	}
+
+	auto reader = xilinx::BitstreamReader<xilinx::Series7>::InitWithBytes(
+	    in_file->as_bytes());
+	if (!reader) {
+		std::cerr << "Input doesn't look like a bitstream" << std::endl;
+		return 1;
+	}
+
+	bool found_fdri_write = false;
+	std::vector<xilinx::Series7::FrameAddress> frame_addresses;
+	absl::optional<uint32_t> idcode;
+	for (auto packet : *reader) {
+		if (packet.opcode() !=
+		    xilinx::ConfigurationPacket<
+		        xilinx::Series7::ConfRegType>::Opcode::Write) {
+			continue;
+		}
+
+		if (packet.address() == xilinx::Series7::ConfRegType::FDRI) {
+			found_fdri_write = true;
+		} else if ((packet.address() ==
+		            xilinx::Series7::ConfRegType::IDCODE) &&
+		           packet.data().size() == 1) {
+			idcode = packet.data()[0];
+		} else if (found_fdri_write &&
+		           (packet.address() ==
+		            xilinx::Series7::ConfRegType::LOUT) &&
+		           (packet.data().size() == 1) && FLAGS_f == false) {
+			frame_addresses.push_back(packet.data()[0]);
+			found_fdri_write = false;
+		} else if (found_fdri_write &&
+		           (packet.address() ==
+		            xilinx::Series7::ConfRegType::FAR) &&
+		           (packet.data().size() == 1) && FLAGS_f == true) {
+			frame_addresses.push_back(packet.data()[0]);
+			found_fdri_write = false;
+		}
+	}
+
+	if (!idcode) {
+		std::cerr << "No IDCODE found." << std::endl;
+		return 1;
+	}
+
+	if (frame_addresses.empty()) {
+		std::cerr << "No LOUT or FAR writes found.  Was "
+		          << "BITSTREAM.GENERAL.DEBUGBITSTREAM or "
+		          << "BITSTREAM.GENERAL.PERFRAMECRC set to YES?"
+		          << std::endl;
+		return 1;
+	}
+
+	auto part = xilinx::Series7::Part(*idcode, frame_addresses.begin(),
+	                                  frame_addresses.end());
+	std::cout << YAML::Node(part) << std::endl;
+
+	return 0;
+}
diff --git a/tools/segmatch.cc b/tools/segmatch.cc
new file mode 100644
index 0000000..128bca6
--- /dev/null
+++ b/tools/segmatch.cc
@@ -0,0 +1,387 @@
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <numeric>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <absl/strings/str_cat.h>
+#include <gflags/gflags.h>
+
+DEFINE_int32(
+    c,
+    4,
+    "threshold under which candidates are output. set to -1 to output all.");
+DEFINE_bool(i, false, "add inverted tags");
+DEFINE_int32(m, 0, "min number of set/cleared samples each");
+DEFINE_int32(M, 0, "min number of set/cleared samples total");
+DEFINE_string(o, "", "set output file");
+DEFINE_string(k, "", "set output mask file");
+
+using std::map;
+using std::string;
+using std::tuple;
+using std::vector;
+
+int num_bits = 0, num_tags = 0;
+map<string, int> bit_ids, tag_ids;
+vector<string> bit_ids_r, tag_ids_r;
+
+#if 0
+struct bool_vec
+{
+	vector<bool> data;
+
+	bool_vec(int n = 0, bool initval = false) : data(n, initval)
+	{
+	}
+
+	void set(int n)
+	{
+		if (int(data.size()) <= n)
+			data.resize(n+1);
+		data[n] = true;
+	}
+
+	bool get(int n)
+	{
+		return data.at(n);
+	}
+
+	void resize(int n)
+	{
+		data.resize(n);
+	}
+
+	int count()
+	{
+		return std::accumulate(data.begin(), data.end(), 0);
+	}
+
+	void apply_and(const bool_vec &other)
+	{
+		assert(data.size() == other.data.size());
+		for (int i = 0; i < int(data.size()); i++)
+			data[i] = data[i] && other.data[i];
+	}
+
+	void apply_andc(const bool_vec &other)
+	{
+		assert(data.size() == other.data.size());
+		for (int i = 0; i < int(data.size()); i++)
+			data[i] = data[i] && !other.data[i];
+	}
+};
+#else
+struct bool_vec {
+	vector<uint64_t> data;
+
+	bool_vec(int n = 0, bool initval = false)
+	    : data((n + 63) / 64, initval ? ~uint64_t(0) : uint64_t(0)) {
+		for (int i = data.size() * 64 - 1; i >= n; i--)
+			data[n / 64] &= ~(uint64_t(1) << (n % 64));
+	}
+
+	void set(int n) {
+		if (int(data.size() * 64) <= n)
+			data.resize((n + 64) / 64);
+		data[n / 64] |= uint64_t(1) << (n % 64);
+	}
+
+	bool get(int n) { return (data[n / 64] >> (n % 64)) & 1; }
+
+	void resize(int n) { data.resize((n + 63) / 64); }
+
+	int count() {
+		int sum = 0;
+		for (int i = 0; i < 64 * int(data.size()); i++)
+			if (get(i))
+				sum++;
+		return sum;
+	}
+
+	void apply_and(const bool_vec& other) {
+		assert(data.size() == other.data.size());
+		for (int i = 0; i < int(data.size()); i++)
+			data[i] &= other.data[i];
+	}
+
+	void apply_andc(const bool_vec& other) {
+		assert(data.size() == other.data.size());
+		for (int i = 0; i < int(data.size()); i++)
+			data[i] &= ~other.data[i];
+	}
+};
+#endif
+
+// segname -> bits, tags_on, tags_off
+typedef tuple<bool_vec, bool_vec, bool_vec> segdata_t;
+map<string, segdata_t> segdata;
+
+map<string, int> segnamecnt;
+
+static inline bool_vec& segdata_bits(segdata_t& sd) {
+	return std::get<0>(sd);
+}
+static inline bool_vec& segdata_tags1(segdata_t& sd) {
+	return std::get<1>(sd);
+}
+static inline bool_vec& segdata_tags0(segdata_t& sd) {
+	return std::get<2>(sd);
+}
+
+void read_input(std::istream& f, std::string filename) {
+	string token;
+	segdata_t* segptr = nullptr;
+
+	while (f >> token) {
+		if (token == "seg") {
+			f >> token;
+			token = filename + ":" + token;
+			while (segdata.count(token)) {
+				int idx = 1;
+				if (segnamecnt.count(token))
+					idx = segnamecnt.at(token);
+				segnamecnt[token] = idx + 1;
+				char buffer[64];
+				snprintf(buffer, 64, "-%d", idx);
+				token += buffer;
+			}
+			segptr = &segdata[token];
+			continue;
+		}
+
+		if (token == "bit") {
+			assert(segptr != nullptr);
+
+			f >> token;
+			if (bit_ids.count(token) == 0) {
+				bit_ids[token] = num_bits++;
+				bit_ids_r.push_back(token);
+			}
+
+			int bit_idx = bit_ids.at(token);
+			auto& bits = segdata_bits(*segptr);
+
+			bits.set(bit_idx);
+			continue;
+		}
+
+		if (token == "tag") {
+			assert(segptr != nullptr);
+
+			f >> token;
+			if (tag_ids.count(token) == 0) {
+				tag_ids[token] = num_tags++;
+				tag_ids_r.push_back(token);
+			}
+
+			int tag_idx = tag_ids.at(token);
+
+			f >> token;
+			assert(token == "0" || token == "1");
+
+			auto& tags = token == "1" ? segdata_tags1(*segptr)
+			                          : segdata_tags0(*segptr);
+
+			tags.set(tag_idx);
+
+			if (FLAGS_i) {
+				auto& inv_tags = token == "1"
+				                     ? segdata_tags0(*segptr)
+				                     : segdata_tags1(*segptr);
+
+				token = tag_ids_r.at(tag_idx) + "__INV";
+
+				if (tag_ids.count(token) == 0) {
+					tag_ids[token] = num_tags++;
+					tag_ids_r.push_back(token);
+				}
+
+				int inv_tag_idx = tag_ids.at(token);
+				inv_tags.set(inv_tag_idx);
+			}
+
+			continue;
+		}
+
+		abort();
+	}
+
+	// printf("Number of segments: %d\n", int(segdata.size()));
+	// printf("Number of bits: %d\n", num_bits);
+	// printf("Number of tags: %d\n", num_tags);
+
+	for (auto& segdat : segdata) {
+		segdata_bits(segdat.second).resize(num_bits);
+		segdata_tags1(segdat.second).resize(num_tags);
+		segdata_tags0(segdat.second).resize(num_tags);
+	}
+}
+
+int main(int argc, char** argv) {
+	gflags::SetUsageMessage(
+	    absl::StrCat("Usage: ", argv[0], " [options] file.."));
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+	if (argc > 1) {
+		for (int optind = 1; optind < argc; optind++) {
+			printf("Reading %s.\n", argv[optind]);
+			std::ifstream f;
+			f.open(argv[optind]);
+
+			// Check if input file exists.
+			if (!f.good()) {
+				printf("ERROR: Input file does not exist!\n");
+				return -1;
+			}
+
+			assert(!f.fail());
+			read_input(f, argv[optind]);
+		}
+	} else {
+		printf("Reading from stding.\n");
+		read_input(std::cin, "stdin");
+	}
+
+	printf("#of segments: %d\n", int(segdata.size()));
+	printf("#of bits: %d\n", num_bits);
+	printf("#of tags: %d\n", num_tags);
+
+	FILE* f = stdout;
+
+	if (!FLAGS_o.empty()) {
+		f = fopen(FLAGS_o.c_str(), "w");
+		assert(f != nullptr);
+	}
+
+	int cnt_const0 = 0;
+	int cnt_const1 = 0;
+	int cnt_candidates = 0;
+	int min_candidates = num_bits;
+	int max_candidates = 0;
+	float avg_candidates = 0;
+
+	std::vector<std::string> out_lines;
+
+	for (int tag_idx = 0; tag_idx < num_tags; tag_idx++) {
+		bool_vec mask(num_bits, true);
+		int count1 = 0, count0 = 0;
+
+		for (auto& segdat : segdata) {
+			auto& sd = segdat.second;
+			bool tag1 = segdata_tags1(sd).get(tag_idx);
+			bool tag0 = segdata_tags0(sd).get(tag_idx);
+
+			assert(!tag1 || !tag0);
+
+			if (tag1) {
+				count1++;
+				mask.apply_and(segdata_bits(sd));
+				continue;
+			}
+
+			if (tag0) {
+				count0++;
+				mask.apply_andc(segdata_bits(sd));
+				continue;
+			}
+		}
+
+		assert(count1 || count0);
+
+		std::string out_line = tag_ids_r.at(tag_idx);
+
+		if (count1 < FLAGS_m) {
+			char buffer[64];
+			snprintf(buffer, 64, " <m1 %d>", count1);
+			out_line += buffer;
+		}
+
+		if (count0 < FLAGS_m) {
+			char buffer[64];
+			snprintf(buffer, 64, " <m0 %d>", count0);
+			out_line += buffer;
+		}
+
+		if (count1 + count0 < FLAGS_M) {
+			char buffer[64];
+			snprintf(buffer, 64, " <M %d %d>", count1, count0);
+			out_line += buffer;
+		}
+
+		if (!count1) {
+			out_lines.push_back(out_line + " <const0>");
+			cnt_const0 += 1;
+			continue;
+		}
+
+		if (!count0) {
+			out_line += " <const1>";
+			cnt_const1 += 1;
+		}
+
+		int num_candidates = mask.count();
+
+		if (count0) {
+			min_candidates =
+			    std::min(min_candidates, num_candidates);
+			max_candidates =
+			    std::max(max_candidates, num_candidates);
+			avg_candidates += num_candidates;
+			cnt_candidates += 1;
+		}
+
+		if (FLAGS_c < 0 ||
+		    (0 < num_candidates && num_candidates <= FLAGS_c)) {
+			std::vector<std::string> out_tags;
+			for (int bit_idx = 0; bit_idx < num_bits; bit_idx++)
+				if (mask.get(bit_idx))
+					out_tags.push_back(
+					    bit_ids_r.at(bit_idx));
+			std::sort(out_tags.begin(), out_tags.end());
+			for (auto& tag : out_tags)
+				out_line += " " + tag;
+		} else {
+			char buffer[64];
+			snprintf(buffer, 64, " <%d candidates>",
+			         num_candidates);
+			out_line += buffer;
+		}
+
+		out_lines.push_back(out_line);
+	}
+
+	std::sort(out_lines.begin(), out_lines.end());
+
+	for (auto& line : out_lines)
+		fprintf(f, "%s\n", line.c_str());
+
+	if (cnt_candidates)
+		avg_candidates /= cnt_candidates;
+
+	printf("#of const0 tags: %d\n", cnt_const0);
+	printf("#of const1 tags: %d\n", cnt_const1);
+
+	if (cnt_candidates) {
+		printf("min #of candidates: %d\n", min_candidates);
+		printf("max #of candidates: %d\n", max_candidates);
+		printf("avg #of candidates: %.3f\n", avg_candidates);
+	}
+
+	if (!FLAGS_k.empty()) {
+		f = fopen(FLAGS_k.c_str(), "w");
+		assert(f != nullptr);
+		std::sort(bit_ids_r.begin(), bit_ids_r.end());
+		for (auto bit : bit_ids_r)
+			fprintf(f, "bit %s\n", bit.c_str());
+	}
+
+	return 0;
+}
diff --git a/tools/xc7frames2bit.cc b/tools/xc7frames2bit.cc
new file mode 100644
index 0000000..28d952d
--- /dev/null
+++ b/tools/xc7frames2bit.cc
@@ -0,0 +1,83 @@
+#include <iostream>
+
+#include <gflags/gflags.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_writer.h>
+#include <prjxray/xilinx/configuration.h>
+
+DEFINE_string(part_name, "", "Name of the 7-series part");
+DEFINE_string(part_file, "", "Definition file for target 7-series part");
+DEFINE_string(
+    frm_file,
+    "",
+    "File containing a list of frame deltas to be applied to the base "
+    "bitstream.  Each line in the file is of the form: "
+    "<frame_address> <word1>,...,<word101>.");
+DEFINE_string(output_file, "", "Write bitstream to file");
+DEFINE_string(architecture,
+              "Series7",
+              "Architecture of the provided bitstream");
+
+namespace xilinx = prjxray::xilinx;
+
+struct Frames2BitWriter {
+	template <typename T>
+	int operator()(T& arg) {
+		using ArchType = std::decay_t<decltype(arg)>;
+		auto part = ArchType::Part::FromFile(FLAGS_part_file);
+		if (!part) {
+			std::cerr << "Part file " << FLAGS_part_file
+			          << " not found or invalid" << std::endl;
+			return 1;
+		}
+
+		// Read the frames from the input file
+		xilinx::Frames<ArchType> frames;
+		if (frames.readFrames(FLAGS_frm_file)) {
+			std::cerr << "Frames file " << FLAGS_frm_file
+			          << " not found or invalid" << std::endl;
+			return 1;
+		}
+
+		if (std::is_same<ArchType, xilinx::Series7>::value ||
+		    std::is_same<ArchType, xilinx::UltraScale>::value ||
+		    std::is_same<ArchType, xilinx::UltraScalePlus>::value) {
+			// In case the frames input file is missing some frames
+			// that are in the tilegrid
+			frames.addMissingFrames(part);
+		}
+
+		// Create data for the type 2 configuration packet with
+		// information about all frames
+		typename xilinx::Configuration<ArchType>::PacketData
+		    configuration_packet_data(
+		        xilinx::Configuration<ArchType>::
+		            createType2ConfigurationPacketData(
+		                frames.getFrames(), part));
+
+		// Put together a configuration package
+		typename ArchType::ConfigurationPackage configuration_package;
+		xilinx::Configuration<ArchType>::createConfigurationPackage(
+		    configuration_package, configuration_packet_data, part);
+
+		// Write bitstream
+		auto bitstream_writer =
+		    xilinx::BitstreamWriter<ArchType>(configuration_package);
+		if (bitstream_writer.writeBitstream(
+		        configuration_package, FLAGS_part_name, FLAGS_frm_file,
+		        "xc7frames2bit", FLAGS_output_file)) {
+			std::cerr << "Failed to write bitstream" << std::endl
+			          << "Exitting" << std::endl;
+		}
+		return 0;
+	}
+};
+
+int main(int argc, char* argv[]) {
+	gflags::SetUsageMessage(argv[0]);
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+	xilinx::Architecture::Container arch_container =
+	    xilinx::ArchitectureFactory::create_architecture(
+	        FLAGS_architecture);
+	return absl::visit(Frames2BitWriter(), arch_container);
+}
diff --git a/tools/xc7patch.cc b/tools/xc7patch.cc
new file mode 100644
index 0000000..369ddac
--- /dev/null
+++ b/tools/xc7patch.cc
@@ -0,0 +1,146 @@
+#include <iostream>
+
+#include <gflags/gflags.h>
+#include <prjxray/memory_mapped_file.h>
+#include <prjxray/xilinx/architectures.h>
+#include <prjxray/xilinx/bitstream_reader.h>
+#include <prjxray/xilinx/bitstream_writer.h>
+#include <prjxray/xilinx/configuration.h>
+
+DEFINE_string(part_name, "", "");
+DEFINE_string(part_file, "", "Definition file for target 7-series part");
+DEFINE_string(bitstream_file,
+              "",
+              "Initial bitstream to which the deltas are applied.");
+DEFINE_string(
+    frm_file,
+    "",
+    "File containing a list of frame deltas to be applied to the base "
+    "bitstream.  Each line in the file is of the form: "
+    "<frame_address> <word1>,...,<word101>.");
+DEFINE_string(output_file, "", "Write patched bitsteam to file");
+DEFINE_string(architecture,
+              "Series7",
+              "Architecture of the provided bitstream");
+
+namespace xilinx = prjxray::xilinx;
+
+template <typename ArchType>
+int patch_frames(
+    const std::string& frm_file_str,
+    std::map<typename ArchType::FrameAddress, std::vector<uint32_t>>* frames) {
+	xilinx::Frames<ArchType> frames_from_file;
+	if (frames_from_file.readFrames(frm_file_str)) {
+		std::cerr << "Failed to read frames" << std::endl;
+		return 1;
+	}
+
+	// Apply the deltas.
+	for (auto& frame : frames_from_file.getFrames()) {
+		auto iter = frames->find(frame.first);
+		if (iter == frames->end()) {
+			std::cerr << "frame address 0x" << std::hex
+			          << static_cast<uint32_t>(frame.first)
+			          << " because it was not found in frames."
+			          << std::endl;
+			return 1;
+		}
+
+		auto& frame_data = iter->second;
+		frame_data = frame.second;
+	}
+
+	return 0;
+}
+
+struct BitstreamPatcher {
+	template <typename T>
+	int operator()(T& arg) {
+		using ArchType = std::decay_t<decltype(arg)>;
+		auto part = ArchType::Part::FromFile(FLAGS_part_file);
+		if (!part) {
+			std::cerr << "Part file not found or invalid"
+			          << std::endl;
+			return 1;
+		}
+
+		auto bitstream_file = prjxray::MemoryMappedFile::InitWithFile(
+		    FLAGS_bitstream_file);
+		if (!bitstream_file) {
+			std::cerr << "Can't open base bitstream file: "
+			          << FLAGS_bitstream_file << std::endl;
+			return 1;
+		}
+
+		auto bitstream_reader =
+		    xilinx::BitstreamReader<ArchType>::InitWithBytes(
+		        bitstream_file->as_bytes());
+		if (!bitstream_reader) {
+			std::cout << "Bitstream does not appear to be a "
+			             "7-series bitstream!"
+			          << std::endl;
+			return 1;
+		}
+
+		auto bitstream_config =
+		    xilinx::Configuration<ArchType>::InitWithPackets(
+		        *part, *bitstream_reader);
+		if (!bitstream_config) {
+			std::cerr
+			    << "Bitstream does not appear to be for this part"
+			    << std::endl;
+			return 1;
+		}
+
+		// Copy the base frames to a mutable collection
+		std::map<typename ArchType::FrameAddress, std::vector<uint32_t>>
+		    frames;
+		for (auto& frame_val : bitstream_config->frames()) {
+			auto& cur_frame = frames[frame_val.first];
+
+			std::copy(frame_val.second.begin(),
+			          frame_val.second.end(),
+			          std::back_inserter(cur_frame));
+		}
+
+		if (!FLAGS_frm_file.empty()) {
+			int ret =
+			    patch_frames<ArchType>(FLAGS_frm_file, &frames);
+			if (ret != 0) {
+				return ret;
+			}
+		}
+
+		// Create data for the type 2 configuration packet with
+		// information about all frames
+		typename xilinx::Configuration<ArchType>::PacketData
+		    configuration_packet_data(
+		        xilinx::Configuration<ArchType>::
+		            createType2ConfigurationPacketData(frames, part));
+
+		// Put together a configuration package
+		typename ArchType::ConfigurationPackage configuration_package;
+		xilinx::Configuration<ArchType>::createConfigurationPackage(
+		    configuration_package, configuration_packet_data, part);
+
+		// Write bitstream.
+		auto bitstream_writer =
+		    xilinx::BitstreamWriter<ArchType>(configuration_package);
+		if (bitstream_writer.writeBitstream(
+		        configuration_package, FLAGS_part_name, FLAGS_frm_file,
+		        "xc7patch", FLAGS_output_file)) {
+			std::cerr << "Failed to write bitstream" << std::endl
+			          << "Exitting" << std::endl;
+		}
+		return 0;
+	}
+};
+
+int main(int argc, char* argv[]) {
+	gflags::SetUsageMessage(argv[0]);
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+	xilinx::Architecture::Container arch_container =
+	    xilinx::ArchitectureFactory::create_architecture(
+	        FLAGS_architecture);
+	return absl::visit(BitstreamPatcher(), arch_container);
+}