diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77e176b..6b4869c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,12 +62,12 @@ jobs: - name: Install Dependencies run: | - sudo apt-get install clang-format-18 + sudo apt-get install clang-format-17 - name: Run formatting style check run: | - clang-format-18 --version - RUNNING_IN_CI=1 CLANG_FORMAT=clang-format-18 \ + clang-format-17 --version + RUNNING_IN_CI=1 CLANG_FORMAT=clang-format-17 \ scripts/run-clang-format.sh ClangTidy: diff --git a/MODULE.bazel b/MODULE.bazel index 794eab7..9138737 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,16 +16,7 @@ git_override( bazel_dep(name = "abseil-cpp", version = "20240722.0.bcr.2") bazel_dep(name = "googletest", version = "1.15.2") bazel_dep(name = "rapidjson", version = "1.1.0.bcr.20241007") - -## C/C++ deps from local registry. -bazel_dep(name = "prjxray", version = "0.0-583-t1e53270") -git_override( - module_name = "prjxray", - commit = "3a95169e555de315c6b4cb71249107972d944303", - patch_strip = 1, - patches = ["//bazel:prjxray-add-module-bazel.patch"], - remote = "https://github.com/f4pga/prjxray.git", -) +bazel_dep(name = "rules_license", version = "1.0.0") # compilation DB; build_cleaner bazel_dep(name = "bant", version = "0.1.14", dev_dependency = True) diff --git a/README.md b/README.md index 2c0fe97..d89e1e1 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,6 @@ or install in system directory that requires root-access: sudo install -D --strip bazel-bin/fpga/fpga-as /usr/local/bin/fpga-as ``` -[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 - # How it works ## Frames generation @@ -59,3 +55,7 @@ The next step is to locate the tile metadata in the FPGA fabric's `tilegrid.json * offset: `77` Using this metadata, you can search the segbits database for the specific feature. By matching the tile_type and the FASM feature name, you can identify the correct configuration bits in the tile type segbits file (`segbits_clblm_r.db`). In this case, you would look for the entry corresponding to CLBLM_R.SLICEM_X0.ALUT.INIT and find the entry for address `[34]`. The value 34_06 then provides the coordinates for the word index and the specific bit index to be set. + +[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 diff --git a/fpga/assembler.cc b/fpga/assembler.cc index 02a2178..4be92ad 100644 --- a/fpga/assembler.cc +++ b/fpga/assembler.cc @@ -430,7 +430,7 @@ int main(int argc, char *argv[]) { << '\n'; return EXIT_FAILURE; } - PrintFrames(frames, std::cout, false); + // Write bitstream return EXIT_SUCCESS; } diff --git a/fpga/database-parsers_test.cc b/fpga/database-parsers_test.cc index 896ebc6..ff787e8 100644 --- a/fpga/database-parsers_test.cc +++ b/fpga/database-parsers_test.cc @@ -10,7 +10,6 @@ #include "gtest/gtest.h" namespace fpga { - template void AbslStringify(Sink &sink, const TileFeature &e) { absl::Format(&sink, "(tile_feature=\"%s\", address=%d)", e.tile_feature, diff --git a/fpga/xilinx/BUILD b/fpga/xilinx/BUILD new file mode 100644 index 0000000..1a8b8ba --- /dev/null +++ b/fpga/xilinx/BUILD @@ -0,0 +1,148 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("@rules_license//rules:license.bzl", "license") + +package( + default_applicable_licenses = [":license"], + default_visibility = ["//:__subpackages__"], +) + +license( + name = "license", + package_name = "xilinx", + license_kind = "@rules_license//licenses/spdx:ISC", +) + +cc_library( + name = "big-endian-span", + hdrs = [ + "big-endian-span.h", + ], + deps = [ + "@abseil-cpp//absl/types:span", + ], +) + +cc_test( + name = "big-endian-span_test", + srcs = [ + "big-endian-span_test.cc", + ], + deps = [ + ":big-endian-span", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "bit-ops", + hdrs = [ + "bit-ops.h", + ], +) + +cc_test( + name = "bit-ops_test", + srcs = [ + "bit-ops_test.cc", + ], + deps = [ + ":bit-ops", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "configuration-packet", + hdrs = [ + "configuration-packet.h", + ], + deps = [ + "@abseil-cpp//absl/types:optional", + "@abseil-cpp//absl/types:span", + ], +) + +cc_library( + name = "arch-xc7-defs", + srcs = [ + "arch-xc7-defs.cc", + ], + hdrs = [ + "arch-xc7-defs.h", + ], +) + +cc_library( + name = "arch-xc7-configuration-packet", + srcs = [ + "arch-xc7-configuration-packet.cc", + ], + hdrs = [ + "arch-xc7-configuration-packet.h", + ], + deps = [ + ":arch-xc7-defs", + ":bit-ops", + ":configuration-packet", + "@abseil-cpp//absl/types:span", + ], +) + +cc_test( + name = "arch-xc7-configuration-packet_test", + srcs = [ + "arch-xc7-configuration-packet_test.cc", + ], + deps = [ + ":arch-xc7-configuration-packet", + ":arch-xc7-defs", + ":bit-ops", + "@abseil-cpp//absl/types:span", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "arch-xc7-frame-address", + srcs = [ + "arch-xc7-frame-address.cc", + ], + hdrs = [ + "arch-xc7-frame-address.h", + ], + deps = [ + ":arch-xc7-defs", + ":bit-ops", + ], +) + +cc_library( + name = "arch-xc7-part", + srcs = [ + "arch-xc7-part.cc", + ], + hdrs = [ + "arch-xc7-part.h", + ], + deps = [ + ":arch-xc7-defs", + ":arch-xc7-frame-address", + ], +) + +cc_test( + name = "arch-xc7-part_test", + srcs = [ + "arch-xc7-part_test.cc", + ], + deps = [ + ":arch-xc7-defs", + ":arch-xc7-frame-address", + ":arch-xc7-part", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) diff --git a/fpga/xilinx/README.md b/fpga/xilinx/README.md new file mode 100644 index 0000000..9fa0483 --- /dev/null +++ b/fpga/xilinx/README.md @@ -0,0 +1,5 @@ +# Xilinx Bitstream Generation + +Most of this code originates from [prjxray][prjxray]. It was imported into this repository to simplify type interfacing and eliminate the dependency on yaml-cpp, which requires exceptions to be enabled. + +[prjxray]: https://github.com/f4pga/prjxray/tree/faf9c774a340e39cf6802d009996ed6016e63521/lib diff --git a/fpga/xilinx/arch-xc7-configuration-packet.cc b/fpga/xilinx/arch-xc7-configuration-packet.cc new file mode 100644 index 0000000..a20ccb7 --- /dev/null +++ b/fpga/xilinx/arch-xc7-configuration-packet.cc @@ -0,0 +1,76 @@ +#include "fpga/xilinx/arch-xc7-configuration-packet.h" + +#include +#include + +#include "absl/types/span.h" +#include "fpga/xilinx/arch-xc7-defs.h" +#include "fpga/xilinx/bit-ops.h" +#include "fpga/xilinx/configuration-packet.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +ConfigurationPacket::ParseResult ConfigurationPacket::InitWithWordsImpl( + absl::Span words, const ConfigurationPacket *previous_packet) { + using ConfigurationRegister = ConfigurationRegister; + // Need at least one 32-bit word to have a valid packet header. + if (words.empty()) { + return {words, {}}; + } + const ConfigurationPacketType header_type = + static_cast(bit_field_get(words[0], 31, 29)); + switch (header_type) { + case ConfigurationPacketType::kNONE: + // Type 0 is emitted at the end of a configuration row + // when BITSTREAM.GENERAL.DEBUGBITSTREAM is set to YES. + // These seem to be padding that are interepreted as + // NOPs. Since Type 0 packets don't exist according to + // UG470 and they seem to be zero-filled, just consume + // the bytes without generating a packet. + return {words.subspan(1), + {{static_cast(header_type), + Opcode::kNOP, + ConfigurationRegister::kCRC, + {}}}}; + case ConfigurationPacketType::kTYPE1: { + const Opcode opcode = static_cast(bit_field_get(words[0], 28, 27)); + const ConfigurationRegister address = + static_cast(bit_field_get(words[0], 26, 13)); + const uint32_t data_word_count = bit_field_get(words[0], 10, 0); + + // If the full packet has not been received, return as + // though no valid packet was found. + if (data_word_count > words.size() - 1) { + return {words, {}}; + } + + return {words.subspan(data_word_count + 1), + {{static_cast(header_type), opcode, address, + words.subspan(1, data_word_count)}}}; + } + case ConfigurationPacketType::kTYPE2: { + std::optional packet; + const Opcode opcode = static_cast(bit_field_get(words[0], 28, 27)); + const uint32_t data_word_count = bit_field_get(words[0], 26, 0); + + // If the full packet has not been received, return as + // though no valid packet was found. + if (data_word_count > words.size() - 1) { + return {words, {}}; + } + + if (previous_packet) { + packet = ConfigurationPacket(static_cast(header_type), opcode, + previous_packet->address(), + words.subspan(1, data_word_count)); + } + + return {words.subspan(data_word_count + 1), packet}; + } + default: return {{}, {}}; + } +} +} // namespace xc7 +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/arch-xc7-configuration-packet.h b/fpga/xilinx/arch-xc7-configuration-packet.h new file mode 100644 index 0000000..85bc03b --- /dev/null +++ b/fpga/xilinx/arch-xc7-configuration-packet.h @@ -0,0 +1,45 @@ +/* + * 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_ARCH_XC7_CONFIGURATION_PACKET_H +#define FPGA_XILINX_ARCH_XC7_CONFIGURATION_PACKET_H + +#include +#include +#include + +#include "absl/types/span.h" +#include "fpga/xilinx/arch-xc7-defs.h" +#include "fpga/xilinx/configuration-packet.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +class ConfigurationPacket + : public ConfigurationPacketBase { + private: + using BaseType = + ConfigurationPacketBase; + + public: + using BaseType::BaseType; + + private: + friend BaseType; + static ParseResult InitWithWordsImpl( + absl::Span words, + const ConfigurationPacket *previous_packet = nullptr); +}; + +using ConfigurationPackage = std::vector>; +} // namespace xc7 +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_ARCH_XC7_CONFIGURATION_PACKET_H diff --git a/fpga/xilinx/arch-xc7-configuration-packet_test.cc b/fpga/xilinx/arch-xc7-configuration-packet_test.cc new file mode 100644 index 0000000..44875e9 --- /dev/null +++ b/fpga/xilinx/arch-xc7-configuration-packet_test.cc @@ -0,0 +1,114 @@ +/* + * 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/arch-xc7-configuration-packet.h" + +#include +#include + +#include "absl/types/span.h" +#include "fpga/xilinx/arch-xc7-defs.h" +#include "fpga/xilinx/bit-ops.h" +#include "gtest/gtest.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +constexpr uint32_t kType1NOP = bit_field_set(0, 31, 29, 0x1); + +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); +} + +TEST(ConfigPacket, InitWithZeroBytes) { + auto packet = ConfigurationPacket::InitWithWords({}); + + EXPECT_EQ(packet.first, absl::Span()); + EXPECT_FALSE(packet.second); +} + +TEST(ConfigPacket, InitWithType1Nop) { + std::vector words{kType1NOP}; + const absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kNOP); + EXPECT_EQ(packet.second->address(), ConfigurationRegister::kCRC); + EXPECT_EQ(packet.second->data(), absl::Span()); + // NOLINTEND(bugprone-unchecked-optional-access) +} + +TEST(ConfigPacket, InitWithType1Read) { + std::vector words{MakeType1(0x1, 0x2, 2), 0xAA, 0xBB}; + const absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kRead); + EXPECT_EQ(packet.second->address(), ConfigurationRegister::kFDRI); + EXPECT_EQ(packet.second->data(), word_span.subspan(1)); + // NOLINTEND(bugprone-unchecked-optional-access) +} + +TEST(ConfigPacket, InitWithType1Write) { + std::vector words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB}; + const absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kWrite); + EXPECT_EQ(packet.second->address(), ConfigurationRegister::kFDRO); + EXPECT_EQ(packet.second->data(), word_span.subspan(1)); + // NOLINTEND(bugprone-unchecked-optional-access) +} + +TEST(ConfigPacket, InitWithType2WithoutPreviousPacketFails) { + std::vector words{MakeType2(0x01, 12)}; + const absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span); + EXPECT_EQ(packet.first, words); + EXPECT_FALSE(packet.second); +} + +TEST(ConfigPacket, InitWithType2WithPreviousPacket) { + const ConfigurationPacket previous_packet( + static_cast(0x1), ConfigurationPacket::Opcode::kRead, + ConfigurationRegister::kMFWR, absl::Span()); + std::vector words{ + MakeType2(0x01, 12), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + const absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords(word_span, &previous_packet); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kRead); + EXPECT_EQ(packet.second->address(), ConfigurationRegister::kMFWR); + EXPECT_EQ(packet.second->data(), word_span.subspan(1)); + // NOLINTEND(bugprone-unchecked-optional-access) +} +} // namespace xc7 +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/arch-xc7-defs.cc b/fpga/xilinx/arch-xc7-defs.cc new file mode 100644 index 0000000..c0cc93e --- /dev/null +++ b/fpga/xilinx/arch-xc7-defs.cc @@ -0,0 +1,45 @@ +#include "fpga/xilinx/arch-xc7-defs.h" + +#include + +namespace fpga { +namespace xilinx { +namespace xc7 { +std::ostream &operator<<(std::ostream &o, const ConfigurationRegister &value) { + switch (value) { + case ConfigurationRegister::kCRC: return o << "CRC"; + case ConfigurationRegister::kFAR: return o << "Frame Address"; + case ConfigurationRegister::kFDRI: return o << "Frame Data Input"; + case ConfigurationRegister::kFDRO: return o << "Frame Data Output"; + case ConfigurationRegister::kCMD: return o << "Command"; + case ConfigurationRegister::kCTL0: return o << "Control 0"; + case ConfigurationRegister::kMASK: return o << "Mask for CTL0 and CTL1"; + case ConfigurationRegister::kSTAT: return o << "Status"; + case ConfigurationRegister::kLOUT: return o << "Legacy Output"; + case ConfigurationRegister::kCOR0: return o << "Configuration Option 0"; + case ConfigurationRegister::kMFWR: return o << "Multiple Frame Write"; + case ConfigurationRegister::kCBC: return o << "Initial CBC Value"; + case ConfigurationRegister::kIDCODE: return o << "Device ID"; + case ConfigurationRegister::kAXSS: return o << "User Access"; + case ConfigurationRegister::kCOR1: return o << "Configuration Option 1"; + case ConfigurationRegister::kWBSTAR: return o << "Warm Boot Start Address"; + case ConfigurationRegister::kTIMER: return o << "Watchdog Timer"; + case ConfigurationRegister::kBOOTSTS: return o << "Boot History Status"; + case ConfigurationRegister::kCTL1: return o << "Control 1"; + case ConfigurationRegister::kBSPI: + return o << "BPI/SPI Configuration Options"; + default: return o << "Unknown"; + } +} + +std::ostream &operator<<(std::ostream &o, BlockType value) { + switch (value) { + case BlockType::kCLBIOCLK: o << "CLB/IO/CLK"; break; + case BlockType::kBlockRam: o << "Block RAM"; break; + case BlockType::kCFGCLB: o << "Config CLB"; break; + } + return o; +} +} // namespace xc7 +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/arch-xc7-defs.h b/fpga/xilinx/arch-xc7-defs.h new file mode 100644 index 0000000..b976202 --- /dev/null +++ b/fpga/xilinx/arch-xc7-defs.h @@ -0,0 +1,87 @@ +/* + * 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_ARCH_XC7_DEFS_H +#define FPGA_XILINX_ARCH_XC7_DEFS_H + +#include +#include + +namespace fpga { +namespace xilinx { +namespace xc7 { +// Series-7 configuration register addresses +// according to UG470, pg. 109 +enum class ConfigurationRegister : unsigned int { + kCRC = 0x00, + kFAR = 0x01, + kFDRI = 0x02, + kFDRO = 0x03, + kCMD = 0x04, + kCTL0 = 0x05, + kMASK = 0x06, + kSTAT = 0x07, + kLOUT = 0x08, + kCOR0 = 0x09, + kMFWR = 0x0a, + kCBC = 0x0b, + kIDCODE = 0x0c, + kAXSS = 0x0d, + kCOR1 = 0x0e, + kWBSTAR = 0x10, + kTIMER = 0x11, + kUNKNOWN = 0x13, + kBOOTSTS = 0x16, + kCTL1 = 0x18, + kBSPI = 0x1F, +}; + +std::ostream &operator<<(std::ostream &o, const ConfigurationRegister &value); + +enum class BlockType : unsigned int { + kCLBIOCLK = 0x0, + kBlockRam = 0x1, + kCFGCLB = 0x2, + /* reserved = 0x3, */ +}; + +std::ostream &operator<<(std::ostream &o, BlockType value); + +enum class Architecture { + kBase, + kUltrascale, + kUltrascalePlus, +}; + +using word_t = uint32_t; + +template +struct frame_words_count; + +template <> +struct frame_words_count { + static constexpr int value = 101; +}; + +template <> +struct frame_words_count { + static constexpr int value = 123; +}; + +template <> +struct frame_words_count { + static constexpr int value = 93; +}; + +template +using frame_words_count_v = frame_words_count::value; +} // namespace xc7 +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_ARCH_XC7_DEFS_H diff --git a/fpga/xilinx/arch-xc7-frame-address.cc b/fpga/xilinx/arch-xc7-frame-address.cc new file mode 100644 index 0000000..edff01f --- /dev/null +++ b/fpga/xilinx/arch-xc7-frame-address.cc @@ -0,0 +1,49 @@ +#include "fpga/xilinx/arch-xc7-frame-address.h" + +#include +#include +#include +#include + +#include "fpga/xilinx/arch-xc7-defs.h" +#include "fpga/xilinx/bit-ops.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +FrameAddress::FrameAddress(BlockType block_type, bool is_bottom_half_rows, + uint8_t row, uint16_t column, uint8_t minor) { + address_ = bit_field_set(0, 25, 23, block_type); + address_ = bit_field_set(address_, 22, 22, is_bottom_half_rows); + address_ = bit_field_set(address_, 21, 17, row); + address_ = bit_field_set(address_, 16, 7, column); + address_ = bit_field_set(address_, 6, 0, minor); +} + +BlockType FrameAddress::block_type() const { + return static_cast(bit_field_get(address_, 25, 23)); +} + +bool FrameAddress::is_bottom_half_rows() const { + return bit_field_get(address_, 22, 22); +} + +uint8_t FrameAddress::row() const { return bit_field_get(address_, 21, 17); } + +uint16_t FrameAddress::column() const { return bit_field_get(address_, 16, 7); } + +uint8_t FrameAddress::minor() const { return bit_field_get(address_, 6, 0); } + +std::ostream &operator<<(std::ostream &o, const FrameAddress &addr) { + o << "[" << std::hex << std::showbase << std::setw(10) + << static_cast(addr) << "] " + << (addr.is_bottom_half_rows() ? "BOTTOM" : "TOP") + << " Row=" << std::setw(2) << std::dec + << static_cast(addr.row()) << " Column=" << std::setw(2) + << std::dec << addr.column() << " Minor=" << std::setw(2) << std::dec + << static_cast(addr.minor()) << " Type=" << addr.block_type(); + return o; +} +} // namespace xc7 +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/arch-xc7-frame-address.h b/fpga/xilinx/arch-xc7-frame-address.h new file mode 100644 index 0000000..61986df --- /dev/null +++ b/fpga/xilinx/arch-xc7-frame-address.h @@ -0,0 +1,48 @@ +/* + * 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_ARCH_XC7_FRAME_ADDRESS_H +#define FPGA_XILINX_ARCH_XC7_FRAME_ADDRESS_H + +#include +#include + +#include "fpga/xilinx/arch-xc7-defs.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +class FrameAddress { + public: + FrameAddress() : address_(0) {} + explicit FrameAddress(uint32_t address) : address_(address){}; + FrameAddress(BlockType block_type, bool is_bottom_half_rows, uint8_t row, + uint16_t column, uint8_t minor); + + explicit operator uint32_t() const { return address_; } + + BlockType block_type() const; + bool is_bottom_half_rows() const; + uint8_t row() const; + uint16_t column() const; + uint8_t minor() const; + + private: + uint32_t address_; +}; + +inline bool operator==(const FrameAddress &lhs, const FrameAddress &rhs) { + return static_cast(lhs) == static_cast(rhs); +} + +std::ostream &operator<<(std::ostream &o, const FrameAddress &addr); +} // namespace xc7 +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_ARCH_XC7_FRAME_ADDRESS_H diff --git a/fpga/xilinx/arch-xc7-part.cc b/fpga/xilinx/arch-xc7-part.cc new file mode 100644 index 0000000..1d018cb --- /dev/null +++ b/fpga/xilinx/arch-xc7-part.cc @@ -0,0 +1,188 @@ +#include "fpga/xilinx/arch-xc7-part.h" + +#include +#include + +#include "fpga/xilinx/arch-xc7-defs.h" +#include "fpga/xilinx/arch-xc7-frame-address.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +bool ConfigurationColumn::IsValidFrameAddress(FrameAddress address) const { + return address.minor() < frame_count_; +} + +std::optional ConfigurationColumn::GetNextFrameAddress( + FrameAddress address) const { + if (!IsValidFrameAddress(address)) { + return {}; + } + + if (static_cast(address.minor() + 1) < frame_count_) { + return FrameAddress(static_cast(address) + 1); + } + + // Next address is not in this column. + return {}; +} + +bool ConfigurationBus::IsValidFrameAddress(FrameAddress address) const { + auto addr_column = configuration_columns_.find(address.column()); + if (addr_column == configuration_columns_.end()) { + return false; + } + + return addr_column->second.IsValidFrameAddress(address); +} + +std::optional ConfigurationBus::GetNextFrameAddress( + FrameAddress address) const { + // Find the column for the current address. + auto addr_column = configuration_columns_.find(address.column()); + + // If the current address isn't in a known column, no way to know the + // next address. + if (addr_column == configuration_columns_.end()) { + return {}; + } + + // Ask the column for the next address. + std::optional next_address = + addr_column->second.GetNextFrameAddress(address); + if (next_address) { + return next_address; + } + + // The current column doesn't know what the next address is. Assume + // that the next valid address is the beginning of the next column. + if (++addr_column != configuration_columns_.end()) { + auto next_address = + FrameAddress(address.block_type(), address.is_bottom_half_rows(), + address.row(), addr_column->first, 0); + if (addr_column->second.IsValidFrameAddress(next_address)) { + return next_address; + } + } + + // Not in this bus. + return {}; +} + +bool Row::IsValidFrameAddress(FrameAddress address) const { + auto addr_bus = configuration_buses_.find(address.block_type()); + if (addr_bus == configuration_buses_.end()) { + return false; + } + return addr_bus->second.IsValidFrameAddress(address); +} + +std::optional Row::GetNextFrameAddress( + FrameAddress address) const { + // Find the bus for the current address. + auto addr_bus = configuration_buses_.find(address.block_type()); + + // If the current address isn't in a known bus, no way to know the next. + if (addr_bus == configuration_buses_.end()) { + return {}; + } + + // Ask the bus for the next address. + std::optional next_address = + addr_bus->second.GetNextFrameAddress(address); + if (next_address) { + return next_address; + } + + // The current bus doesn't know what the next address is. Rows come next + // in frame address numerical order so punt back to the caller to figure + // it out. + return {}; +} + +bool GlobalClockRegion::IsValidFrameAddress(FrameAddress address) const { + auto addr_row = rows_.find(address.row()); + if (addr_row == rows_.end()) { + return false; + } + return addr_row->second.IsValidFrameAddress(address); +} + +std::optional GlobalClockRegion::GetNextFrameAddress( + FrameAddress address) const { + // Find the row for the current address. + auto addr_row = rows_.find(address.row()); + + // If the current address isn't in a known row, no way to know the next. + if (addr_row == rows_.end()) { + return {}; + } + + // Ask the row for the next address. + std::optional next_address = + addr_row->second.GetNextFrameAddress(address); + if (next_address) { + return next_address; + } + + // The current row doesn't know what the next address is. Assume that + // the next valid address is the beginning of the next row. + if (++addr_row != rows_.end()) { + auto next_address = + FrameAddress(address.block_type(), address.is_bottom_half_rows(), + addr_row->first, 0, 0); + if (addr_row->second.IsValidFrameAddress(next_address)) { + return next_address; + } + } + + // Must be in a different global clock region. + return {}; +} + +bool Part::IsValidFrameAddress(FrameAddress address) const { + if (address.is_bottom_half_rows()) { + return bottom_region_.IsValidFrameAddress(address); + } + return top_region_.IsValidFrameAddress(address); +} + +std::optional Part::GetNextFrameAddress( + FrameAddress address) const { + // Ask the current global clock region first. + std::optional next_address = + (address.is_bottom_half_rows() ? bottom_region_.GetNextFrameAddress(address) + : top_region_.GetNextFrameAddress(address)); + if (next_address) { + return next_address; + } + + // If the current address is in the top region, the bottom region is + // next numerically. + if (!address.is_bottom_half_rows()) { + next_address = FrameAddress(address.block_type(), true, 0, 0, 0); + if (bottom_region_.IsValidFrameAddress(*next_address)) { + return next_address; + } + } + + // Block types are next numerically. + if (address.block_type() < BlockType::kBlockRam) { + next_address = FrameAddress(BlockType::kBlockRam, false, 0, 0, 0); + if (IsValidFrameAddress(*next_address)) { + return next_address; + } + } + + if (address.block_type() < BlockType::kCFGCLB) { + next_address = FrameAddress(BlockType::kCFGCLB, false, 0, 0, 0); + if (IsValidFrameAddress(*next_address)) { + return next_address; + } + } + return {}; +} + +} // namespace xc7 +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/arch-xc7-part.h b/fpga/xilinx/arch-xc7-part.h new file mode 100644 index 0000000..21257e9 --- /dev/null +++ b/fpga/xilinx/arch-xc7-part.h @@ -0,0 +1,269 @@ +/* + * 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_ARCH_XC7_PART_H +#define FPGA_XILINX_ARCH_XC7_PART_H + +#include +#include +#include +#include +#include +#include +#include + +#include "fpga/xilinx/arch-xc7-defs.h" +#include "fpga/xilinx/arch-xc7-frame-address.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +// ConfigurationColumn represents an endpoint on a ConfigurationBus. +class ConfigurationColumn { + public: + ConfigurationColumn() = default; + explicit ConfigurationColumn(unsigned int frame_count) + : frame_count_(frame_count) {} + + // Returns a ConfigurationColumn that describes a continguous range of + // minor addresses that encompasses the given + // FrameAddresses. The provided addresses must only + // differ only by their minor addresses. + template + ConfigurationColumn(T first, T last); + + // Returns true if the minor field of the address is within the valid + // range of this column. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next address in numerical order. If the next address + // would be outside this column, return no object. + std::optional GetNextFrameAddress(FrameAddress address) const; + + private: + unsigned int frame_count_; +}; + +template +ConfigurationColumn::ConfigurationColumn(T first, T last) { + assert(std::all_of(first, last, [&](const typename T::value_type &addr) { + return (addr.block_type() == first->block_type() && + addr.is_bottom_half_rows() == first->is_bottom_half_rows() && + addr.row() == first->row() && addr.column() == first->column()); + })); + + auto max_minor = std::max_element( + first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { + return lhs.minor() < rhs.minor(); + }); + + frame_count_ = max_minor->minor() + 1; +} + +// ConfigurationBus represents a bus for sending frames to a specific BlockType +// within a Row. An instance of ConfigurationBus will contain one or more +// ConfigurationColumns. +class ConfigurationBus { + public: + ConfigurationBus() = default; + + // Constructs a ConfigurationBus from iterators yielding + // FrameAddresses. The frame address need not be contiguous or sorted + // but they must all have the same block type, row half, and row + // address components. + template + ConfigurationBus(T first, T last); + + // Returns true if the provided address falls into a valid segment of + // the address range on this bus. Only the column and minor components + // of the address are considered as all other components are outside + // the scope of a bus. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next valid address on the bus in numerically increasing + // order. If the next address would fall outside this bus, no object is + // returned. + std::optional GetNextFrameAddress(FrameAddress address) const; + + private: + std::map configuration_columns_; +}; + +template +ConfigurationBus::ConfigurationBus(T first, T last) { + assert(std::all_of(first, last, [&](const typename T::value_type &addr) { + return (addr.block_type() == first->block_type() && + addr.is_bottom_half_rows() == first->is_bottom_half_rows() && + addr.row() == first->row()); + })); + + std::sort(first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { + return lhs.column() < rhs.column(); + }); + + for (auto col_first = first; col_first != last;) { + auto col_last = + std::upper_bound(col_first, last, col_first->column(), + [](const unsigned int &lhs, const FrameAddress &rhs) { + return lhs < rhs.column(); + }); + + configuration_columns_.emplace(col_first->column(), + ConfigurationColumn(col_first, col_last)); + col_first = col_last; + } +} + +class Row { + public: + Row() = default; + + // Construct a row from a range of iterators that yield FrameAddresses. + // The addresses may be noncontinguous and/or unsorted but all must + // share the same row half and row components. + template + Row(T first, T last); + + // Returns true if the provided address falls within a valid range + // attributed to this row. Only the block type, column, and minor + // address components are considerd as the remaining components are + // outside the scope of a row. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next numerically increasing address within the Row. If + // the next address would fall outside the Row, no object is returned. + // If the next address would cross from one block type to another, no + // object is returned as other rows of the same block type come before + // other block types numerically. + std::optional GetNextFrameAddress(FrameAddress address) const; + + private: + std::map configuration_buses_; +}; + +template +Row::Row(T first, T last) { + assert(std::all_of(first, last, [&](const typename T::value_type &addr) { + return (addr.is_bottom_half_rows() == first->is_bottom_half_rows() && + addr.row() == first->row()); + })); + + std::sort(first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { + return lhs.block_type() < rhs.block_type(); + }); + + for (auto bus_first = first; bus_first != last;) { + auto bus_last = + std::upper_bound(bus_first, last, bus_first->block_type(), + [](const BlockType &lhs, const FrameAddress &rhs) { + return lhs < rhs.block_type(); + }); + + configuration_buses_.emplace( + bus_first->block_type(), + std::move(ConfigurationBus(bus_first, bus_last))); + bus_first = bus_last; + } +} + +// GlobalClockRegion represents all the resources associated with a single +// global clock buffer (BUFG) tile. In 7-Series FPGAs, there are two BUFG +// tiles that divide the chip into top and bottom "halves". Each half may +// contains any number of rows, buses, and columns. +class GlobalClockRegion { + public: + GlobalClockRegion() = default; + + // Construct a GlobalClockRegion from iterators that yield + // FrameAddresses which are known to be valid. The addresses may be + // noncontinguous and/or unordered but they must share the same row + // half address component. + template + GlobalClockRegion(T first, T last); + + // Returns true if the address falls within a valid range inside the + // global clock region. The row half address component is ignored as it + // is outside the context of a global clock region. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next numerically increasing address known within this + // global clock region. If the next address would fall outside this + // global clock region, no address is returned. If the next address + // would jump to a different block type, no address is returned as the + // same block type in other global clock regions come numerically + // before other block types. + std::optional GetNextFrameAddress(FrameAddress address) const; + + private: + std::map rows_; +}; + +template +GlobalClockRegion::GlobalClockRegion(T first, T last) { + assert(std::all_of(first, last, [&](const typename T::value_type &addr) { + return addr.is_bottom_half_rows() == first->is_bottom_half_rows(); + })); + + std::sort(first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { + return lhs.row() < rhs.row(); + }); + + for (auto row_first = first; row_first != last;) { + auto row_last = + std::upper_bound(row_first, last, row_first->row(), + [](const uint8_t &lhs, const FrameAddress &rhs) { + return lhs < rhs.row(); + }); + + rows_.emplace(row_first->row(), std::move(Row(row_first, row_last))); + row_first = row_last; + } +} + +class Part { + public: + constexpr static uint32_t kInvalidIdcode = 0; + + static std::optional FromFile(const std::string &path); + + // Constructs an invalid part with a zero IDCODE. Required for YAML + // conversion but shouldn't be used otherwise. + Part() : idcode_(kInvalidIdcode) {} + + template + Part(uint32_t idcode, T collection) + : Part(idcode, std::begin(collection), std::end(collection)) {} + + template + Part(uint32_t idcode, T first, T last); + + uint32_t idcode() const { return idcode_; } + + bool IsValidFrameAddress(FrameAddress address) const; + + std::optional GetNextFrameAddress(FrameAddress address) const; + + private: + uint32_t idcode_; + GlobalClockRegion top_region_; + GlobalClockRegion bottom_region_; +}; + +template +Part::Part(uint32_t idcode, T first, T last) : idcode_(idcode) { + auto first_of_top = std::partition(first, last, [](const FrameAddress &addr) { + return addr.is_bottom_half_rows(); + }); + top_region_ = GlobalClockRegion(first_of_top, last); + bottom_region_ = GlobalClockRegion(first, first_of_top); +} +} // namespace xc7 +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_ARCH_XC7_PART_H diff --git a/fpga/xilinx/arch-xc7-part_test.cc b/fpga/xilinx/arch-xc7-part_test.cc new file mode 100644 index 0000000..930cf0b --- /dev/null +++ b/fpga/xilinx/arch-xc7-part_test.cc @@ -0,0 +1,394 @@ +#include "fpga/xilinx/arch-xc7-part.h" + +#include +#include + +#include "fpga/xilinx/arch-xc7-defs.h" +#include "fpga/xilinx/arch-xc7-frame-address.h" +#include "gtest/gtest.h" + +namespace fpga { +namespace xilinx { +namespace xc7 { +namespace { +TEST(ConfigurationColumnTest, IsValidFrameAddress) { + const ConfigurationColumn column(10); + + // Inside this column. + EXPECT_TRUE(column.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 2, 3))); + // Past this column's frame width. + EXPECT_FALSE(column.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 2, 10))); +} + +TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNextAddressInColumn) { + const ConfigurationColumn column(10); + + std::optional next_address = column.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 2, 3)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 1, 2, 4)); +} + +TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingAtEndOfColumn) { + const ConfigurationColumn column(10); + + EXPECT_FALSE(column.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 2, 9))); +} + +TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingOutsideColumn) { + const ConfigurationColumn column(10); + + // Just past last frame in column. + EXPECT_FALSE(column.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 2, 10))); +} + +TEST(ConfigurationBusTest, IsValidFrameAddress) { + std::vector addresses; + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 1, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 1, 1); + + const ConfigurationBus bus(addresses.begin(), addresses.end()); + + EXPECT_TRUE(bus.IsValidFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 0))); + EXPECT_TRUE(bus.IsValidFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 1, 1))); + + EXPECT_FALSE(bus.IsValidFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 2))); +} + +TEST(ConfigurationBusTest, GetNextFrameAddressYieldNextAddressInBus) { + std::vector addresses; + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 1, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 1, 1); + + const ConfigurationBus bus(addresses.begin(), addresses.end()); + + auto next_address = + bus.GetNextFrameAddress(FrameAddress(BlockType::kBlockRam, false, 0, 0, 0)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kBlockRam, false, 0, 0, 1)); + + next_address = + bus.GetNextFrameAddress(FrameAddress(BlockType::kBlockRam, false, 0, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kBlockRam, false, 0, 1, 0)); +} + +TEST(ConfigurationBusTest, GetNextFrameAddressYieldNothingAtEndOfBus) { + std::vector addresses; + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 1, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 1, 1); + + const ConfigurationBus bus(addresses.begin(), addresses.end()); + + EXPECT_FALSE(bus.GetNextFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 1, 1))); +} + +TEST(RowTest, IsValidFrameAddress) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + + const Row row(addresses.begin(), addresses.end()); + + EXPECT_TRUE(row.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 0))); + EXPECT_TRUE(row.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 0))); + EXPECT_TRUE(row.IsValidFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 2))); + + EXPECT_FALSE(row.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 2))); + EXPECT_FALSE(row.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 2, 0))); +} + +TEST(RowTest, GetNextFrameAddressYieldNextAddressInRow) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + + const Row row(addresses.begin(), addresses.end()); + + auto next_address = + row.GetNextFrameAddress(FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 0)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 1)); + + next_address = + row.GetNextFrameAddress(FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 0)); + + // Rows have unique behavior for GetNextFrameAddress() at the end of a + // bus. Since the addresses need to be returned in numerically + // increasing order, all of the rows need to be returned before moving + // to a different bus. That means that Row::GetNextFrameAddress() needs + // to return no object at the end of a bus and let the caller use that + // as a signal to try the next row. + next_address = + row.GetNextFrameAddress(FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 1)); + EXPECT_FALSE(next_address); + + next_address = + row.GetNextFrameAddress(FrameAddress(BlockType::kBlockRam, false, 0, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kBlockRam, false, 0, 0, 2)); + + next_address = + row.GetNextFrameAddress(FrameAddress(BlockType::kBlockRam, false, 0, 0, 2)); + EXPECT_FALSE(next_address); +} + +TEST(RowTest, GetNextFrameAddressYieldNothingAtEndOfRow) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + + const Row row(addresses.begin(), addresses.end()); + + EXPECT_FALSE(row.GetNextFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 2))); +} + +TEST(GlobalClockRegionTest, IsValidFrameAddress) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 1); + + const GlobalClockRegion global_clock_region(addresses.begin(), + addresses.end()); + + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 0))); + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 0))); + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 2))); + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 0, 0))); + + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 2))); + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 2, 0))); + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 2, 0, 0))); + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + FrameAddress(BlockType::kCFGCLB, false, 0, 0, 2))); +} + +TEST(GlobalClockRegionTest, + GetNextFrameAddressYieldNextAddressInGlobalClockRegion) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 1); + + const GlobalClockRegion global_clock_region(addresses.begin(), + addresses.end()); + + auto next_address = global_clock_region.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 0)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 1)); + + next_address = global_clock_region.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 0)); + + next_address = global_clock_region.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 1, 0, 0)); + + next_address = global_clock_region.GetNextFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kBlockRam, false, 0, 0, 2)); +} + +TEST(GlobalClockRegionTest, + GetNextFrameAddressYieldNothingAtEndOfGlobalClockRegion) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 1); + + const GlobalClockRegion global_clock_region(addresses.begin(), + addresses.end()); + + EXPECT_FALSE(global_clock_region.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 0, 1))); + EXPECT_FALSE(global_clock_region.GetNextFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 2))); +} + +TEST(PartTest, IsValidFrameAddress) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, true, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, true, 0, 0, 1); + + const Part part(0x1234, addresses.begin(), addresses.end()); + + EXPECT_TRUE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 0))); + EXPECT_TRUE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 0))); + EXPECT_TRUE(part.IsValidFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 2))); + EXPECT_TRUE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 0, 0))); + EXPECT_TRUE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, true, 0, 0, 0))); + + EXPECT_FALSE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 2))); + EXPECT_FALSE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 2, 0))); + EXPECT_FALSE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 2, 0, 0))); + EXPECT_FALSE( + part.IsValidFrameAddress(FrameAddress(BlockType::kCFGCLB, false, 0, 0, 2))); + EXPECT_FALSE(part.IsValidFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, true, 0, 1, 0))); +} + +TEST(PartTest, GetNextFrameAddressYieldNextAddressInPart) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, true, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, true, 0, 0, 1); + + const Part part(0x1234, addresses.begin(), addresses.end()); + + auto next_address = part.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 0)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 1)); + + next_address = part.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 0)); + + next_address = part.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 0, 1, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, false, 1, 0, 0)); + + next_address = part.GetNextFrameAddress( + FrameAddress(BlockType::kCLBIOCLK, false, 1, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kCLBIOCLK, true, 0, 0, 0)); + + next_address = + part.GetNextFrameAddress(FrameAddress(BlockType::kCLBIOCLK, true, 0, 0, 1)); + ASSERT_TRUE(next_address); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + EXPECT_EQ(*next_address, FrameAddress(BlockType::kBlockRam, false, 0, 0, 0)); +} + +TEST(PartTest, GetNextFrameAddressYieldNothingAtEndOfPart) { + std::vector addresses; + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 0, 1, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 0); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 1); + addresses.emplace_back(BlockType::kBlockRam, false, 0, 0, 2); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, false, 1, 0, 1); + addresses.emplace_back(BlockType::kCLBIOCLK, true, 0, 0, 0); + addresses.emplace_back(BlockType::kCLBIOCLK, true, 0, 0, 1); + + const Part part(0x1234, addresses.begin(), addresses.end()); + + EXPECT_FALSE(part.GetNextFrameAddress( + FrameAddress(BlockType::kBlockRam, false, 0, 0, 2))); +} +} // namespace +} // namespace xc7 +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/big-endian-span.h b/fpga/xilinx/big-endian-span.h new file mode 100644 index 0000000..e851bd4 --- /dev/null +++ b/fpga/xilinx/big-endian-span.h @@ -0,0 +1,121 @@ +/* + * 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_BIG_ENDIAN_SPAN +#define FPGA_XILINX_BIG_ENDIAN_SPAN + +#include +#include +#include + +#include "absl/types/span.h" + +namespace fpga { +namespace xilinx { +template +class BigEndianSpan { + public: + constexpr static size_t kBytesPerElement = sizeof(WordType); + using byte_type = ByteType; + using word_type = WordType; + using size_type = std::size_t; + class value_type { + public: + explicit operator WordType() const { + WordType word = 0; + for (size_t ii = 0; ii < kBytesPerElement; ++ii) { + word |= (static_cast(bytes_[ii]) + << ((kBytesPerElement - 1 - ii) * 8)); + } + return word; + } + + value_type &operator=(WordType word) { + for (size_t ii = 0; ii < kBytesPerElement; ++ii) { + bytes_[ii] = ((word >> ((kBytesPerElement - 1 - ii) * 8)) & 0xFF); + } + return *this; + } + + protected: + friend class BigEndianSpan; + + explicit value_type(absl::Span bytes) : bytes_(bytes){}; + + private: + absl::Span bytes_; + }; + + class iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = BigEndianSpan::value_type; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + value_type operator*() const { return value_type(bytes_); } + + bool operator==(const iterator &other) const { + return bytes_ == other.bytes_; + } + + bool operator!=(const iterator &other) const { + return bytes_ != other.bytes_; + } + + iterator &operator++() { + bytes_ = bytes_.subspan(kBytesPerElement); + return *this; + } + + protected: + friend class BigEndianSpan; + + explicit iterator(absl::Span bytes) : bytes_(bytes){}; + + private: + absl::Span bytes_; + }; + + using pointer = value_type *; + using reference = value_type &; + + explicit BigEndianSpan(absl::Span bytes) : bytes_(bytes){}; + + constexpr size_type size() const noexcept { + return bytes_.size() / kBytesPerElement; + }; + + constexpr size_type length() const noexcept { return size(); } + + constexpr bool empty() const noexcept { return size() == 0; } + + value_type operator[](size_type pos) const { + assert(pos < size()); + return value_type(bytes_.subspan((pos * kBytesPerElement))); + } + + constexpr reference at(size_type pos) const { return this->operator[](pos); } + + iterator begin() const { return iterator(bytes_); } + iterator end() const { return iterator({}); } + + private: + absl::Span bytes_; +}; + +template +BigEndianSpan make_big_endian_span( + Container &bytes) { + return BigEndianSpan( + absl::Span(bytes)); +} +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_BIG_ENDIAN_SPAN diff --git a/fpga/xilinx/big-endian-span_test.cc b/fpga/xilinx/big-endian-span_test.cc new file mode 100644 index 0000000..87c7967 --- /dev/null +++ b/fpga/xilinx/big-endian-span_test.cc @@ -0,0 +1,58 @@ +/* + * 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/big-endian-span.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace fpga { +namespace xilinx { +namespace { +TEST(BigEndianSpanTest, Read32WithEmptySpan) { + std::vector bytes; + auto words = make_big_endian_span(bytes); + EXPECT_EQ(words.size(), static_cast(0)); +} + +TEST(BigEndianSpanTest, Read32WithTooFewBytes) { + std::vector bytes{0x0, 0x1, 0x2}; + auto words = make_big_endian_span(bytes); + EXPECT_EQ(words.size(), static_cast(0)); +} + +TEST(BigEndianSpanTest, Read32WithExactBytes) { + std::vector bytes{0x0, 0x1, 0x2, 0x3}; + auto words = make_big_endian_span(bytes); + ASSERT_EQ(words.size(), static_cast(1)); + EXPECT_EQ(static_cast(words[0]), static_cast(0x00010203)); +} + +TEST(BigEndianSpanTest, Read32WithMultipleWords) { + std::vector bytes{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; + auto words = make_big_endian_span(bytes); + ASSERT_EQ(words.size(), static_cast(2)); + EXPECT_EQ(static_cast(words[0]), static_cast(0x00010203)); + EXPECT_EQ(static_cast(words[1]), static_cast(0x04050607)); +} + +TEST(BigEndianSpanTest, Write32) { + std::vector bytes{0x0, 0x1, 0x2, 0x3}; + auto words = make_big_endian_span(bytes); + words[0] = 0x04050607; + + const std::vector expected{0x4, 0x5, 0x6, 0x7}; + EXPECT_EQ(bytes, expected); +} +} // namespace +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/bit-ops.h b/fpga/xilinx/bit-ops.h new file mode 100644 index 0000000..beeb958 --- /dev/null +++ b/fpga/xilinx/bit-ops.h @@ -0,0 +1,52 @@ +/* + * 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_BIT_OPS_H +#define FPGA_XILINX_BIT_OPS_H +namespace fpga { +namespace xilinx { +template +constexpr UInt bit_mask(const int bit) { + return (static_cast(1) << bit); +} + +template +constexpr UInt bit_sizeof() { + return sizeof(UInt) * 8; +} + +template +constexpr UInt bit_all_ones() { + return ~static_cast(0); +} + +template +constexpr UInt bit_mask_range(const int top_bit, const int bottom_bit) { + return ( + (bit_all_ones() >> (bit_sizeof() - 1 - top_bit)) & + (bit_all_ones() - bit_mask(bottom_bit) + static_cast(1))); +} + +template +constexpr UInt bit_field_get(UInt value, const int top_bit, + const int bottom_bit) { + return (value & bit_mask_range(top_bit, bottom_bit)) >> bottom_bit; +} + +template +constexpr UInt bit_field_set(const UInt reg_value, const int top_bit, + const int bottom_bit, + const ValueType field_value) { + return ((reg_value & ~bit_mask_range(top_bit, bottom_bit)) | + ((static_cast(field_value) << bottom_bit) & + bit_mask_range(top_bit, bottom_bit))); +} +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_BIT_OPS_H diff --git a/fpga/xilinx/bit-ops_test.cc b/fpga/xilinx/bit-ops_test.cc new file mode 100644 index 0000000..98143d1 --- /dev/null +++ b/fpga/xilinx/bit-ops_test.cc @@ -0,0 +1,84 @@ +/* + * 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/bit-ops.h" + +#include + +#include "gtest/gtest.h" + +namespace fpga { +namespace xilinx { +namespace { +TEST(BitMaskTest, Bit0) { + const uint32_t expected = bit_mask(0); + EXPECT_EQ(static_cast(0x1), expected); +} + +TEST(BitMaskTest, Bit3) { + const uint32_t expected = bit_mask(3); + EXPECT_EQ(static_cast(0x8), expected); +} + +TEST(BitMaskRange, SingleBit) { + const uint32_t expected = bit_mask_range(23, 23); + EXPECT_EQ(static_cast(0x800000), expected); +} + +TEST(BitMaskRange, DownToZero) { + const uint32_t expected = bit_mask_range(7, 0); + EXPECT_EQ(static_cast(0xFF), expected); +} + +TEST(BitMaskRange, MiddleBits) { + const uint32_t expected = bit_mask_range(18, 8); + EXPECT_EQ(static_cast(0x7FF00), expected); +} + +TEST(BitFieldGetTest, OneSelectedBit) { + const uint32_t expected = bit_field_get(0xFFFFFFFF, 23, 23); + EXPECT_EQ(static_cast(1), expected); +} + +TEST(BitFieldGetTest, SelectDownToZero) { + const uint32_t expected = bit_field_get(0xFFCCBBAA, 7, 0); + EXPECT_EQ(static_cast(0xAA), expected); +} + +TEST(BitFieldGetTest, SelectMidway) { + const uint32_t expected = bit_field_get(0xFFCCBBAA, 18, 8); + EXPECT_EQ(static_cast(0x4BB), expected); +} + +TEST(BitFieldSetTest, WriteOneBit) { + const uint32_t actual = bit_field_set(static_cast(0x0), 23, 23, + static_cast(0x1)); + EXPECT_EQ(actual, static_cast(0x800000)); +} + +TEST(BitFieldSetTest, WriteOneBitWithOutOfRangeValue) { + const uint32_t actual = bit_field_set(static_cast(0x0), 23, 23, + static_cast(0x3)); + EXPECT_EQ(actual, static_cast(0x800000)); +} + +TEST(BitFieldSetTest, WriteMultipleBits) { + const uint32_t actual = bit_field_set(static_cast(0x0), 18, 8, + static_cast(0x123)); + EXPECT_EQ(actual, static_cast(0x12300)); +} + +TEST(BitFieldSetTest, WriteMultipleBitsWithOutOfRangeValue) { + const uint32_t actual = bit_field_set(static_cast(0x0), 18, 8, + static_cast(0x1234)); + EXPECT_EQ(actual, static_cast(0x23400)); +} +} // namespace +} // namespace xilinx +} // namespace fpga diff --git a/fpga/xilinx/configuration-packet.h b/fpga/xilinx/configuration-packet.h new file mode 100644 index 0000000..2f5c822 --- /dev/null +++ b/fpga/xilinx/configuration-packet.h @@ -0,0 +1,122 @@ +/* + * 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_CONFIGURATION_PACKET_H +#define FPGA_XILINX_CONFIGURATION_PACKET_H + +#include +#include +#include +#include +#include +#include + +#include "absl/types/optional.h" +#include "absl/types/span.h" + +namespace fpga { +namespace xilinx { +// As described in the configuration user guide for Series-7 +// (UG470, pg. 108) there are two types of configuration packets +enum class ConfigurationPacketType { kNONE, kTYPE1, kTYPE2 }; + +// Creates a Type1 or Type2 configuration packet. +// Specification of the packets for Series-7 can be found in UG470, pg. 108 +// For Spartan6 UG380, pg. 98 +template +class ConfigurationPacketBase { + public: + using ParseResult = + std::pair, + absl::optional>>; + + // Opcodes as specified in UG470 page 108 + enum class Opcode { + kNOP = 0, + kRead = 1, + kWrite = 2, + // reserved = 3 + }; + + ConfigurationPacketBase(unsigned int header_type, Opcode opcode, + ConfigRegType address, + const absl::Span &data) + : header_type_(header_type), + opcode_(opcode), + address_(address), + data_(data) {} + + unsigned int header_type() const { return header_type_; } + Opcode opcode() const { return opcode_; } + ConfigRegType address() const { return address_; } + const absl::Span &data() const { return data_; } + static ParseResult InitWithWords(absl::Span words, + const Derived *previous_packet = nullptr) { + return Derived::InitWithWordsImpl(words, previous_packet); + } + + private: + unsigned int header_type_; + Opcode opcode_; + ConfigRegType address_; + absl::Span data_; +}; + +template +inline std::ostream &operator<<( + std::ostream &o, + const ConfigurationPacketBase &packet) { + if (packet.header_type() == 0x0) { + return o << "[Zero-pad]" << '\n'; + } + using Opcode = ConfigurationPacketBase::Opcode; + switch (packet.opcode()) { + case Opcode::NOP: o << "[NOP]" << '\n'; break; + case Opcode::Read: + o << "[Read Type="; + o << packet.header_type(); + o << " Address="; + o << std::setw(2) << std::hex; + o << static_cast(packet.address()); + o << " Length="; + o << std::setw(10) << std::dec << packet.data().size(); + o << " Reg=\"" << packet.address() << "\""; + o << "]" << '\n'; + break; + case Opcode::Write: + o << "[Write Type="; + o << packet.header_type(); + o << " Address="; + o << std::setw(2) << std::hex; + o << static_cast(packet.address()); + o << " Length="; + o << std::setw(10) << std::dec << packet.data().size(); + o << " Reg=\"" << packet.address() << "\""; + o << "]" << '\n'; + o << "Data in hex:" << '\n'; + + for (size_t ii = 0; ii < packet.data().size(); ++ii) { + o << std::setw(8) << std::hex; + o << packet.data()[ii] << " "; + + if ((ii + 1) % 4 == 0) { + o << '\n'; + } + } + if (packet.data().size() % 4 != 0) { + o << '\n'; + } + break; + default: o << "[Invalid Opcode]" << '\n'; + } + return o; +} +} // namespace xilinx +} // namespace fpga +#endif // FPGA_XILINX_CONFIGURATION_PACKET_H diff --git a/shell.nix b/shell.nix index b6022a4..169175a 100644 --- a/shell.nix +++ b/shell.nix @@ -4,7 +4,7 @@ let # There is too much volatility between even micro-versions of # newer clang-format. Use slightly older version for now. - clang_for_formatting = pkgs.llvmPackages_18.clang-tools; + clang_for_formatting = pkgs.llvmPackages_17.clang-tools; # clang tidy: use latest. clang_for_tidy = pkgs.llvmPackages_18.clang-tools;