diff --git a/README.md b/README.md index bc60782..bc92b3f 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,20 @@ This command-line tool converts [FASM][fasm-spec] files into bitstreams, simplifying the assembly of human-readable FPGA configurations into the binary formats needed to program various FPGAs. At this stage, it can generate a set of frames in the same manner as [fasm2frames](https://github.com/chipsalliance/f4pga-xc-fasm/blob/25dc605c9c0896204f0c3425b52a332034cf5e5c/xc_fasm/fasm2frames.py). -It has been tested with the Artix-7 [counter example][counter-example], where it produces identical frames—at approximately 10 times the speed compared to the pure Python textX based parser implementation. +It has been tested with the Artix-7 [counter example][counter-example], where it produces identical frames—at and a working bitstream at approximately 10 times the speed compared to the pure Python textX based parser implementation. ## Usage First, install [Bazel][bazel] and ensure you have a basic C/C++ toolchain set up. Then run: ``` -bazel run -c opt //fpga:fpga-as -- --prjxray_db_path=/some/path/prjxray-db/artix7 --part=xc7a35tcsg324-1 < /some/path.fasm +bazel run -c opt //fpga:fpga-as -- --prjxray_db_path=/some/path/prjxray-db/artix7 --part=xc7a35tcsg324-1 < /some/path.fasm > output.bit +``` + +Finally, load the bitstream in your FPGA using [openFPGALoader][open-fpga-loader] + +``` +openFPGALoader -b arty output.bit ``` ## Installation @@ -59,3 +65,4 @@ Using this metadata, you can search the segbits database for the specific featur [fasm-spec]: https://fasm.readthedocs.io/en/stable/# [bazel]: https://bazel.build/ [counter-example]: https://github.com/chipsalliance/f4pga-examples/blob/13f11197b33dae1cde3bf146f317d63f0134eacf/xc7/counter_test/counter.v +[open-fpga-loader]: https://github.com/trabucayre/openFPGALoader diff --git a/fpga/BUILD b/fpga/BUILD index 0dfd1ac..96fde4e 100644 --- a/fpga/BUILD +++ b/fpga/BUILD @@ -3,6 +3,7 @@ load("@rules_license//rules:license.bzl", "license") package( default_applicable_licenses = [":license"], + default_visibility = ["//:__subpackages__"], ) license( @@ -38,6 +39,7 @@ cc_library( "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/types:span", ], ) @@ -134,6 +136,8 @@ cc_binary( ":database-parsers", ":fasm-parser", ":memory-mapped-file", + "//fpga/xilinx:arch-types", + "//fpga/xilinx:bitstream", "@abseil-cpp//absl/cleanup:cleanup", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/container:flat_hash_set", diff --git a/fpga/assembler.cc b/fpga/assembler.cc index 4be92ad..5913c1f 100644 --- a/fpga/assembler.cc +++ b/fpga/assembler.cc @@ -23,11 +23,12 @@ #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" -#include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "fpga/database-parsers.h" #include "fpga/database.h" #include "fpga/fasm-parser.h" +#include "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/bitstream.h" struct TileSiteInfo { std::string tile; @@ -330,6 +331,7 @@ static absl::StatusOr GetOptFlagOrFromEnv( return flag_value.value(); } +#if 0 static void GetPrettyFrameLine( const uint32_t address, const std::array &bits, @@ -369,6 +371,7 @@ static void PrintFrames(const fpga::Frames &frames, std::ostream &out, } } } +#endif int main(int argc, char *argv[]) { const std::string usage = Usage(argv[0]); @@ -430,7 +433,16 @@ int main(int argc, char *argv[]) { << '\n'; return EXIT_FAILURE; } - PrintFrames(frames, std::cout, false); + const fpga::Part &part_data = part_database_result->tiles().part; + const auto bitstream_status = + fpga::xilinx::BitStream::Encode< + fpga::Frames>(part_data, "fasm", "fpga-source", frames, std::cout); + if (!bitstream_status.ok()) { + std::cerr << StatusToErrorMessage("could not generate bistream", + bitstream_status) + << '\n'; + return EXIT_FAILURE; + } // Write bitstream return EXIT_SUCCESS; } diff --git a/fpga/database-parsers.cc b/fpga/database-parsers.cc index ba3dc67..e961129 100644 --- a/fpga/database-parsers.cc +++ b/fpga/database-parsers.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "absl/base/optimization.h" @@ -354,9 +355,13 @@ absl::StatusOr Unmarshal(const rapidjson::Value &json) { struct Part part; OK_OR_RETURN(IsObject(json)); ASSIGN_OR_RETURN(part.idcode, GetMember(json, "idcode")); - ASSIGN_OR_RETURN( - part.iobanks, - (GetMember>(json, "iobanks"))); + std::optional iobanks; + ASSIGN_OR_RETURN(iobanks, + (OptGetMember>( + json, "iobanks"))); + if (iobanks) { + part.iobanks = std::move(iobanks.value()); + } ASSIGN_OR_RETURN(part.global_clock_regions, (GetMember( json, "global_clock_regions"))); return part; diff --git a/fpga/database-parsers.h b/fpga/database-parsers.h index 67095b3..c34fb23 100644 --- a/fpga/database-parsers.h +++ b/fpga/database-parsers.h @@ -85,7 +85,7 @@ struct SegmentBit { // Word index of the bit to enable. uint32_t word_bit; - // If the char '!' is prepended. + // False if the char '!' is prepended. bool is_set; }; diff --git a/fpga/database.cc b/fpga/database.cc index 46096e3..057bc59 100644 --- a/fpga/database.cc +++ b/fpga/database.cc @@ -280,19 +280,7 @@ absl::StatusOr ParseTileTypeDatabase( } absl::StatusOr CreateBanksRegistry( - const std::filesystem::path &part_json_path, - const std::filesystem::path &package_pins_path) { - // Parse part.json. - const absl::StatusOr> part_json_result = - fpga::MemoryMapFile(part_json_path); - if (!part_json_result.ok()) return part_json_result.status(); - const absl::StatusOr part_result = - fpga::ParsePartJSON(part_json_result.value()->AsStringView()); - if (!part_result.ok()) { - return part_result.status(); - } - const fpga::Part &part = part_result.value(); - + const fpga::Part &part, const std::filesystem::path &package_pins_path) { // Parse package pins. const absl::StatusOr> package_pins_csv_result = fpga::MemoryMapFile(package_pins_path); @@ -344,16 +332,27 @@ absl::StatusOr PartDatabase::Parse(std::string_view database_path, } return {}; }; - auto banks_tiles_registry_result = CreateBanksRegistry( - std::filesystem::path(database_path) / part_name / "part.json", - std::filesystem::path(database_path) / part_name / "package_pins.csv"); + // Parse part.json. + const absl::StatusOr> part_json_result = + fpga::MemoryMapFile(std::filesystem::path(database_path) / part_name / + "part.json"); + if (!part_json_result.ok()) return part_json_result.status(); + const absl::StatusOr part_result = + fpga::ParsePartJSON(part_json_result.value()->AsStringView()); + if (!part_result.ok()) { + return part_result.status(); + } + const fpga::Part &part = part_result.value(); + + auto banks_tiles_registry_result = + CreateBanksRegistry(part, std::filesystem::path(database_path) / part_name / + "package_pins.csv"); if (!banks_tiles_registry_result.ok()) { return banks_tiles_registry_result.status(); } - const Tiles tiles_foo(std::move(tilegrid_result.value()), - std::move(tiles_database), - std::move(banks_tiles_registry_result.value())); - const std::shared_ptr tiles = std::make_shared(tiles_foo); + const std::shared_ptr tiles = std::make_shared( + std::move(tilegrid_result.value()), std::move(tiles_database), + std::move(banks_tiles_registry_result.value()), part); return absl::StatusOr(tiles); } diff --git a/fpga/database.h b/fpga/database.h index a3277ee..63ae4f9 100644 --- a/fpga/database.h +++ b/fpga/database.h @@ -74,13 +74,15 @@ class PartDatabase { ~PartDatabase() = default; struct Tiles { Tiles(TileGrid grid, TileTypesSegmentsBitsGetter bits, - BanksTilesRegistry banks) + BanksTilesRegistry banks, Part part) : grid(std::move(grid)), bits(std::move(bits)), - banks(std::move(banks)) {} + banks(std::move(banks)), + part(std::move(part)) {} TileGrid grid; TileTypesSegmentsBitsGetter bits; BanksTilesRegistry banks; + Part part; }; explicit PartDatabase(std::shared_ptr part_tiles) : tiles_(std::move(part_tiles)) {} diff --git a/fpga/memory-mapped-file.h b/fpga/memory-mapped-file.h index dfe812f..83b510e 100644 --- a/fpga/memory-mapped-file.h +++ b/fpga/memory-mapped-file.h @@ -1,12 +1,14 @@ #ifndef FPGA_MEMORY_MAPPED_FILE_H #define FPGA_MEMORY_MAPPED_FILE_H +#include #include #include #include #include #include "absl/status/statusor.h" +#include "absl/types/span.h" namespace fpga { // Taken from: verible/common/strings/mem-block.h @@ -14,6 +16,10 @@ class MemoryBlock { public: virtual ~MemoryBlock() = default; virtual std::string_view AsStringView() const = 0; + absl::Span AsBytesView() const { + std::string_view const bytes = AsStringView(); + return {reinterpret_cast(bytes.data()), bytes.size()}; + } protected: MemoryBlock() = default; diff --git a/fpga/xilinx/BUILD b/fpga/xilinx/BUILD index eba0b8f..644e65d 100644 --- a/fpga/xilinx/BUILD +++ b/fpga/xilinx/BUILD @@ -130,6 +130,11 @@ cc_library( ], deps = [ ":arch-xc7-frame", + "//fpga:database-parsers", + "//fpga:memory-mapped-file", + "@abseil-cpp//absl/container:btree", + "@abseil-cpp//absl/log:check", + "@abseil-cpp//absl/status:statusor", ], ) @@ -195,6 +200,7 @@ cc_library( ":arch-xc7-configuration-packet", ":bit-ops", ":configuration-packet", + "@abseil-cpp//absl/container:btree", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/types:optional", "@abseil-cpp//absl/types:span", @@ -206,15 +212,108 @@ cc_test( srcs = [ "configuration-xc7_test.cc", ], + data = [ + "testdata/xc7-configuration.bit", + "testdata/xc7-configuration.debug.bit", + "testdata/xc7-configuration.perframecrc.bit", + "testdata/xc7-configuration-test.json", + ], deps = [ ":arch-types", ":arch-xc7-frame", + ":bitstream-reader", ":configuration", ":configuration-packet", ":frames", + "//fpga:database-parsers", + "//fpga:memory-mapped-file", + "@abseil-cpp//absl/log:check", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/types:optional", + "@abseil-cpp//absl/types:span", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "bitstream-reader", + hdrs = [ + "bitstream-reader.h", + ], + deps = [ + ":arch-types", + ":big-endian-span", + "@abseil-cpp//absl/types:optional", + "@abseil-cpp//absl/types:span", + ], +) + +cc_test( + name = "bitstream-reader_test", + srcs = [ + "bitstream-reader-xc7_test.cc", + ], + deps = [ + ":arch-types", + ":bitstream-reader", + "@abseil-cpp//absl/types:span", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "bitstream-writer", + srcs = [ + "bitstream-writer.cc", + ], + hdrs = [ + "bitstream-writer.h", + ], + deps = [ + ":arch-types", + ":arch-xc7-configuration-packet", + ":bit-ops", + ":configuration-packet", + "@abseil-cpp//absl/log:check", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/time", "@abseil-cpp//absl/types:optional", "@abseil-cpp//absl/types:span", + ], +) + +cc_test( + name = "bitstream-writer_test", + srcs = [ + "bitstream-writer_test.cc", + ], + deps = [ + ":arch-types", + ":arch-xc7-configuration-packet", + ":bit-ops", + ":bitstream-writer", + ":configuration-packet", + "@abseil-cpp//absl/types:span", "@googletest//:gtest", "@googletest//:gtest_main", ], ) + +cc_library( + name = "bitstream", + hdrs = [ + "bitstream.h", + ], + deps = [ + ":arch-types", + ":bitstream-writer", + ":configuration", + ":frames", + "//fpga:database-parsers", + "@abseil-cpp//absl/container:btree", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings:string_view", + ], +) diff --git a/fpga/xilinx/arch-xc7-configuration-packet.h b/fpga/xilinx/arch-xc7-configuration-packet.h index 56a1a3c..0974d9c 100644 --- a/fpga/xilinx/arch-xc7-configuration-packet.h +++ b/fpga/xilinx/arch-xc7-configuration-packet.h @@ -79,7 +79,6 @@ class ConfigurationPacket private: using BaseType = ConfigurationPacketBase; - using ParseResult = BaseType::ParseResult; public: using BaseType::BaseType; diff --git a/fpga/xilinx/arch-xc7-frame.cc b/fpga/xilinx/arch-xc7-frame.cc index 84c9372..744908e 100644 --- a/fpga/xilinx/arch-xc7-frame.cc +++ b/fpga/xilinx/arch-xc7-frame.cc @@ -14,6 +14,7 @@ std::ostream &operator<<(std::ostream &o, BlockType value) { case BlockType::kBlockRam: o << "Block RAM"; break; case BlockType::kCFGCLB: o << "Config CLB"; break; case BlockType::kReserved: o << "Reserved"; break; + case BlockType::kInvalid: o << "Invalid"; break; } return o; } diff --git a/fpga/xilinx/arch-xc7-frame.h b/fpga/xilinx/arch-xc7-frame.h index 6ac7a6a..d8d9758 100644 --- a/fpga/xilinx/arch-xc7-frame.h +++ b/fpga/xilinx/arch-xc7-frame.h @@ -52,6 +52,7 @@ enum class BlockType : unsigned int { kBlockRam = 0x1, kCFGCLB = 0x2, kReserved = 0x3, + kInvalid = 0xFFFFFFFF, }; std::ostream &operator<<(std::ostream &o, BlockType value); diff --git a/fpga/xilinx/arch-xc7-part.cc b/fpga/xilinx/arch-xc7-part.cc index 073700d..ced8400 100644 --- a/fpga/xilinx/arch-xc7-part.cc +++ b/fpga/xilinx/arch-xc7-part.cc @@ -1,13 +1,73 @@ #include "fpga/xilinx/arch-xc7-part.h" +#include #include #include +#include +#include "absl/container/btree_map.h" +#include "absl/log/check.h" +#include "fpga/database-parsers.h" #include "fpga/xilinx/arch-xc7-frame.h" namespace fpga { namespace xilinx { namespace xc7 { +namespace { +static BlockType BlockTypeFrom(const fpga::ConfigBusType type) { + switch (type) { + case ConfigBusType::kCLBIOCLK: return BlockType::kCLBIOCLK; + case ConfigBusType::kBlockRam: return BlockType::kBlockRam; + case ConfigBusType::kCFGCLB: return BlockType::kCLBIOCLK; + default: break; + } + return BlockType::kInvalid; +} + +absl::StatusOr RowFrom(const fpga::ClockRegionRow &clock_region_row) { + absl::btree_map row; + for (const auto &bus_column_pair : clock_region_row) { + const std::vector &config_column = bus_column_pair.second; + if (config_column.empty()) { + continue; + } + const BlockType block_type = BlockTypeFrom(bus_column_pair.first); + absl::btree_map configuration_bus; + for (size_t i = 0; i < config_column.size(); ++i) { + configuration_bus.emplace(i, config_column[i]); + } + row.emplace(block_type, ConfigurationBus(configuration_bus)); + } + return Row(row); +} + +absl::StatusOr GlobalClockRegionFrom( + const fpga::GlobalClockRegionHalf &half) { + absl::btree_map rows; + for (size_t i = 0; i < half.size(); ++i) { + const absl::StatusOr row_status = RowFrom(half[i]); + if (!row_status.ok()) { + return row_status.status(); + } + rows.emplace(i, row_status.value()); + } + return GlobalClockRegion(rows); +} +} // namespace +absl::StatusOr Part::FromPart(const fpga::Part &part) { + absl::StatusOr bottom = + GlobalClockRegionFrom(part.global_clock_regions.bottom_rows); + if (!bottom.ok()) { + return bottom.status(); + } + absl::StatusOr top = + GlobalClockRegionFrom(part.global_clock_regions.top_rows); + if (!top.ok()) { + return top.status(); + } + return Part(part.idcode, top.value(), bottom.value()); +} + bool ConfigurationColumn::IsValidFrameAddress(FrameAddress address) const { return address.minor() < frame_count_; } @@ -181,7 +241,6 @@ std::optional Part::GetNextFrameAddress( } return {}; } - } // namespace xc7 } // namespace xilinx } // namespace fpga diff --git a/fpga/xilinx/arch-xc7-part.h b/fpga/xilinx/arch-xc7-part.h index 05ca34a..2e3c1bd 100644 --- a/fpga/xilinx/arch-xc7-part.h +++ b/fpga/xilinx/arch-xc7-part.h @@ -13,11 +13,13 @@ #include #include #include -#include #include #include #include +#include "absl/container/btree_map.h" +#include "absl/status/statusor.h" +#include "fpga/database-parsers.h" #include "fpga/xilinx/arch-xc7-frame.h" namespace fpga { @@ -69,9 +71,14 @@ ConfigurationColumn::ConfigurationColumn(T first, T last) { // within a Row. An instance of ConfigurationBus will contain one or more // ConfigurationColumns. class ConfigurationBus { + private: + using container_type = absl::btree_map; + public: ConfigurationBus() = default; + explicit ConfigurationBus(container_type configuration_columns) + : configuration_columns_(std::move(configuration_columns)) {} // Constructs a ConfigurationBus from iterators yielding // frame addresses. The frame address need not be contiguous or sorted // but they must all have the same block type, row half, and row @@ -91,7 +98,7 @@ class ConfigurationBus { std::optional GetNextFrameAddress(FrameAddress address) const; private: - std::map configuration_columns_; + container_type configuration_columns_; }; template @@ -120,6 +127,9 @@ ConfigurationBus::ConfigurationBus(T first, T last) { } class Row { + private: + using container_type = absl::btree_map; + public: Row() = default; @@ -129,6 +139,9 @@ class Row { template Row(T first, T last); + explicit Row(container_type configuration_buses) + : configuration_buses_(std::move(configuration_buses)) {} + // 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 @@ -143,7 +156,7 @@ class Row { std::optional GetNextFrameAddress(FrameAddress address) const; private: - std::map configuration_buses_; + container_type configuration_buses_; }; template @@ -176,9 +189,14 @@ Row::Row(T first, T last) { // tiles that divide the chip into top and bottom "halves". Each half may // contains any number of rows, buses, and columns. class GlobalClockRegion { + private: + using container_type = absl::btree_map; + public: GlobalClockRegion() = default; + explicit GlobalClockRegion(container_type rows) : rows_(std::move(rows)) {} + // Construct a GlobalClockRegion from iterators that yield // frame addresses which are known to be valid. The addresses may be // noncontinguous and/or unordered but they must share the same row @@ -186,6 +204,10 @@ class GlobalClockRegion { template GlobalClockRegion(T first, T last); + GlobalClockRegion(container_type::const_iterator first, + container_type::const_iterator last) + : rows_(first, 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. @@ -200,7 +222,7 @@ class GlobalClockRegion { std::optional GetNextFrameAddress(FrameAddress address) const; private: - std::map rows_; + container_type rows_; }; template @@ -229,7 +251,7 @@ class Part { public: constexpr static uint32_t kInvalidIdcode = 0; - static std::optional FromFile(const std::string &path); + static absl::StatusOr FromPart(const fpga::Part &part); // Constructs an invalid part with a zero IDCODE. Required for YAML // conversion but shouldn't be used otherwise. @@ -242,6 +264,11 @@ class Part { template Part(uint32_t idcode, T first, T last); + Part(uint32_t idcode, GlobalClockRegion top, GlobalClockRegion bottom) + : idcode_(idcode), + top_region_(std::move(top)), + bottom_region_(std::move(bottom)) {} + uint32_t idcode() const { return idcode_; } bool IsValidFrameAddress(FrameAddress address) const; diff --git a/fpga/xilinx/big-endian-span.h b/fpga/xilinx/big-endian-span.h index e851bd4..2d2e339 100644 --- a/fpga/xilinx/big-endian-span.h +++ b/fpga/xilinx/big-endian-span.h @@ -12,6 +12,7 @@ #include #include +#include #include #include "absl/types/span.h" @@ -116,6 +117,13 @@ BigEndianSpan make_big_endian_span( return BigEndianSpan( absl::Span(bytes)); } + +template +BigEndianSpan make_big_endian_span( + absl::Span &bytes) { + return BigEndianSpan( + absl::Span(bytes)); +} } // namespace xilinx } // namespace fpga #endif // FPGA_XILINX_BIG_ENDIAN_SPAN diff --git a/fpga/xilinx/bistream-writer.h b/fpga/xilinx/bistream-writer.h new file mode 100644 index 0000000..0e88976 --- /dev/null +++ b/fpga/xilinx/bistream-writer.h @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2017-2020 The Project X-Ray Authors. + * + * Use of this source code is governed by a ISC-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/ISC + * + * SPDX-License-Identifier: ISC + */ +/* + * Takes in a collection of ConfigurationPacket and writes them to specified + * file This includes the following: -Bus auto detection -Sync Word -FPGA + * configuration + */ +#ifndef FPGA_XILINX_BITSTREAM_WRITER_H +#define FPGA_XILINX_BITSTREAM_WRITER_H +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/log/check.h" +#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 "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/arch-xc7-configuration-packet.h" + +namespace fpga { +namespace xilinx { +uint32_t PacketHeader(const xc7::ConfigurationPacket &packet); + +// Writes out the complete Xilinx bitstream including +// header, sync word and configuration sequence. +template +class BitstreamWriter { + private: + using ArchType = ArchitectureType; + using FrameWords = ArchType::FrameWords; + using FrameAddress = ArchType::FrameAddress; + using ConfigurationPacket = ArchType::ConfigurationPacket; + using ConfigurationPackage = ArchType::ConfigurationPackage; + using ConfigurationRegister = ArchType::ConfigurationRegister; + + public: + using header_t = std::vector; + using packets_t = std::vector>; + using BitstreamHeader = std::vector; + // Only defined if a packet exists + using op_data_t = absl::optional>; + using data_iterator_t = absl::Span::iterator; + using itr_value_type = uint32_t; + + class packet_iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = BitstreamWriter::itr_value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + packet_iterator &operator++(); + + bool operator==(const packet_iterator &other) const; + bool operator!=(const packet_iterator &other) const; + + itr_value_type operator*() const; + itr_value_type operator->() const; + + using state_t = enum { + STATE_HEADER = 1, + STATE_DATA = 2, + STATE_END = 3, + }; + + protected: + explicit packet_iterator(const ConfigurationPacket *packet, state_t state, + data_iterator_t itr_data); + + private: + friend class 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 *packet_; + }; + + class iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = BitstreamWriter::itr_value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + iterator &operator++(); + + bool operator==(const iterator &other) const; + bool operator!=(const iterator &other) const; + + itr_value_type operator*() 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 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 op_itr_packet_; + }; + + explicit 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 +int BitstreamWriter::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 << '\n'; + return 1; + } + + BitstreamHeader bit_header( + create_header(part_name, frames_file, generator_name)); + out_file.write(reinterpret_cast(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(4); + + BitstreamWriter 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 const 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 BitstreamWriter::BitstreamHeader +BitstreamWriter::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((build_source.size() + 1) >> 8)); + bit_header.push_back(static_cast(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((part_name.size() + 1) >> 8)); + bit_header.push_back(static_cast(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((build_date_string.size() + 1) >> 8)); + bit_header.push_back(static_cast(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((build_time_string.size() + 1) >> 8)); + bit_header.push_back(static_cast(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 BitstreamWriter::packet_iterator +BitstreamWriter::iterator::packet_begin() { + // itr_packets = packets.begin(); + const ConfigurationPacket &packet = **itr_packets_; + + return BitstreamWriter::packet_iterator( + &packet, BitstreamWriter::packet_iterator::STATE_HEADER, + packet.data().begin()); +} + +template +typename BitstreamWriter::packet_iterator +BitstreamWriter::iterator::packet_end() { + const ConfigurationPacket &packet = **itr_packets_; + + return BitstreamWriter::packet_iterator( + &packet, BitstreamWriter::packet_iterator::STATE_END, + // Essentially ignored + packet.data().end()); +} + +template +BitstreamWriter::packet_iterator::packet_iterator( + const ConfigurationPacket *packet, state_t state, data_iterator_t itr_data) + : state_(state), itr_data_(itr_data), packet_(packet) {} + +template +typename BitstreamWriter::packet_iterator & +BitstreamWriter::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 +bool BitstreamWriter::packet_iterator::operator==( + const packet_iterator &other) const { + return state_ == other.state_ && itr_data_ == other.itr_data_; +} + +template +bool BitstreamWriter::packet_iterator::operator!=( + const packet_iterator &other) const { + return !(*this == other); +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::packet_iterator::operator*() const { + if (state_ == STATE_HEADER) { + return packet2header(*packet_); + } + if (state_ == STATE_DATA) { + return *itr_data_; + } + CHECK(false) << "unreachable state"; +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::packet_iterator::operator->() const { + return *(*this); +} + +template +typename BitstreamWriter::iterator BitstreamWriter::begin() { + typename packets_t::const_iterator itr_packets = packets_.begin(); + absl::optional op_packet_itr; + + // May have no packets + if (itr_packets != packets_.end()) { + // op_packet_itr = packet_begin(); + // FIXME: de-duplicate this + const ConfigurationPacket &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 BitstreamWriter::iterator BitstreamWriter::end() { + return iterator(header_.end(), packets_, packets_.end(), + absl::optional()); +} + +template +BitstreamWriter::iterator::iterator( + header_t::iterator itr_header, + const typename BitstreamWriter::packets_t &packets, + typename BitstreamWriter::packets_t::const_iterator itr_packets, + absl::optional itr_packet) + : itr_header_(itr_header), + packets_(packets), + itr_packets_(itr_packets), + op_itr_packet_(itr_packet) {} + +template +typename BitstreamWriter::iterator & +BitstreamWriter::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 +bool BitstreamWriter::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 +bool BitstreamWriter::iterator::operator!=(const iterator &other) const { + return !(*this == other); +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::iterator::operator*() const { + if (itr_header_ != header_.end()) { + return *itr_header_; + } + // Iterating over packets, get data from current packet position + return *(*op_itr_packet_); +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::iterator::operator->() const { + return *(*this); +} +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_XC7SERIES_BITSTREAM_WRITER_H diff --git a/fpga/xilinx/bitstream-reader-xc7_test.cc b/fpga/xilinx/bitstream-reader-xc7_test.cc new file mode 100644 index 0000000..b130acf --- /dev/null +++ b/fpga/xilinx/bitstream-reader-xc7_test.cc @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017-2020 The Project X-Ray Authors. + * + * Use of this source code is governed by a ISC-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/ISC + * + * SPDX-License-Identifier: ISC + */ +#include + +#include +#include + +#include "absl/types/span.h" +#include "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/bitstream-reader.h" + +namespace fpga { +namespace xilinx { +namespace { +constexpr fpga::xilinx::Architecture kArch = fpga::xilinx::Architecture::kXC7; +using ArchType = ArchitectureType; +using ConfigurationPacket = ArchType::ConfigurationPacket; +using ConfigurationRegister = ArchType::ConfigurationRegister; + +TEST(XC7BitstreamReaderTest, InitWithEmptyBytesReturnsNull) { + absl::Span const bitstream; + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_FALSE(reader); +} + +TEST(XC7BitstreamReaderTest, InitWithOnlySyncReturnsObject) { + std::vector const bitstream{0xAA, 0x99, 0x55, 0x66}; + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_TRUE(reader); +} + +TEST(XC7BitstreamReaderTest, + InitWithSyncAfterNonWordSizedPaddingReturnsObject) { + std::vector const bitstream{0xFF, 0xFE, 0xAA, 0x99, 0x55, 0x66}; + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_TRUE(reader); +} + +TEST(XC7BitstreamReaderTest, InitWithSyncAfterWordSizedPaddingReturnsObject) { + std::vector const bitstream{0xFF, 0xFE, 0xFD, 0xFC, + 0xAA, 0x99, 0x55, 0x66}; + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_TRUE(reader); +} + +TEST(XC7BitstreamReaderTest, ParsesType1Packet) { + std::vector const bitstream{ + 0xAA, 0x99, 0x55, 0x66, // sync + 0x20, 0x00, 0x00, 0x00, // NOP + }; + auto reader = BitstreamReader::InitWithBytes(bitstream); + ASSERT_TRUE(reader); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + ASSERT_NE(reader->begin(), reader->end()); + + auto first_packet = reader->begin(); + EXPECT_EQ(first_packet->opcode(), ConfigurationPacket::Opcode::kNOP); + + EXPECT_EQ(++first_packet, reader->end()); + // NOLINTEND(bugprone-unchecked-optional-access) +} + +TEST(XC7BitstreamReaderTest, ParseType2PacketWithoutType1Fails) { + std::vector const bitstream{ + 0xAA, 0x99, 0x55, 0x66, // sync + 0x40, 0x00, 0x00, 0x00, // Type 2 NOP + }; + auto reader = BitstreamReader::InitWithBytes(bitstream); + ASSERT_TRUE(reader); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(reader->begin(), reader->end()); +} + +TEST(XC7BitstreamReaderTest, ParsesType2AfterType1Packet) { + std::vector const 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 data_words{0x01020304, 0x05060708, 0x090A0B0C, + 0x0D0E0F10}; + + auto reader = BitstreamReader::InitWithBytes(bitstream); + ASSERT_TRUE(reader); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + ASSERT_NE(reader->begin(), reader->end()); + + auto first_packet = reader->begin(); + EXPECT_EQ(first_packet->opcode(), ConfigurationPacket::Opcode::kRead); + EXPECT_EQ(first_packet->address(), ConfigurationRegister::kFDRO); + EXPECT_EQ(first_packet->data(), absl::Span()); + + auto second_packet = ++first_packet; + ASSERT_NE(second_packet, reader->end()); + EXPECT_EQ(second_packet->opcode(), ConfigurationPacket::Opcode::kRead); + EXPECT_EQ(second_packet->address(), ConfigurationRegister::kFDRO); + EXPECT_EQ(first_packet->data(), absl::Span(data_words)); + + EXPECT_EQ(++first_packet, reader->end()); + // NOLINTEND(bugprone-unchecked-optional-access) +} +} // namespace +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/bitstream-reader.h b/fpga/xilinx/bitstream-reader.h new file mode 100644 index 0000000..62f7bde --- /dev/null +++ b/fpga/xilinx/bitstream-reader.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2017-2020 The Project X-Ray Authors. + * + * Use of this source code is governed by a ISC-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/ISC + * + * SPDX-License-Identifier: ISC + */ +#ifndef FPGA_XILINX_BITSTREAM_READER_H +#define FPGA_XILINX_BITSTREAM_READER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/types/optional.h" +#include "absl/types/span.h" +#include "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/big-endian-span.h" + +namespace fpga { +namespace xilinx { +// Constructs a collection of 32-bit big-endian words from a bitstream file. +// Provides an iterator over the configuration packets. +template +class BitstreamReader { + public: + using ArchType = ArchitectureType; + using value_type = ArchType::ConfigurationPacket; + + // Implements an iterator over the words grouped in configuration + // packets. + class iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = BitstreamReader::value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + 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 words); + + private: + friend BitstreamReader; + + typename value_type::ParseResult parse_result_; + absl::Span words_; + }; + + // Construct a reader from a collection of 32-bit, big-endian words. + // Assumes that any sync word has already been removed. + explicit BitstreamReader(std::vector &&words) + : words_(std::move(words)) {} + + BitstreamReader() = default; + size_t size() { return words_.size(); } + + // Construct a `BitstreamReader` from a Container of bytes. + // Any bytes preceding an initial sync word are ignored. + template + static absl::optional> InitWithBytes(T bitstream); + + // Extract information from bitstream necessary to reconstruct RBT + // header and add it to the AUX data + template + 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 &words() { return words_; }; + + // Returns an iterator that yields `ConfigurationPackets` + // as read from the bitstream. + iterator begin(); + iterator end(); + + private: + static const std::array kSyncWord; + static const std::array kWcfgCmd; + static const std::array kNullCmd; + + std::vector words_; +}; + +// Extract FPGA configuration logic information +template +void BitstreamReader::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 +template +void BitstreamReader::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 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 +template +absl::optional> BitstreamReader::InitWithBytes( + T bitstream) { + using bitstream_value_type = const T::value_type; + using words_value_type = ArchType::FrameWords::value_type; + + // 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>(); + } + sync_pos += kSyncWord.size(); + + // Wrap the provided container in a span that strips off the preamble. + absl::Span bitstream_span(bitstream); + absl::Span 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(config_packets); + /// std::vector words; + std::vector words{big_endian_reader.begin(), + big_endian_reader.end()}; + + return BitstreamReader(std::move(words)); +} + +// Sync word as specified in UG470 page 81 +template +const std::array BitstreamReader::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 +const std::array BitstreamReader::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 +const std::array BitstreamReader::kNullCmd = {0x30008001, + 0x0}; + +template +typename BitstreamReader::iterator BitstreamReader::begin() { + return iterator(absl::MakeSpan(words_)); +} + +template +typename BitstreamReader::iterator BitstreamReader::end() { + return iterator({}); +} + +template +BitstreamReader::iterator::iterator(absl::Span words) { + parse_result_.first = words; + parse_result_.second = {}; + ++(*this); +} + +template +typename BitstreamReader::iterator & +BitstreamReader::iterator::operator++() { + do { + auto new_result = value_type::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(); + break; + } + words_ = parse_result_.first; + parse_result_ = new_result; + } while (!parse_result_.first.empty() && !parse_result_.second); + + if (!parse_result_.second) { + words_ = absl::Span(); + } + return *this; +} + +template +bool BitstreamReader::iterator::operator==(const iterator &other) const { + return words_ == other.words_; +} + +template +bool BitstreamReader::iterator::operator!=(const iterator &other) const { + return !(*this == other); +} + +template +const typename BitstreamReader::value_type & +BitstreamReader::iterator::operator*() const { + return *(parse_result_.second); +} + +template +const typename BitstreamReader::value_type * +BitstreamReader::iterator::operator->() const { + return parse_result_.second.operator->(); +} +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_BITSTREAM_READER_H diff --git a/fpga/xilinx/bitstream-writer.cc b/fpga/xilinx/bitstream-writer.cc new file mode 100644 index 0000000..32cf8a3 --- /dev/null +++ b/fpga/xilinx/bitstream-writer.cc @@ -0,0 +1,60 @@ +#include "fpga/xilinx/bitstream-writer.h" + +#include + +#include "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/arch-xc7-configuration-packet.h" +#include "fpga/xilinx/bit-ops.h" +#include "fpga/xilinx/configuration-packet.h" + +namespace fpga { +namespace xilinx { +// Per UG470 pg 80: Bus Width Auto Detection +template <> +typename BitstreamWriter::header_t + BitstreamWriter::header_{ + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044, + 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; + +template <> +typename BitstreamWriter::header_t + BitstreamWriter::header_{ + 0xFFFFFFFF, 0x000000BB, 0x11220044, 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; + +template <> +typename BitstreamWriter::header_t + BitstreamWriter::header_{ + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044, + 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; + +uint32_t PacketHeader(const xc7::ConfigurationPacket &packet) { + uint32_t ret = 0; + ret = bit_field_set(ret, 31, 29, packet.header_type()); + switch (static_cast(packet.header_type())) { + case ConfigurationPacketType::kNONE: + // Bitstreams are 0 padded sometimes, essentially making + // a type 0 frame Ignore the other fields for now + break; + case ConfigurationPacketType::kTYPE1: { + // 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 ConfigurationPacketType::kTYPE2: { + // 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 fpga diff --git a/fpga/xilinx/bitstream-writer.h b/fpga/xilinx/bitstream-writer.h new file mode 100644 index 0000000..5bb5359 --- /dev/null +++ b/fpga/xilinx/bitstream-writer.h @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2017-2020 The Project X-Ray Authors. + * + * Use of this source code is governed by a ISC-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/ISC + * + * SPDX-License-Identifier: ISC + */ +/* + * Takes in a collection of ConfigurationPacket and writes them to specified + * file This includes the following: -Bus auto detection -Sync Word -FPGA + * configuration + */ +#ifndef FPGA_XILINX_BITSTREAM_WRITER_H +#define FPGA_XILINX_BITSTREAM_WRITER_H +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/log/check.h" +#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 "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/arch-xc7-configuration-packet.h" + +namespace fpga { +namespace xilinx { +uint32_t PacketHeader(const xc7::ConfigurationPacket &packet); + +// Writes out the complete Xilinx bitstream including +// header, sync word and configuration sequence. +template +class BitstreamWriter { + private: + using ArchType = ArchitectureType; + using FrameWords = ArchType::FrameWords; + using FrameAddress = ArchType::FrameAddress; + using ConfigurationPacket = ArchType::ConfigurationPacket; + using ConfigurationPackage = ArchType::ConfigurationPackage; + using ConfigurationRegister = ArchType::ConfigurationRegister; + + public: + using header_t = std::vector; + using packets_t = std::vector>; + using BitstreamHeader = std::vector; + // Only defined if a packet exists + using op_data_t = absl::optional>; + using data_iterator_t = absl::Span::iterator; + using itr_value_type = uint32_t; + + class packet_iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = BitstreamWriter::itr_value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + packet_iterator &operator++(); + + bool operator==(const packet_iterator &other) const; + bool operator!=(const packet_iterator &other) const; + + itr_value_type operator*() const; + itr_value_type operator->() const; + + using state_t = enum { + STATE_HEADER = 1, + STATE_DATA = 2, + STATE_END = 3, + }; + + protected: + explicit packet_iterator(const ConfigurationPacket *packet, state_t state, + data_iterator_t itr_data); + + private: + friend class 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 *packet_; + }; + + class iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = BitstreamWriter::itr_value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + iterator &operator++(); + + bool operator==(const iterator &other) const; + bool operator!=(const iterator &other) const; + + itr_value_type operator*() 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 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 op_itr_packet_; + }; + + explicit 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 &source_name, + const std::string &generator_name, std::ostream &out); + 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 &source_name, + const std::string &generator_name, + std::ostream &out); +}; + +template +int BitstreamWriter::writeBitstream(const ConfigurationPackage &packets, + const std::string &part_name, + const std::string &source_name, + const std::string &generator_name, + std::ostream &out) { + BitstreamHeader bit_header( + create_header(part_name, source_name, generator_name, out)); + out.write(reinterpret_cast(bit_header.data()), + bit_header.size()); + + auto end_of_header_pos = out.tellp(); + auto header_data_length_pos = + end_of_header_pos - static_cast(4); + + BitstreamWriter out_bitstream_writer(packets); + int bytes_per_word = sizeof(typename FrameWords::value_type); + for (uint32_t word : out_bitstream_writer) { + for (int byte = bytes_per_word - 1; byte >= 0; byte--) { + out.put((word >> (byte * 8)) & 0xFF); + } + } + + uint32_t const length_of_data = out.tellp() - end_of_header_pos; + + out.seekp(header_data_length_pos); + for (int byte = 3; byte >= 0; byte--) { + out.put((length_of_data >> (byte * 8)) & 0xFF); + } + return 0; +} + +template +typename BitstreamWriter::BitstreamHeader +BitstreamWriter::create_header(const std::string &part_name, + const std::string &source_name, + const std::string &generator_name, + std::ostream &out) { + // Sync header + BitstreamHeader bit_header{0x0, 0x9, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, + 0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x01, 'a'}; + auto build_source = absl::StrCat(source_name, ";Generator=" + generator_name); + bit_header.push_back(static_cast((build_source.size() + 1) >> 8)); + bit_header.push_back(static_cast(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((part_name.size() + 1) >> 8)); + bit_header.push_back(static_cast(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((build_date_string.size() + 1) >> 8)); + bit_header.push_back(static_cast(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((build_time_string.size() + 1) >> 8)); + bit_header.push_back(static_cast(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 BitstreamWriter::packet_iterator +BitstreamWriter::iterator::packet_begin() { + // itr_packets = packets.begin(); + const ConfigurationPacket &packet = **itr_packets_; + + return BitstreamWriter::packet_iterator( + &packet, BitstreamWriter::packet_iterator::STATE_HEADER, + packet.data().begin()); +} + +template +typename BitstreamWriter::packet_iterator +BitstreamWriter::iterator::packet_end() { + const ConfigurationPacket &packet = **itr_packets_; + + return BitstreamWriter::packet_iterator( + &packet, BitstreamWriter::packet_iterator::STATE_END, + // Essentially ignored + packet.data().end()); +} + +template +BitstreamWriter::packet_iterator::packet_iterator( + const ConfigurationPacket *packet, state_t state, data_iterator_t itr_data) + : state_(state), itr_data_(itr_data), packet_(packet) {} + +template +typename BitstreamWriter::packet_iterator & +BitstreamWriter::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 +bool BitstreamWriter::packet_iterator::operator==( + const packet_iterator &other) const { + return state_ == other.state_ && itr_data_ == other.itr_data_; +} + +template +bool BitstreamWriter::packet_iterator::operator!=( + const packet_iterator &other) const { + return !(*this == other); +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::packet_iterator::operator*() const { + if (state_ == STATE_HEADER) { + return PacketHeader(*packet_); + } + if (state_ == STATE_DATA) { + return *itr_data_; + } + CHECK(false) << "unreachable state"; +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::packet_iterator::operator->() const { + return *(*this); +} + +template +typename BitstreamWriter::iterator BitstreamWriter::begin() { + typename packets_t::const_iterator itr_packets = packets_.begin(); + absl::optional op_packet_itr; + + // May have no packets + if (itr_packets != packets_.end()) { + // op_packet_itr = packet_begin(); + // FIXME: de-duplicate this + const ConfigurationPacket &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 BitstreamWriter::iterator BitstreamWriter::end() { + return iterator(header_.end(), packets_, packets_.end(), + absl::optional()); +} + +template +BitstreamWriter::iterator::iterator( + header_t::iterator itr_header, + const typename BitstreamWriter::packets_t &packets, + typename BitstreamWriter::packets_t::const_iterator itr_packets, + absl::optional itr_packet) + : itr_header_(itr_header), + packets_(packets), + itr_packets_(itr_packets), + op_itr_packet_(itr_packet) {} + +template +typename BitstreamWriter::iterator & +BitstreamWriter::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 +bool BitstreamWriter::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 +bool BitstreamWriter::iterator::operator!=(const iterator &other) const { + return !(*this == other); +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::iterator::operator*() const { + if (itr_header_ != header_.end()) { + return *itr_header_; + } + // Iterating over packets, get data from current packet position + return *(*op_itr_packet_); +} + +template +typename BitstreamWriter::itr_value_type +BitstreamWriter::iterator::operator->() const { + return *(*this); +} +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_XC7SERIES_BITSTREAM_WRITER_H diff --git a/fpga/xilinx/bitstream-writer_test.cc b/fpga/xilinx/bitstream-writer_test.cc new file mode 100644 index 0000000..384abd9 --- /dev/null +++ b/fpga/xilinx/bitstream-writer_test.cc @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2017-2020 The Project X-Ray Authors. + * + * Use of this source code is governed by a ISC-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/ISC + * + * SPDX-License-Identifier: ISC + */ +#include "fpga/xilinx/bitstream-writer.h" + +#include +#include +#include + +#include "absl/types/span.h" +#include "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/arch-xc7-configuration-packet.h" +#include "fpga/xilinx/bit-ops.h" +#include "fpga/xilinx/configuration-packet.h" +#include "gtest/gtest.h" + +namespace fpga { +namespace xilinx { +namespace { +constexpr fpga::xilinx::Architecture kArch = fpga::xilinx::Architecture::kXC7; +using ArchType = ArchitectureType; +using ConfigurationPacket = ArchType::ConfigurationPacket; +using ConfigurationRegister = ArchType::ConfigurationRegister; + +constexpr uint32_t MakeType1(const int opcode, const int address, + const int word_count) { + return bit_field_set( + bit_field_set( + bit_field_set(bit_field_set(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 bit_field_set( + bit_field_set(bit_field_set(0x0, 31, 29, 0x2), 28, 27, + opcode), + 26, 0, word_count); +} + +#if 0 +void DumpPackets(BitstreamWriter writer, bool nl = true) { + int i = 0; + for (unsigned int itr : writer) { + if (nl) { + printf("% 3d: 0x0%08X\n", i, itr); + } else { + printf("0x0%08X, ", itr); + } + fflush(stdout); + ++i; + } + if (!nl) { + printf("\n"); + } +} +#endif + +// Special all 0's +void AddType0(std::vector> &packets) { + static std::vector words{}; + absl::Span const word_span(words); + // CRC is config value 0 + packets.emplace_back( + new ConfigurationPacket(0, ConfigurationPacket::Opcode::kNOP, + xc7::ConfigurationRegister::kCRC, word_span)); +} + +void AddType1(std::vector> &packets) { + static std::vector words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB}; + absl::Span const word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span); + ASSERT_TRUE(packet.second.has_value()); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + packets.emplace_back(new ConfigurationPacket(*(packet.second))); +} + +// Empty +void AddType1E(std::vector> &packets) { + static std::vector words{MakeType1(0x2, 0x3, 0)}; + absl::Span const word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span); + ASSERT_TRUE(packet.second.has_value()); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + packets.emplace_back(new ConfigurationPacket(*(packet.second))); +} + +void AddType2(std::vector> &packets) { + // Type 1 packet with address + // Without this InitWithWords will fail on type 2 + ConfigurationPacket *packet1; + { + static std::vector words{MakeType1(0x2, 0x3, 0)}; + absl::Span const word_span(words); + auto packet1_pair = ConfigurationPacket::InitWithWords(word_span); + ASSERT_TRUE(packet1_pair.second.has_value()); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + packets.emplace_back(new ConfigurationPacket(*(packet1_pair.second))); + packet1 = packets[0].get(); + } + // Type 2 packet with data + { + static std::vector words{ + MakeType2(0x01, 12), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + absl::Span const word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span, packet1); + ASSERT_TRUE(packet.second.has_value()); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + packets.emplace_back(new ConfigurationPacket(*(packet.second))); + } +} + +// Empty packets should produce just the header +TEST(BitstreamWriterTest, WriteHeader) { + std::vector> const packets; + + BitstreamWriter writer(packets); + std::vector const words(writer.begin(), writer.end()); + + // Per UG470 pg 80: Bus Width Auto Detection + std::vector const ref_header{ + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044, + 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; + EXPECT_EQ(words, ref_header); +} + +TEST(BitstreamWriterTest, WriteType0) { + std::vector> packets; + AddType0(packets); + BitstreamWriter writer(packets); + std::vector const words(writer.begin(), writer.end()); + std::vector const 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> packets; + AddType1(packets); + BitstreamWriter writer(packets); + std::vector const words(writer.begin(), writer.end()); + std::vector const 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> packets; + AddType2(packets); + BitstreamWriter writer(packets); + std::vector const words(writer.begin(), writer.end()); + std::vector const 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> packets; + AddType1(packets); + AddType1E(packets); + AddType2(packets); + AddType1E(packets); + BitstreamWriter writer(packets); + std::vector const words(writer.begin(), writer.end()); + std::vector const 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); +} +} // namespace +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/bitstream.h b/fpga/xilinx/bitstream.h new file mode 100644 index 0000000..738157d --- /dev/null +++ b/fpga/xilinx/bitstream.h @@ -0,0 +1,74 @@ +#ifndef FPGA_XILINX_BITSTREAM_H +#define FPGA_XILINX_BITSTREAM_H + +#include +#include +#include + +#include "absl/container/btree_map.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "fpga/database-parsers.h" +#include "fpga/xilinx/arch-types.h" +#include "fpga/xilinx/bitstream-writer.h" +#include "fpga/xilinx/configuration.h" +#include "fpga/xilinx/frames.h" + +namespace fpga { +namespace xilinx { +template +class BitStream { + private: + using ArchType = ArchitectureType; + using FrameWords = ArchType::FrameWords; + using FrameAddress = ArchType::FrameAddress; + using ConfigurationPackage = ArchType::ConfigurationPackage; + using Part = ArchType::Part; + using FramesType = Frames; + using ConfigurationType = Configuration; + using BitstreamWriterType = BitstreamWriter; + + public: + template + static absl::Status Encode(const fpga::Part &part, + absl::string_view part_name, + absl::string_view source_name, + const FramesData &frames_data, std::ostream &out) { + absl::btree_map converted_frames; + for (const auto &address_words_pair : frames_data) { + converted_frames.emplace(address_words_pair.first, + address_words_pair.second); + } + constexpr absl::string_view kGeneratorName = "fpga-assembler"; + FramesType frames(converted_frames); + absl::StatusOr xilinx_part_status = Part::FromPart(part); + if (!xilinx_part_status.ok()) { + return xilinx_part_status.status(); + } + std::optional xilinx_part = xilinx_part_status.value(); + frames.AddMissingFrames(xilinx_part); + // Create data for the type 2 configuration packet with + // information about all frames + typename ConfigurationType::PacketData configuration_packet_data( + ConfigurationType::CreateType2ConfigurationPacketData(frames.GetFrames(), + xilinx_part)); + + // Put together a configuration package + ConfigurationPackage configuration_package; + ConfigurationType::CreateConfigurationPackage( + configuration_package, configuration_packet_data, xilinx_part); + + // Write bitstream + auto bitstream_writer = BitstreamWriterType(configuration_package); + if (bitstream_writer.writeBitstream( + configuration_package, std::string(part_name), + std::string(source_name), std::string(kGeneratorName), out)) { + return absl::InternalError("failed generating bitstream"); + } + return absl::OkStatus(); + } +}; +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_BITSTREAM_H diff --git a/fpga/xilinx/configuration-packet.h b/fpga/xilinx/configuration-packet.h index 4365206..72a8af8 100644 --- a/fpga/xilinx/configuration-packet.h +++ b/fpga/xilinx/configuration-packet.h @@ -33,9 +33,7 @@ enum class ConfigurationPacketType : uint32_t { kNONE, kTYPE1, kTYPE2 }; template class ConfigurationPacketBase { public: - using ParseResult = - std::pair, - absl::optional>>; + using ParseResult = std::pair, absl::optional>; // Opcodes as specified in UG470 page 108 enum class Opcode { diff --git a/fpga/xilinx/configuration-xc7_test.cc b/fpga/xilinx/configuration-xc7_test.cc index 44de4a3..b3e53f4 100644 --- a/fpga/xilinx/configuration-xc7_test.cc +++ b/fpga/xilinx/configuration-xc7_test.cc @@ -10,12 +10,19 @@ #include #include #include +#include +#include #include +#include "absl/log/check.h" +#include "absl/status/statusor.h" #include "absl/types/optional.h" #include "absl/types/span.h" +#include "fpga/database-parsers.h" +#include "fpga/memory-mapped-file.h" #include "fpga/xilinx/arch-types.h" #include "fpga/xilinx/arch-xc7-frame.h" +#include "fpga/xilinx/bitstream-reader.h" #include "fpga/xilinx/configuration-packet.h" #include "fpga/xilinx/configuration.h" #include "fpga/xilinx/frames.h" @@ -139,118 +146,128 @@ TEST(XC7ConfigurationTest, ConstructFromPacketsWithAutoincrement) { // NOLINTEND(bugprone-unchecked-optional-access) } -// TEST(XC7ConfigurationTest, -// DISABLED_DebugAndPerFrameCrcBitstreamsProduceEqualConfigurations) { -// auto part = 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::InitWithBytes( -// debug_bitstream->as_bytes()); -// ASSERT_TRUE(debug_reader); - -// auto debug_configuration = -// Configuration::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::InitWithBytes( -// perframecrc_bitstream->as_bytes()); -// ASSERT_TRUE(perframecrc_reader); - -// auto perframecrc_configuration = -// Configuration::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(XC7ConfigurationTest, -// 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::InitWithBytes( -// debug_bitstream->as_bytes()); -// ASSERT_TRUE(debug_reader); - -// auto debug_configuration = -// Configuration::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::InitWithBytes( -// normal_bitstream->as_bytes()); -// ASSERT_TRUE(normal_reader); - -// auto normal_configuration = -// Configuration::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"; -// } -// } -// } +// Load Part from JSON. +absl::StatusOr LoadPartJSON(const std::filesystem::path &path) { + const auto block_status = MemoryMapFile(path); + if (!block_status.ok()) { + return block_status.status(); + } + const auto part_status = ParsePartJSON(block_status.value()->AsStringView()); + if (!part_status.ok()) { + return part_status.status(); + } + return Part::FromPart(part_status.value()); +} + +TEST(XC7ConfigurationTest, + DebugAndPerFrameCrcBitstreamsProduceEqualConfigurations) { + const std::filesystem::path kTestDataBase = "fpga/xilinx/testdata"; + auto part = LoadPartJSON(kTestDataBase / "xc7-configuration-test.json"); + ASSERT_TRUE(part.ok()) << part.status(); + + absl::StatusOr> debug_bitstream_status = + MemoryMapFile(kTestDataBase / "xc7-configuration.debug.bit"); + ASSERT_TRUE(debug_bitstream_status.ok()) << debug_bitstream_status.status(); + auto &debug_bitstream = debug_bitstream_status.value(); + CHECK(debug_bitstream); + + auto debug_reader = + BitstreamReader::InitWithBytes(debug_bitstream->AsBytesView()); + CHECK(debug_reader.has_value()); + auto debug_configuration = + Configuration::InitWithPackets(*part, *debug_reader); + CHECK(debug_configuration.has_value()); + + absl::StatusOr> perframecrc_bitstream_status = + MemoryMapFile(kTestDataBase / "xc7-configuration.perframecrc.bit"); + ASSERT_TRUE(perframecrc_bitstream_status.ok()) + << perframecrc_bitstream_status.status(); + auto &perframecrc_bitstream = perframecrc_bitstream_status.value(); + ASSERT_TRUE(perframecrc_bitstream); + + auto perframecrc_reader = + BitstreamReader::InitWithBytes(perframecrc_bitstream->AsBytesView()); + CHECK(perframecrc_reader.has_value()); + auto perframecrc_configuration = + Configuration::InitWithPackets(*part, *perframecrc_reader); + CHECK(perframecrc_configuration.has_value()); + + 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(XC7ConfigurationTest, DebugAndNormalBitstreamsProduceEqualConfigurations) { + const std::filesystem::path kTestDataBase = "fpga/xilinx/testdata"; + auto part = LoadPartJSON(kTestDataBase / "xc7-configuration-test.json"); + ASSERT_TRUE(part.ok()) << part.status(); + + absl::StatusOr> debug_bitstream_status = + MemoryMapFile(kTestDataBase / "xc7-configuration.debug.bit"); + ASSERT_TRUE(debug_bitstream_status.ok()) << debug_bitstream_status.status(); + auto &debug_bitstream = debug_bitstream_status.value(); + CHECK(debug_bitstream); + + auto debug_reader = + BitstreamReader::InitWithBytes(debug_bitstream->AsBytesView()); + CHECK(debug_reader.has_value()); + auto debug_configuration = + Configuration::InitWithPackets(*part, *debug_reader); + CHECK(debug_configuration.has_value()); + + absl::StatusOr> normal_bitstream_status = + MemoryMapFile(kTestDataBase / "xc7-configuration.bit"); + ASSERT_TRUE(normal_bitstream_status.ok()) << normal_bitstream_status.status(); + auto &normal_bitstream = normal_bitstream_status.value(); + ASSERT_TRUE(normal_bitstream); + + auto normal_reader = + BitstreamReader::InitWithBytes(normal_bitstream->AsBytesView()); + CHECK(normal_reader.has_value()); + auto normal_configuration = + Configuration::InitWithPackets(*part, *normal_reader); + CHECK(normal_configuration.has_value()); + for (const auto &debug_frame : debug_configuration->frames()) { + const 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 (const 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"; + } + } +} template T Fill(typename T::value_type value) { @@ -325,13 +342,11 @@ TEST(XC7ConfigurationTest, CheckForPaddingFrames) { auto test_config = Configuration::InitWithPackets(*test_part, packets); - ASSERT_TRUE(test_config); - // NOLINTBEGIN(bugprone-unchecked-optional-access) + CHECK(test_config.has_value()); ASSERT_EQ(test_config->frames().size(), 5); for (const auto &frame : test_config->frames()) { EXPECT_EQ(frame.second, frames.GetFrames().at(frame.first)); } - // NOLINTEND(bugprone-unchecked-optional-access) } } // namespace } // namespace xilinx diff --git a/fpga/xilinx/configuration.h b/fpga/xilinx/configuration.h index 4214d1a..c87de5b 100644 --- a/fpga/xilinx/configuration.h +++ b/fpga/xilinx/configuration.h @@ -14,10 +14,10 @@ #include #include #include -#include #include #include +#include "absl/container/btree_map.h" #include "absl/types/optional.h" #include "absl/types/span.h" #include "fpga/xilinx/arch-types.h" @@ -38,7 +38,7 @@ class Configuration { static constexpr auto kWordsPerFrame = std::tuple_size_v; public: - using FrameMap = std::map>; + using FrameMap = absl::btree_map>; using PacketData = std::vector; // Returns a configuration, i.e. collection of frame addresses @@ -59,7 +59,7 @@ class Configuration { // Returns the payload for a type 2 packet // which allows for bigger payload compared to type 1. static PacketData CreateType2ConfigurationPacketData( - const std::map &frames, + const absl::btree_map &frames, std::optional &part) { PacketData packet_data; // Certain configuration frames blocks are separated by Zero Frames, @@ -83,7 +83,8 @@ class Configuration { return packet_data; } - Configuration(const Part &part, std::map &frames) + Configuration(const Part &part, + absl::btree_map &frames) : part_(part) { for (auto &frame : frames) { frames_[frame.first] = absl::Span(frame.second); diff --git a/fpga/xilinx/frames.h b/fpga/xilinx/frames.h index 151578e..6eede3c 100644 --- a/fpga/xilinx/frames.h +++ b/fpga/xilinx/frames.h @@ -10,7 +10,6 @@ #ifndef FPGA_XILINX_FRAMES_H #define FPGA_XILINX_FRAMES_H -#include #include #include "fpga/xilinx/arch-types.h" @@ -32,15 +31,19 @@ class Frames { data_.insert(address, words); } + Frames() = default; + explicit Frames(absl::btree_map data) + : data_(std::move(data)) {} + // Adds empty frames that are present in the tilegrid of a specific part // but are missing in the current frames container. void AddMissingFrames(const std::optional &part); // Returns the map with frame addresses and corresponding data - std::map &GetFrames() { return data_; } + absl::btree_map &GetFrames() { return data_; } private: - std::map data_; + absl::btree_map data_; // Updates the ECC information in the frame void UpdateECC(FrameWords &words); diff --git a/fpga/xilinx/testdata/xc7-configuration-test.json b/fpga/xilinx/testdata/xc7-configuration-test.json new file mode 100644 index 0000000..468da0d --- /dev/null +++ b/fpga/xilinx/testdata/xc7-configuration-test.json @@ -0,0 +1,451 @@ +{ + "idcode": 56803475, + "global_clock_regions": { + "top": { + "rows": { + "0": { + "configuration_buses": { + "CLB_IO_CLK": { + "configuration_columns": { + "0": { + "frame_count": 42 + }, + "1": { + "frame_count": 30 + }, + "2": { + "frame_count": 36 + }, + "3": { + "frame_count": 36 + }, + "4": { + "frame_count": 36 + }, + "5": { + "frame_count": 36 + }, + "6": { + "frame_count": 28 + }, + "7": { + "frame_count": 36 + }, + "8": { + "frame_count": 36 + }, + "9": { + "frame_count": 28 + }, + "10": { + "frame_count": 36 + }, + "11": { + "frame_count": 36 + }, + "12": { + "frame_count": 36 + }, + "13": { + "frame_count": 36 + }, + "14": { + "frame_count": 36 + }, + "15": { + "frame_count": 36 + }, + "16": { + "frame_count": 36 + }, + "17": { + "frame_count": 36 + }, + "18": { + "frame_count": 30 + }, + "19": { + "frame_count": 36 + }, + "20": { + "frame_count": 36 + }, + "21": { + "frame_count": 36 + }, + "22": { + "frame_count": 36 + }, + "23": { + "frame_count": 30 + }, + "24": { + "frame_count": 36 + }, + "25": { + "frame_count": 36 + }, + "26": { + "frame_count": 36 + }, + "27": { + "frame_count": 36 + }, + "28": { + "frame_count": 36 + }, + "29": { + "frame_count": 36 + }, + "30": { + "frame_count": 28 + }, + "31": { + "frame_count": 36 + }, + "32": { + "frame_count": 36 + }, + "33": { + "frame_count": 36 + }, + "34": { + "frame_count": 28 + }, + "35": { + "frame_count": 36 + }, + "36": { + "frame_count": 36 + }, + "37": { + "frame_count": 28 + }, + "38": { + "frame_count": 36 + }, + "39": { + "frame_count": 36 + }, + "40": { + "frame_count": 36 + }, + "41": { + "frame_count": 36 + }, + "42": { + "frame_count": 30 + }, + "43": { + "frame_count": 42 + } + } + }, + "BLOCK_RAM": { + "configuration_columns": { + "0": { + "frame_count": 128 + }, + "1": { + "frame_count": 128 + }, + "2": { + "frame_count": 128 + } + } + } + } + }, + "1": { + "configuration_buses": { + "CLB_IO_CLK": { + "configuration_columns": { + "0": { + "frame_count": 42 + }, + "1": { + "frame_count": 30 + }, + "2": { + "frame_count": 36 + }, + "3": { + "frame_count": 36 + }, + "4": { + "frame_count": 36 + }, + "5": { + "frame_count": 36 + }, + "6": { + "frame_count": 28 + }, + "7": { + "frame_count": 36 + }, + "8": { + "frame_count": 36 + }, + "9": { + "frame_count": 28 + }, + "10": { + "frame_count": 36 + }, + "11": { + "frame_count": 36 + }, + "12": { + "frame_count": 36 + }, + "13": { + "frame_count": 36 + }, + "14": { + "frame_count": 36 + }, + "15": { + "frame_count": 36 + }, + "16": { + "frame_count": 36 + }, + "17": { + "frame_count": 36 + }, + "18": { + "frame_count": 30 + }, + "19": { + "frame_count": 36 + }, + "20": { + "frame_count": 36 + }, + "21": { + "frame_count": 36 + }, + "22": { + "frame_count": 36 + }, + "23": { + "frame_count": 30 + }, + "24": { + "frame_count": 36 + }, + "25": { + "frame_count": 36 + }, + "26": { + "frame_count": 36 + }, + "27": { + "frame_count": 36 + }, + "28": { + "frame_count": 36 + }, + "29": { + "frame_count": 36 + }, + "30": { + "frame_count": 28 + }, + "31": { + "frame_count": 36 + }, + "32": { + "frame_count": 36 + }, + "33": { + "frame_count": 36 + }, + "34": { + "frame_count": 28 + }, + "35": { + "frame_count": 36 + }, + "36": { + "frame_count": 36 + }, + "37": { + "frame_count": 32 + } + } + }, + "BLOCK_RAM": { + "configuration_columns": { + "0": { + "frame_count": 128 + }, + "1": { + "frame_count": 128 + } + } + } + } + } + } + }, + "bottom": { + "rows": { + "0": { + "configuration_buses": { + "CLB_IO_CLK": { + "configuration_columns": { + "0": { + "frame_count": 42 + }, + "1": { + "frame_count": 30 + }, + "2": { + "frame_count": 36 + }, + "3": { + "frame_count": 36 + }, + "4": { + "frame_count": 36 + }, + "5": { + "frame_count": 36 + }, + "6": { + "frame_count": 28 + }, + "7": { + "frame_count": 36 + }, + "8": { + "frame_count": 36 + }, + "9": { + "frame_count": 28 + }, + "10": { + "frame_count": 36 + }, + "11": { + "frame_count": 36 + }, + "12": { + "frame_count": 36 + }, + "13": { + "frame_count": 36 + }, + "14": { + "frame_count": 36 + }, + "15": { + "frame_count": 36 + }, + "16": { + "frame_count": 36 + }, + "17": { + "frame_count": 36 + }, + "18": { + "frame_count": 30 + }, + "19": { + "frame_count": 36 + }, + "20": { + "frame_count": 36 + }, + "21": { + "frame_count": 36 + }, + "22": { + "frame_count": 36 + }, + "23": { + "frame_count": 30 + }, + "24": { + "frame_count": 36 + }, + "25": { + "frame_count": 36 + }, + "26": { + "frame_count": 36 + }, + "27": { + "frame_count": 36 + }, + "28": { + "frame_count": 36 + }, + "29": { + "frame_count": 36 + }, + "30": { + "frame_count": 28 + }, + "31": { + "frame_count": 36 + }, + "32": { + "frame_count": 36 + }, + "33": { + "frame_count": 36 + }, + "34": { + "frame_count": 28 + }, + "35": { + "frame_count": 36 + }, + "36": { + "frame_count": 36 + }, + "37": { + "frame_count": 28 + }, + "38": { + "frame_count": 36 + }, + "39": { + "frame_count": 36 + }, + "40": { + "frame_count": 36 + }, + "41": { + "frame_count": 36 + }, + "42": { + "frame_count": 30 + }, + "43": { + "frame_count": 42 + } + } + }, + "BLOCK_RAM": { + "configuration_columns": { + "0": { + "frame_count": 128 + }, + "1": { + "frame_count": 128 + }, + "2": { + "frame_count": 128 + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/fpga/xilinx/testdata/xc7-configuration.bit b/fpga/xilinx/testdata/xc7-configuration.bit new file mode 100644 index 0000000..6ef4f5e Binary files /dev/null and b/fpga/xilinx/testdata/xc7-configuration.bit differ diff --git a/fpga/xilinx/testdata/xc7-configuration.debug.bit b/fpga/xilinx/testdata/xc7-configuration.debug.bit new file mode 100644 index 0000000..3dfd748 Binary files /dev/null and b/fpga/xilinx/testdata/xc7-configuration.debug.bit differ diff --git a/fpga/xilinx/testdata/xc7-configuration.perframecrc.bit b/fpga/xilinx/testdata/xc7-configuration.perframecrc.bit new file mode 100644 index 0000000..7179e2d Binary files /dev/null and b/fpga/xilinx/testdata/xc7-configuration.perframecrc.bit differ diff --git a/shell.nix b/shell.nix index 169175a..511b076 100644 --- a/shell.nix +++ b/shell.nix @@ -31,6 +31,9 @@ pkgs.mkShell { pprof perf_data_converter valgrind + + # FPGA utils. + openfpgaloader ]; CLANG_TIDY="${clang_for_tidy}/bin/clang-tidy";