Merge pull request #1503 from antmicro/bits2rbt_and_aux

Add bits2rbt and aux data support to bitread
diff --git a/Makefile b/Makefile
index 293407a..b76f07a 100644
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,7 @@
 # ------------------------
 TEST_EXCLUDE = $(foreach x,$(ALL_EXCLUDE) docs fuzzers minitests experiments,--ignore $(x))
 
-test: test-py test-cpp
+test: test-py test-cpp test-tools
 	@true
 
 test-py:
@@ -61,7 +61,10 @@
 	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
+test-tools:
+	$(MAKE) -f Makefile.tools_tests
+
+.PHONY: test test-py test-cpp test-tools
 
 # Run HTML test
 # ------------------------
@@ -73,7 +76,7 @@
 
 # Auto formatting of code.
 # ------------------------
-FORMAT_EXCLUDE = $(foreach x,$(ALL_EXCLUDE),-and -not -path './$(x)/*') -and -not -name *.bit
+FORMAT_EXCLUDE = $(foreach x,$(ALL_EXCLUDE),-and -not -path './$(x)/*') -and -not -name *.bit -and -not -name *.tar.gz
 
 CLANG_FORMAT ?= clang-format-5.0
 format-cpp:
@@ -100,7 +103,7 @@
 # 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 ]]
+WS_FILTER = [ -f {} -a ! -L {} ] && [[ {} != *.bit ]] && [[ {} != *.tar.gz ]]
 
 # 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.
diff --git a/Makefile.tools_tests b/Makefile.tools_tests
new file mode 100644
index 0000000..6022623
--- /dev/null
+++ b/Makefile.tools_tests
@@ -0,0 +1,45 @@
+SHELL=/bin/bash
+BITREAD = build/tools/bitread
+BITS2RBT = build/tools/bits2rbt/bits2rbt
+TEST_DATA_PATH = lib/test_data
+
+#FIXME Uncomment bits2rbt_bram_xc7 once https://github.com/SymbiFlow/prjxray/issues/1285 is fixed
+BITS2RBT_TESTS = bits2rbt_xc7 #bits2rbt_bram_xc7
+
+TESTS = $(BITS2RBT_TESTS)
+
+bits2rbt_xc7_ARGS_1 = -part_file build/Series7/part.yaml -architecture Series7 -y -o build/xc7.bits -aux build/xc7.aux build/Series7/design.bit
+bits2rbt_xc7_ARGS_2 = -arch Series7 -aux build/xc7.aux -o build/xc7.rbt build/xc7.bits
+bits2rbt_xc7_TEST = diff <(tail -n +8 build/Series7/design.rbt) <(tail -n +8 build/xc7.rbt)
+
+bits2rbt_bram_xc7_ARGS_1 = -part_file build/Series7/part.yaml -architecture Series7 -y -o build/bram_xc7.bits -aux build/bram_xc7.aux build/Series7/bram.bit
+bits2rbt_bram_xc7_ARGS_2 = -arch Series7 -aux build/bram_xc7.aux -o build/bram_xc7.rbt build/bram_xc7.bits
+bits2rbt_bram_xc7_TEST = diff <(tail -n +8 build/Series7/bram.rbt) <(tail -n +8 build/bram_xc7.rbt)
+
+all: $(TESTS)
+
+test_data: $(TEST_DATA_PATH)/ToolsTestData.tar.gz
+	@echo "Unpacking test data"
+	tar -zxf $< -C build
+
+define bitread_test_tpl =
+$(1): $(2) $(3) test_data
+	@echo "Started test $(1)"
+	$(2) $$($(1)_ARGS_1) > /dev/null && $(3) $$($(1)_ARGS_2) > /dev/null && $$($(1)_TEST); \
+	RETVAL=$$$$? ; \
+	if [ $$$$RETVAL -eq 0 ]; then \
+		echo "$(1) PASS"; \
+		true; \
+	else \
+		echo "$(1) FAIL"; \
+		false; \
+	fi
+
+$(1)_clean:
+	rm -rf build/*.frm build/*.aux build/*.bits build/*.rbt
+
+endef
+$(foreach test,$(BITS2RBT_TESTS),$(eval $(call bitread_test_tpl,$(test),$(BITREAD),$(BITS2RBT))))
+
+clean: $(foreach test,$(TESTS),$(test)_clean)
+	@true
diff --git a/lib/include/prjxray/xilinx/bitstream_reader.h b/lib/include/prjxray/xilinx/bitstream_reader.h
index bae16bc..e13447b 100644
--- a/lib/include/prjxray/xilinx/bitstream_reader.h
+++ b/lib/include/prjxray/xilinx/bitstream_reader.h
@@ -68,6 +68,14 @@
 	static absl::optional<BitstreamReader<ArchType>> InitWithBytes(
 	    T bitstream);
 
+	// Extract information from bitstream necessary to reconstruct RBT
+	// header and add it to the AUX data
+	template <typename T>
+	static void PrintHeader(T bitstream, FILE* aux_fp);
+
+	// Extract configuration logic data and add to the AUX data
+	void PrintFpgaConfigurationLogicData(FILE* aux_fp);
+
 	const std::vector<uint32_t>& words() { return words_; };
 
 	// Returns an iterator that yields `ConfigurationPackets`
@@ -76,11 +84,59 @@
 	iterator end();
 
        private:
-	static std::array<uint8_t, 4> kSyncWord;
+	static const std::array<uint8_t, 4> kSyncWord;
+	static const std::array<uint32_t, 2> kWcfgCmd;
+	static const std::array<uint32_t, 2> kNullCmd;
 
 	std::vector<uint32_t> words_;
 };
 
+// Extract FPGA configuration logic information
+template <typename ArchType>
+void BitstreamReader<ArchType>::PrintFpgaConfigurationLogicData(
+    FILE* aux_fp) {
+	// Get the data before the first FDRI_WRITE command packet
+	const auto fpga_conf_end = std::search(
+	    words_.cbegin(), words_.cend(), kWcfgCmd.cbegin(), kWcfgCmd.cend());
+	fprintf(aux_fp, "FPGA configuration logic prefix:");
+	for (auto it = words_.cbegin(); it != fpga_conf_end; ++it) {
+		fprintf(aux_fp, " %08X", *it);
+	}
+	fprintf(aux_fp, "\n");
+
+	// Get the data after the last Null Command packet
+	const auto last_null_cmd = std::find_end(
+	    words_.cbegin(), words_.cend(), kNullCmd.cbegin(), kNullCmd.cend());
+	fprintf(aux_fp, "FPGA configuration logic suffix:");
+	for (auto it = last_null_cmd; it != words_.cend(); ++it) {
+		fprintf(aux_fp, " %08X", *it);
+	}
+	fprintf(aux_fp, "\n");
+}
+
+template <typename ArchType>
+template <typename T>
+void BitstreamReader<ArchType>::PrintHeader(T bitstream, FILE* aux_fp) {
+	// 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;
+	}
+	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 header_packets =
+	    bitstream_span.subspan(0, sync_pos - bitstream.begin());
+
+	fprintf(aux_fp, "Header bytes:");
+	for (auto& word : header_packets) {
+		fprintf(aux_fp, " %02X", word);
+	}
+	fprintf(aux_fp, "\n");
+}
+
 template <typename ArchType>
 template <typename T>
 absl::optional<BitstreamReader<ArchType>>
@@ -111,9 +167,19 @@
 
 // Sync word as specified in UG470 page 81
 template <typename ArchType>
-std::array<uint8_t, 4> BitstreamReader<ArchType>::kSyncWord{0xAA, 0x99, 0x55,
+const std::array<uint8_t, 4> BitstreamReader<ArchType>::kSyncWord{0xAA, 0x99, 0x55,
                                                             0x66};
 
+// Writing the WCFG(0x1) command in type 1 packet with 1 word to the CMD register (0x30008001)
+// Refer to UG470 page 110
+template <typename ArchType>
+const std::array<uint32_t, 2> BitstreamReader<ArchType>::kWcfgCmd = {0x30008001, 0x1};
+
+// Writing the NULL(0x0) command in type 1 packet with 1 word to the CMD register (0x30008001)
+// Refer to UG470 page 110
+template <typename ArchType>
+const std::array<uint32_t, 2> BitstreamReader<ArchType>::kNullCmd = {0x30008001, 0x0};
+
 template <typename ArchType>
 typename BitstreamReader<ArchType>::iterator
 BitstreamReader<ArchType>::begin() {
diff --git a/lib/include/prjxray/xilinx/configuration.h b/lib/include/prjxray/xilinx/configuration.h
index 89fad61..0a88354 100644
--- a/lib/include/prjxray/xilinx/configuration.h
+++ b/lib/include/prjxray/xilinx/configuration.h
@@ -68,6 +68,7 @@
 
 	const typename ArchType::Part& part() const { return part_; }
 	const FrameMap& frames() const { return frames_; }
+	void PrintFrameAddresses(FILE* fp);
 
        private:
 	typename ArchType::Part part_;
@@ -358,6 +359,19 @@
 	return Configuration(part, frames);
 }
 
+template <typename ArchType>
+void Configuration<ArchType>::PrintFrameAddresses(FILE* fp) {
+	fprintf(fp, "Frame addresses in bitstream: ");
+	for (auto frame = frames_.begin(); frame != frames_.end(); ++frame) {
+		fprintf(fp, "%08X", (int)frame->first);
+		if (std::next(frame) != frames_.end()) {
+			fprintf(fp, " ");
+		} else {
+			fprintf(fp, "\n");
+		}
+	}
+}
+
 }  // namespace xilinx
 }  // namespace prjxray
 
diff --git a/lib/test_data/ToolsTestData.tar.gz b/lib/test_data/ToolsTestData.tar.gz
new file mode 100644
index 0000000..36c5b60
--- /dev/null
+++ b/lib/test_data/ToolsTestData.tar.gz
Binary files differ
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 6a166eb..30650fd 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -34,4 +34,6 @@
 	libprjxray
 )
 
+add_subdirectory(bits2rbt)
+
 install(TARGETS xc7frames2bit xc7patch gen_part_base_yaml frame_address_decoder bittool segmatch bitread DESTINATION bin)
diff --git a/tools/bitread.cc b/tools/bitread.cc
index f5aa7e0..796b8c7 100644
--- a/tools/bitread.cc
+++ b/tools/bitread.cc
@@ -54,6 +54,10 @@
 DEFINE_string(architecture,
               "Series7",
               "Architecture of the provided bitstream");
+DEFINE_string(
+    aux,
+    "",
+    "write machine-readable output file with auxiliary bitstream data");
 
 namespace xilinx = prjxray::xilinx;
 
@@ -115,6 +119,25 @@
 			fprintf(f, "\n");
 		}
 
+		if (!FLAGS_aux.empty()) {
+			FILE* aux_file = fopen(FLAGS_aux.c_str(), "w");
+			if (aux_file == nullptr) {
+				printf(
+				    "Can't open aux output file '%s' for "
+				    "writing!\n",
+				    FLAGS_aux.c_str());
+				return 1;
+			}
+			// Extract and decode header information as in RBT file
+			xilinx::BitstreamReader<ArchType>::PrintHeader(
+			    bytes_, aux_file);
+			// Extract FPGA configuration logic information
+			reader->PrintFpgaConfigurationLogicData(aux_file);
+			// Extract configuration frames' addresses
+			config->PrintFrameAddresses(aux_file);
+			fclose(aux_file);
+		}
+
 		std::vector<std::vector<bool>> pgmdata;
 		std::vector<int> pgmsep;
 
diff --git a/tools/bits2rbt/CMakeLists.txt b/tools/bits2rbt/CMakeLists.txt
new file mode 100644
index 0000000..206fbbc
--- /dev/null
+++ b/tools/bits2rbt/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_executable(bits2rbt bits2rbt.cc
+	configuration_packets.cc
+	header.cc
+	ecc.cc)
+target_link_libraries(bits2rbt absl::strings gflags)
diff --git a/tools/bits2rbt/bits2rbt.cc b/tools/bits2rbt/bits2rbt.cc
new file mode 100644
index 0000000..e08fa23
--- /dev/null
+++ b/tools/bits2rbt/bits2rbt.cc
@@ -0,0 +1,44 @@
+#include <gflags/gflags.h>
+#include <fstream>
+#include <iostream>
+
+#include "configuration_packets.h"
+
+DEFINE_string(o, "", "Output RBT file");
+DEFINE_string(aux, "", "Input auxiliary data file");
+DEFINE_string(arch, "Series7", "FPGA architecture");
+
+int main(int argc, char** argv) {
+	gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+	if (argc != 2) {
+		std::cerr << "Error: Input bits file not specified"
+		          << std::endl;
+		return 1;
+	}
+
+	std::ofstream out_file;
+	if (!FLAGS_o.empty()) {
+		out_file.open(FLAGS_o.c_str(), std::ofstream::out);
+		if (!out_file.good()) {
+			std::cerr << "Error: Can't open output file\n";
+			return 1;
+		}
+	}
+	std::ostream& out = (FLAGS_o.empty()) ? std::cout : out_file;
+
+	try {
+		std::shared_ptr<ConfigurationPackets> packets =
+		    ConfigurationPackets::InitFromFile(argv[1], FLAGS_arch);
+		packets->AddAuxData(FLAGS_aux);
+		packets->WriteBits(out);
+	} catch (std::runtime_error& e) {
+		std::cerr << "Error: " << e.what();
+		return 1;
+	}
+
+	if (!FLAGS_o.empty()) {
+		out_file.close();
+	}
+	return 0;
+}
diff --git a/tools/bits2rbt/configuration_packets.cc b/tools/bits2rbt/configuration_packets.cc
new file mode 100644
index 0000000..6dfa871
--- /dev/null
+++ b/tools/bits2rbt/configuration_packets.cc
@@ -0,0 +1,241 @@
+#include <absl/strings/str_split.h>
+#include <algorithm>
+#include <bitset>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+
+#include "configuration_packets.h"
+#include "crc.h"
+#include "ecc.h"
+#include "header.h"
+
+ConfigurationPackets::ConfigurationPackets(const std::string& arch)
+    : words_per_frame_(words_in_architecture.at(arch)),
+      architecture_(arch),
+      ecc_(arch, words_per_frame_) {}
+
+const std::unordered_map<std::string, size_t>
+    ConfigurationPackets::words_in_architecture = {{"Series7", 101},
+                                                   {"UltraScale", 123},
+                                                   {"UltraScalePlus", 93}};
+
+std::shared_ptr<ConfigurationPackets> ConfigurationPackets::InitFromFile(
+    const std::string& file,
+    const std::string& arch) {
+	std::ifstream ifs(file, std::ifstream::in);
+	if (!ifs.good()) {
+		throw std::runtime_error("Couldn't open bits file\n");
+	}
+	if (words_in_architecture.find(arch) == words_in_architecture.end()) {
+		throw std::runtime_error("Error: Unrecognized architecture " +
+		                         arch + "\n");
+	}
+	std::shared_ptr<ConfigurationPackets> packets =
+	    std::make_shared<ConfigurationPackets>(arch);
+	packets->SetBits(ifs);
+	packets->UpdateECCs();
+	return packets;
+}
+
+void ConfigurationPackets::SetBits(std::ifstream& ifs) {
+	while (!ifs.eof()) {
+		std::string line;
+		getline(ifs, line);
+		SetBit(line);
+	}
+}
+
+void ConfigurationPackets::WriteBits(std::ostream& out) {
+	header_->Write(out);
+	WriteFpgaConfiguration(out, fpga_configuration_head_);
+	WriteConfiguration(out);
+	WriteFpgaConfiguration(out, fpga_configuration_tail_);
+}
+
+void ConfigurationPackets::WriteFpgaConfiguration(
+    std::ostream& out,
+    const std::vector<uint32_t>& vect) {
+	for (auto& word : vect) {
+		out << std::bitset<32>(word) << std::endl;
+	}
+}
+
+void ConfigurationPackets::WriteConfiguration(std::ostream& out) {
+	for (auto packet = configuration_data_packets_.cbegin();
+	     packet != configuration_data_packets_.cend(); ++packet) {
+		WritePacket(out, packet);
+		if (IsDifferentRow(packet, std::next(packet))) {
+			// Write the zero frame at the end of each row
+			WritePacket(out, packet);
+		}
+	}
+}
+
+void ConfigurationPackets::WritePacket(
+    std::ostream& out,
+    ConfigurationFrames::const_iterator frame_citr) const {
+	bool is_new_row(false);
+	if ((frame_citr == configuration_data_packets_.cbegin()) ||
+	    IsDifferentRow(frame_citr, std::prev(frame_citr))) {
+		// Write the WCFG command followed by a FAR write with address
+		// of the next frame WCFG command write
+		out << std::bitset<32>(kCmdWrite | 0x1) << std::endl;
+		out << std::bitset<32>(0x1) << std::endl;
+		out << std::bitset<32>(kNop) << std::endl;
+		// FAR Write of the next frame followed by frame address
+		out << std::bitset<32>(kFarWrite | 0x1) << std::endl;
+		out << std::bitset<32>(frame_citr->first) << std::endl;
+		if (architecture_ == "Series7") {
+			out << std::bitset<32>(kNop) << std::endl;
+		}
+		is_new_row = true;
+	}
+	uint32_t crc(GetCRC(frame_citr, is_new_row));
+	// FDRI Write
+	out << std::bitset<32>(kFdriWrite | words_per_frame_) << std::endl;
+	// Declared number of configuration words
+	for (auto& word : frame_citr->second) {
+		out << std::bitset<32>(word) << std::endl;
+	}
+	// FAR Write followed by frame address
+	out << std::bitset<32>(kFarWrite | 0x1) << std::endl;
+	out << std::bitset<32>(frame_citr->first) << std::endl;
+	// CRC Write followed by packet CRC
+	out << std::bitset<32>(kCrcWrite | 0x1) << std::endl;
+	out << std::bitset<32>(crc) << std::endl;
+}
+
+void ConfigurationPackets::SetBit(const std::string& line) {
+	if (line.empty()) {
+		return;
+	}
+	uint32_t frame_address, word, bit;
+	sscanf(line.c_str(), "bit_%08x_%03u_%02u", &frame_address, &word, &bit);
+	if (configuration_data_packets_.find(frame_address) ==
+	    configuration_data_packets_.end()) {
+		configuration_data_packets_[frame_address] =
+		    std::vector<uint32_t>(words_per_frame_, 0x0);
+	}
+	configuration_data_packets_[frame_address].at(word) |= (1 << bit);
+}
+
+void ConfigurationPackets::Line2Vector(const std::string& line,
+                                       std::vector<uint32_t>& vect) {
+	static std::function<uint32_t(const std::string& str)> str_to_uint =
+	    [](const std::string& str) -> uint32_t {
+		assert(!str.empty());
+		return std::stoul(str, nullptr, 16);
+	};
+	// Skip the line content description before the conversion
+	std::vector<std::string> str_vector =
+	    absl::StrSplit(line.substr(line.find(":") + 2), " ");
+	std::transform(str_vector.begin(), str_vector.end(),
+	               std::back_inserter(vect), str_to_uint);
+}
+
+void ConfigurationPackets::AddAuxData(const std::string& file) {
+	std::ifstream ifs(file, std::ifstream::in);
+	if (!ifs.good()) {
+		throw std::runtime_error("Couldn't open auxiliary data file\n");
+	}
+
+	std::string line;
+	getline(ifs, line);
+	InitializeHeader(line);
+	getline(ifs, line);
+	InitializeFpgaConfigurationHead(line);
+	getline(ifs, line);
+	InitializeFpgaConfigurationTail(line);
+	getline(ifs, line);
+	InitializeConfigurationData(line);
+}
+
+uint32_t ConfigurationPackets::GetFpgaConfigurationCRC() const {
+	uint32_t crc(0);
+	auto far = std::search(fpga_configuration_head_.begin(),
+	                       fpga_configuration_head_.end(), kRCrcCmd.begin(),
+	                       kRCrcCmd.end());
+	for (auto itr = far + kRCrcCmd.size();
+	     itr != fpga_configuration_head_.end(); ++itr) {
+		if (*itr == kNop)
+			continue;
+		// Check if it is a write packet
+		assert(*itr & 0x30000000);
+		// Get the packet address
+		uint32_t addr = (*itr >> 13) & 0x1F;
+		std::advance(itr, 1);
+		assert(itr != fpga_configuration_head_.end());
+		crc = icap_crc(addr, *itr, crc);
+	}
+	return crc;
+}
+
+uint32_t ConfigurationPackets::GetCRC(
+    ConfigurationFrames::const_iterator frame_citr,
+    bool is_new_row) const {
+	uint32_t crc = 0;
+	if (is_new_row) {
+		if (frame_citr == configuration_data_packets_.begin()) {
+			crc = GetFpgaConfigurationCRC();
+		}
+		crc = icap_crc(kCmdReg, kWcfgCmd, crc);
+		crc = icap_crc(kFarReg, frame_citr->first, crc);
+	}
+	for (const auto& word : frame_citr->second) {
+		crc = icap_crc(kFdriReg, word, crc);
+	}
+	crc = icap_crc(kFarReg, frame_citr->first, crc);
+	return crc;
+}
+
+void ConfigurationPackets::UpdateECCs() {
+	for (auto& packet : configuration_data_packets_) {
+		auto& data = packet.second;
+		ecc_.UpdateFrameECC(data);
+	}
+}
+
+void ConfigurationPackets::InitializeConfigurationData(
+    const std::string& line) {
+	std::function<void(const uint32_t&)> update_packets =
+	    [this](const uint32_t& addr) {
+		    if (configuration_data_packets_.find(addr) ==
+		        configuration_data_packets_.end()) {
+			    configuration_data_packets_[addr] =
+			        std::vector<uint32_t>(words_per_frame_, 0x0);
+		    }
+	    };
+	std::vector<uint32_t> frames_addr;
+	Line2Vector(line, frames_addr);
+	std::for_each(frames_addr.begin(), frames_addr.end(), update_packets);
+}
+
+void ConfigurationPackets::InitializeFpgaConfigurationHead(
+    const std::string& line) {
+	Line2Vector(line, fpga_configuration_head_);
+}
+
+void ConfigurationPackets::InitializeFpgaConfigurationTail(
+    const std::string& line) {
+	Line2Vector(line, fpga_configuration_tail_);
+}
+
+void ConfigurationPackets::InitializeHeader(const std::string& line) {
+	// FIXME Remove the configuration data head part once the aux data is
+	// fixed
+	header_ = std::make_unique<Header>(line, fpga_configuration_head_);
+}
+
+bool ConfigurationPackets::IsDifferentRow(
+    ConfigurationFrames::const_iterator frame_citr1,
+    ConfigurationFrames::const_iterator frame_citr2) const {
+	auto get_row = [this](const uint32_t& address) {
+		size_t row_shift =
+		    (architecture_ == "UltraScalePlus") ? 18 : 17;
+		return (address >> row_shift) & 0x3FF;
+	};
+	return (get_row(frame_citr1->first) != get_row(frame_citr2->first));
+}
diff --git a/tools/bits2rbt/configuration_packets.h b/tools/bits2rbt/configuration_packets.h
new file mode 100644
index 0000000..61f45f6
--- /dev/null
+++ b/tools/bits2rbt/configuration_packets.h
@@ -0,0 +1,65 @@
+#ifndef CONFIGURATION_PACKETS_H_
+#define CONFIGURATION_PACKETS_H_
+#include <array>
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+#include "ecc.h"
+#include "header.h"
+
+class ConfigurationPackets {
+       public:
+	ConfigurationPackets(const std::string& arch);
+	static const std::unordered_map<std::string, size_t>
+	    words_in_architecture;
+	static std::shared_ptr<ConfigurationPackets> InitFromFile(
+	    const std::string& file,
+	    const std::string& arch);
+	void AddAuxData(const std::string& file);
+	void WriteBits(std::ostream& out);
+	void SetBits(std::ifstream& ifs);
+
+       private:
+	using ConfigurationFrame = std::pair<uint32_t, std::vector<uint32_t>>;
+	using ConfigurationFrames = std::map<uint32_t, std::vector<uint32_t>>;
+	// Refer to UG470 page 109 for address of configuration registers and commands
+	const uint32_t kCmdWrite = 0x30008000;
+	const uint32_t kFdriWrite = 0x30004000;
+	const uint32_t kFarWrite = 0x30002000;
+	const uint32_t kCrcWrite = 0x30000000;
+	const uint32_t kNop = 0x20000000;
+	const uint32_t kFarReg = 0x1;
+	const uint32_t kFdriReg = 0x2;
+	const uint32_t kCmdReg = 0x4;
+	const uint32_t kWcfgCmd = 0x1;
+	// Writing the RCRC(0x7) command in type 1 packet with 1 word to the CMD register (0x30008001)
+	const std::array<uint32_t, 2> kRCrcCmd = {{0x30008001, 0x7}};
+	size_t words_per_frame_;
+	std::string architecture_;
+	ConfigurationFrames configuration_data_packets_;
+	std::vector<uint32_t> fpga_configuration_head_;
+	std::vector<uint32_t> fpga_configuration_tail_;
+	std::unique_ptr<Header> header_;
+	ECC ecc_;
+
+	void InitializeConfigurationData(const std::string& line);
+	void InitializeFpgaConfigurationHead(const std::string& line);
+	void InitializeFpgaConfigurationTail(const std::string& line);
+	void InitializeHeader(const std::string& line);
+	uint32_t GetCRC(ConfigurationFrames::const_iterator frame_citr,
+	                bool is_new_row = false) const;
+	uint32_t GetFpgaConfigurationCRC() const;
+	void UpdateECCs();
+	void SetBit(const std::string& line);
+	void WriteConfiguration(std::ostream& out);
+	void WriteFpgaConfiguration(std::ostream& out,
+	                            const std::vector<uint32_t>& vect);
+	void WritePacket(std::ostream& out,
+	                 ConfigurationFrames::const_iterator frame_citr) const;
+	bool IsDifferentRow(
+	    ConfigurationFrames::const_iterator frame_citr1,
+	    ConfigurationFrames::const_iterator frame_citr2) const;
+	void Line2Vector(const std::string& line, std::vector<uint32_t>& vect);
+};
+#endif  // CONFIGURATION_PACKETS_H_
diff --git a/tools/bits2rbt/crc.h b/tools/bits2rbt/crc.h
new file mode 100644
index 0000000..04af724
--- /dev/null
+++ b/tools/bits2rbt/crc.h
@@ -0,0 +1,18 @@
+uint32_t icap_crc(uint32_t addr, uint32_t data, uint32_t prev) {
+	int kAddressBitWidth = 5;
+	constexpr int kDataBitWidth = 32;
+	constexpr uint32_t kCrc32CastagnoliPolynomial = 0x82F63B78;
+
+	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;
+}
diff --git a/tools/bits2rbt/ecc.cc b/tools/bits2rbt/ecc.cc
new file mode 100644
index 0000000..7fe865d
--- /dev/null
+++ b/tools/bits2rbt/ecc.cc
@@ -0,0 +1,107 @@
+#include "ecc.h"
+#include <cassert>
+#include <iostream>
+
+const std::unordered_map<std::string, size_t> ECC::ecc_word_per_architecture = {
+    {"Series7", 50},
+    {"UltraScale", 60},
+    {"UltraScalePlus", 45}};
+
+uint32_t ECC::GetSeries7WordEcc(uint32_t idx,
+                                uint32_t data,
+                                uint32_t ecc) const {
+	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;
+}
+
+uint64_t ECC::GetUSEccFrameOffset(int word, int bit) const {
+	int nib = bit / 4;
+	int nibbit = bit % 4;
+	// ECC offset is expanded to 1 bit per nibble,
+	// and then shifted based on the bit index in nibble
+	// e.g. word 3, bit 9
+	// offset: 0b10100110010 - concatenate (3 + (255 - last_frame_index(e.g.
+	// 92 for US+)) [frame offset] and 9/4 [nibble offset] becomes:
+	// 0x10100110010 shifted by bit in nibble (9%4): 0x20200220020
+	uint32_t offset = (word + (255 - (frame_words_ - 1))) << 3 | nib;
+	uint64_t exp_offset = 0;
+	// Odd parity
+	offset ^= (1 << 11);
+	for (int i = 0; i < 11; i++)
+		if (offset & (1 << i))
+			offset ^= (1 << 11);
+	// Expansion
+	for (int i = 0; i < 12; i++)
+		if (offset & (1 << i))
+			exp_offset |= (1ULL << (4 * i));
+	return exp_offset << nibbit;
+};
+
+uint64_t ECC::GetUSWordEcc(uint32_t idx, uint32_t data, uint64_t ecc) const {
+	if (idx == ecc_word_) {
+		data = 0x0;
+	}
+	if (idx == ecc_word_ + 1) {
+		data &= 0xffff0000;
+	}
+	for (int i = 0; i < 32; i++) {
+		if (data & 1) {
+			ecc ^= GetUSEccFrameOffset(idx, i);
+		}
+		data >>= 1;
+	}
+	return ecc;
+}
+
+uint64_t ECC::CalculateECC(const std::vector<uint32_t>& data) const {
+	uint64_t ecc = 0;
+	for (size_t w = 0; w < data.size(); ++w) {
+		const uint32_t& word = data.at(w);
+		if (architecture_ == "Series7") {
+			ecc = GetSeries7WordEcc(w, word, ecc);
+		} else {
+			ecc = GetUSWordEcc(w, word, ecc);
+		}
+	}
+	return ecc;
+}
+
+void ECC::UpdateFrameECC(std::vector<uint32_t>& data) const {
+	assert(data.size() >= ecc_word_);
+	uint64_t ecc(CalculateECC(data));
+	if (architecture_ == "Series7") {
+		data[ecc_word_] &= 0xffffe000;
+		data[ecc_word_] |= ecc & 0x1fff;
+	} else {
+		data[ecc_word_] = ecc;
+		data[ecc_word_ + 1] &= 0xffff0000;
+		data[ecc_word_ + 1] |= (ecc >> 32) & 0xffff;
+	}
+}
diff --git a/tools/bits2rbt/ecc.h b/tools/bits2rbt/ecc.h
new file mode 100644
index 0000000..aca3577
--- /dev/null
+++ b/tools/bits2rbt/ecc.h
@@ -0,0 +1,33 @@
+#ifndef ECC_H_
+#define ECC_H_
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+class ECC {
+       public:
+	ECC(const std::string& architecture, size_t words_per_frame)
+	    : architecture_(architecture),
+	      frame_words_(words_per_frame),
+	      ecc_word_(ecc_word_per_architecture.at(architecture)) {}
+
+	void UpdateFrameECC(std::vector<uint32_t>& data) const;
+
+       private:
+	const std::string architecture_;
+	const size_t frame_words_;
+	const size_t ecc_word_;
+	static const std::unordered_map<std::string, size_t>
+	    ecc_word_per_architecture;
+
+	uint64_t CalculateECC(const std::vector<uint32_t>& words) const;
+	uint64_t GetUSEccFrameOffset(int word, int bit) const;
+	uint32_t GetSeries7WordEcc(uint32_t idx,
+	                           uint32_t data,
+	                           uint32_t ecc) const;
+	uint64_t GetUSWordEcc(uint32_t idx, uint32_t data, uint64_t ecc) const;
+};
+
+#endif  // ECC_H_
diff --git a/tools/bits2rbt/header.cc b/tools/bits2rbt/header.cc
new file mode 100644
index 0000000..b3c1456
--- /dev/null
+++ b/tools/bits2rbt/header.cc
@@ -0,0 +1,105 @@
+#include "header.h"
+#include <algorithm>
+#include <ctime>
+#include <sstream>
+
+Header::Header(const std::string& line,
+               std::vector<uint32_t>& fpga_config_packets) {
+	absl::string_view header_str(line);
+	// Go to tag 'a' of the TLV formatted header
+	header_str.remove_prefix(header_str.find("61"));
+	bool tlv_header_end = false;
+	while (!tlv_header_end) {
+		char tag = char(GetByteAndAdvance(header_str));
+		switch (tag) {
+			case 'a':
+				design_name_ = GetTLVHeaderValue(header_str);
+				break;
+			case 'b':
+				part_ = GetTLVHeaderValue(header_str);
+				break;
+			case 'c':
+				date_ = GetTLVHeaderValue(header_str);
+				break;
+			case 'd':
+				date_ += " " + GetTLVHeaderValue(header_str);
+				break;
+			case 'e':
+				// Get number of bytes in bitstream and multiply
+				// by 8 to obtain number of bits
+				no_bits_ = GetWord(header_str) * 8;
+				tlv_header_end = true;
+				break;
+			default:
+				assert(false);
+		}
+	}
+	while (!header_str.empty()) {
+		fpga_config_packets.emplace_back(GetWord(header_str));
+	}
+}
+
+std::string Header::GetDate() {
+	int year, month, day, hour, min, sec;
+	std::replace_if(date_.begin(), date_.end(),
+	    		[](char c) { return c == '/' or c == ':'; }, ' ');
+	std::istringstream(date_) >> year >> month >> day >> hour >> min >> sec;
+	std::tm time_raw = {sec, min, hour, day, month - 1, year - 1900};
+	time_t time = mktime(&time_raw);
+	const std::tm* time_out = std::localtime(&time);
+	return std::string(std::asctime(time_out));
+}
+
+std::string Header::GetArchitecture() {
+	if (part_.find("xczu") != std::string::npos) {
+		return "zynquplus";
+	}
+	if (part_.find("7a") != std::string::npos) {
+		return "artix7";
+	}
+	if (part_.find("xcku") != std::string::npos) {
+		return "kintexu";
+	}
+	return "Unknown architecture";
+}
+
+void Header::Write(std::ostream& out) {
+	out << "Xilinx ASCII Bitstream" << std::endl;
+	out << "Created by" << std::endl;
+	out << "Design name:   " << design_name_ << std::endl;
+	out << "Architecture:  " << GetArchitecture() << std::endl;
+	out << "Part:          " << part_ << std::endl;
+	out << "Date:          " << GetDate();
+	out << "Bits:          " << no_bits_ << std::endl;
+}
+
+size_t Header::GetByteAndAdvance(absl::string_view& str_view) {
+	size_t space_pos(str_view.find(" "));
+	size_t byte =
+	    std::stoul(std::string(str_view.substr(0, space_pos)), nullptr, 16);
+	str_view.remove_prefix((space_pos != absl::string_view::npos)
+	                           ? space_pos + 1
+	                           : str_view.size());
+	return byte;
+}
+
+size_t Header::GetTLVHeaderLength(absl::string_view& str_view) {
+	return (GetByteAndAdvance(str_view) << 8) | GetByteAndAdvance(str_view);
+}
+
+std::string Header::GetTLVHeaderValue(absl::string_view& str_view) {
+	size_t length(GetTLVHeaderLength(str_view));
+	std::string value;
+	for (size_t i = 0; i < length; i++) {
+		value += char(GetByteAndAdvance(str_view));
+	}
+	// Lose trailing 0x00
+	value.pop_back();
+	return value;
+}
+
+uint32_t Header::GetWord(absl::string_view& str_view) {
+	return (GetByteAndAdvance(str_view) << 24) |
+	       (GetByteAndAdvance(str_view) << 16) |
+	       (GetByteAndAdvance(str_view) << 8) | GetByteAndAdvance(str_view);
+}
diff --git a/tools/bits2rbt/header.h b/tools/bits2rbt/header.h
new file mode 100644
index 0000000..3c42fb4
--- /dev/null
+++ b/tools/bits2rbt/header.h
@@ -0,0 +1,27 @@
+#ifndef HEADER_H_
+#define HEADER_H_
+#include <absl/strings/string_view.h>
+#include <iostream>
+#include <string>
+#include <vector>
+
+class Header {
+       public:
+	Header(const std::string& line,
+	       std::vector<uint32_t>& fpga_config_packets);
+	void Write(std::ostream& out);
+
+       private:
+	std::string design_name_;
+	std::string part_;
+	std::string date_;
+	uint32_t no_bits_;
+
+	std::string GetTLVHeaderValue(absl::string_view& str_view);
+	uint32_t GetWord(absl::string_view& str_view);
+	size_t GetTLVHeaderLength(absl::string_view& str_view);
+	size_t GetByteAndAdvance(absl::string_view& str_view);
+	std::string GetDate();
+	std::string GetArchitecture();
+};
+#endif  // HEADER_H_