From b35bf3fe111a89fe119cc14911f42fb662292d2b Mon Sep 17 00:00:00 2001 From: Tomasz Michalak Date: Wed, 2 Oct 2019 12:29:32 +0200 Subject: [PATCH 1/2] bitstream_tools: Add support for Spartan6 Signed-off-by: Tomasz Michalak --- lib/CMakeLists.txt | 8 + lib/include/prjxray/xilinx/architectures.h | 23 +- lib/include/prjxray/xilinx/bitstream_reader.h | 3 +- lib/include/prjxray/xilinx/bitstream_writer.h | 2 + lib/include/prjxray/xilinx/configuration.h | 124 ++++++++ .../prjxray/xilinx/configuration_register.h | 43 +++ lib/include/prjxray/xilinx/frames.h | 20 +- .../prjxray/xilinx/spartan6/block_type.h | 37 +++ lib/include/prjxray/xilinx/spartan6/command.h | 35 +++ .../xilinx/spartan6/configuration_bus.h | 91 ++++++ .../xilinx/spartan6/configuration_column.h | 75 +++++ .../xilinx/spartan6/configuration_row.h | 90 ++++++ .../prjxray/xilinx/spartan6/frame_address.h | 55 ++++ .../xilinx/spartan6/global_clock_region.h | 93 ++++++ lib/include/prjxray/xilinx/spartan6/part.h | 70 +++++ lib/xilinx/bitstream_writer.cc | 37 +++ lib/xilinx/configuration.cc | 272 +++++++++++++++++- lib/xilinx/configuration_packet.cc | 80 ++++++ lib/xilinx/configuration_register.cc | 82 ++++++ lib/xilinx/frames.cc | 4 + lib/xilinx/spartan6/block_type.cc | 62 ++++ lib/xilinx/spartan6/configuration_bus.cc | 76 +++++ lib/xilinx/spartan6/configuration_column.cc | 52 ++++ lib/xilinx/spartan6/configuration_row.cc | 62 ++++ lib/xilinx/spartan6/frame_address.cc | 92 ++++++ lib/xilinx/spartan6/global_clock_region.cc | 70 +++++ lib/xilinx/spartan6/part.cc | 115 ++++++++ tools/bitread.cc | 30 +- tools/bittool.cc | 28 +- 29 files changed, 1804 insertions(+), 27 deletions(-) create mode 100644 lib/include/prjxray/xilinx/spartan6/block_type.h create mode 100644 lib/include/prjxray/xilinx/spartan6/command.h create mode 100644 lib/include/prjxray/xilinx/spartan6/configuration_bus.h create mode 100644 lib/include/prjxray/xilinx/spartan6/configuration_column.h create mode 100644 lib/include/prjxray/xilinx/spartan6/configuration_row.h create mode 100644 lib/include/prjxray/xilinx/spartan6/frame_address.h create mode 100644 lib/include/prjxray/xilinx/spartan6/global_clock_region.h create mode 100644 lib/include/prjxray/xilinx/spartan6/part.h create mode 100644 lib/xilinx/spartan6/block_type.cc create mode 100644 lib/xilinx/spartan6/configuration_bus.cc create mode 100644 lib/xilinx/spartan6/configuration_column.cc create mode 100644 lib/xilinx/spartan6/configuration_row.cc create mode 100644 lib/xilinx/spartan6/frame_address.cc create mode 100644 lib/xilinx/spartan6/global_clock_region.cc create mode 100644 lib/xilinx/spartan6/part.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6683dfb1..ce9420c2 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -7,6 +7,14 @@ add_library(libprjxray xilinx/configuration_register.cc xilinx/configuration.cc xilinx/frames.cc + # Spartan6 specific + xilinx/spartan6/frame_address.cc + xilinx/spartan6/global_clock_region.cc + xilinx/spartan6/part.cc + xilinx/spartan6/configuration_row.cc + xilinx/spartan6/block_type.cc + xilinx/spartan6/configuration_bus.cc + xilinx/spartan6/configuration_column.cc # Series-7 specific xilinx/xc7series/frame_address.cc xilinx/xc7series/global_clock_region.cc diff --git a/lib/include/prjxray/xilinx/architectures.h b/lib/include/prjxray/xilinx/architectures.h index ce003ba8..cdf992e6 100644 --- a/lib/include/prjxray/xilinx/architectures.h +++ b/lib/include/prjxray/xilinx/architectures.h @@ -6,20 +6,23 @@ #include #include -#include +#include +#include #include #include namespace prjxray { namespace xilinx { +class Spartan6; class Series7; class UltraScale; class UltraScalePlus; class Architecture { public: - using Container = absl::variant; + using Container = + absl::variant; Architecture(const std::string& name) : name_(name) {} const std::string& name() const { return name_; } virtual ~Architecture() {} @@ -28,6 +31,18 @@ class Architecture { const std::string name_; }; +class Spartan6 : public Architecture { + public: + using ConfRegType = Spartan6ConfigurationRegister; + using Part = spartan6::Part; + using ConfigurationPackage = + std::vector>>; + using FrameAddress = spartan6::FrameAddress; + using WordType = uint16_t; + Spartan6() : Architecture("Spartan6") {} + static constexpr int words_per_frame = 65; +}; + class Series7 : public Architecture { public: using ConfRegType = Series7ConfigurationRegister; @@ -57,7 +72,9 @@ class ArchitectureFactory { public: static Architecture::Container create_architecture( const std::string& arch) { - if (arch == "Series7") { + if (arch == "Spartan6") { + return Spartan6(); + } else if (arch == "Series7") { return Series7(); } else if (arch == "UltraScale") { return UltraScale(); diff --git a/lib/include/prjxray/xilinx/bitstream_reader.h b/lib/include/prjxray/xilinx/bitstream_reader.h index 61e807f1..5464a520 100644 --- a/lib/include/prjxray/xilinx/bitstream_reader.h +++ b/lib/include/prjxray/xilinx/bitstream_reader.h @@ -90,7 +90,8 @@ BitstreamReader::InitWithBytes(T bitstream) { auto config_packets = bitstream_span.subspan(sync_pos - bitstream.begin()); - // Convert the bytes into 32-bit, big-endian words. + // 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{big_endian_reader.begin(), diff --git a/lib/include/prjxray/xilinx/bitstream_writer.h b/lib/include/prjxray/xilinx/bitstream_writer.h index 883e9c2a..09d69f45 100644 --- a/lib/include/prjxray/xilinx/bitstream_writer.h +++ b/lib/include/prjxray/xilinx/bitstream_writer.h @@ -25,6 +25,8 @@ namespace xilinx { uint32_t packet2header( const ConfigurationPacket& packet); +uint32_t packet2header( + const ConfigurationPacket& packet); // Writes out the complete Xilinx bitstream including // header, sync word and configuration sequence. template diff --git a/lib/include/prjxray/xilinx/configuration.h b/lib/include/prjxray/xilinx/configuration.h index 752ee7ff..6236345c 100644 --- a/lib/include/prjxray/xilinx/configuration.h +++ b/lib/include/prjxray/xilinx/configuration.h @@ -94,6 +94,130 @@ Configuration::createType2ConfigurationPacketData( return packet_data; } +template <> +template +absl::optional> +Configuration::InitWithPackets(const typename Spartan6::Part& part, + Collection& packets) { + using ArchType = Spartan6; + // Registers that can be directly written to. + uint32_t command_register = 0; + uint32_t frame_address_register = 0; + uint32_t mask_register = 0; + __attribute__((unused)) uint32_t ctl1_register = 0; + + // Internal state machine for writes. + bool start_new_write = false; + typename ArchType::FrameAddress current_frame_address = 0; + + Configuration::FrameMap frames; + for (auto packet : packets) { + if (packet.opcode() != + ConfigurationPacket< + typename ArchType::ConfRegType>::Opcode::Write) { + continue; + } + + switch (packet.address()) { + case ArchType::ConfRegType::MASK: + if (packet.data().size() < 1) + continue; + mask_register = packet.data()[0]; + break; + case ArchType::ConfRegType::CTL: + if (packet.data().size() < 1) + continue; + ctl1_register = + packet.data()[0] & mask_register; + break; + case ArchType::ConfRegType::CMD: + if (packet.data().size() < 1) + continue; + command_register = packet.data()[0]; + // Writes to CMD trigger an immediate action. In + // the case of WCFG, that is just setting a flag + // for the next FDRI. + if (command_register == 0x1) { + start_new_write = true; + } + break; + case ArchType::ConfRegType::IDCODE: { + // This really should be a two-word write. + if (packet.data().size() < 2) + continue; + + // If the IDCODE doesn't match our expected + // part, consider the bitstream invalid. + uint32_t idcode = (packet.data()[0] << 16) | + (packet.data()[1]); + if (idcode != part.idcode()) { + return {}; + } + break; + } + // UG380 describes the frame addressing scheme where two + // words for FAR_MAJ update FAR_MAJ anda FAR_MIN - + // FAR_MAJ comes first + case ArchType::ConfRegType::FAR_MAJ: { + size_t packet_size = packet.data().size(); + assert(packet_size < 3); + if (packet_size < 1) { + continue; + } else if (packet_size < 2) { + frame_address_register = + (packet.data()[0] & 0xFFFF) << 16; + } else { + frame_address_register = + ((packet.data()[0] & 0xFFFF) + << 16) | + (packet.data()[1] & 0xFFFF); + } + break; + } + case ArchType::ConfRegType::FAR_MIN: + // This really should be a one-word write. + if (packet.data().size() < 1) + continue; + + frame_address_register |= + packet.data()[0] & 0x3FF; + + break; + case ArchType::ConfRegType::FDRI: { + if (start_new_write) { + current_frame_address = + frame_address_register; + start_new_write = false; + } + + // Spartan6 frames are 65-words long. Writes + // to this register can be multiples of that to + // do auto-incrementing block writes. + + for (size_t ii = 0; ii < packet.data().size(); + ii += ArchType::words_per_frame) { + frames[current_frame_address] = + packet.data().subspan( + ii, ArchType::words_per_frame); + + auto next_address = + part.GetNextFrameAddress( + current_frame_address); + if (!next_address) + break; + + current_frame_address = *next_address; + } + break; + } + default: + break; + } + } + + return Configuration(part, frames); +} + template template absl::optional> diff --git a/lib/include/prjxray/xilinx/configuration_register.h b/lib/include/prjxray/xilinx/configuration_register.h index 9825aacb..0699f6cc 100644 --- a/lib/include/prjxray/xilinx/configuration_register.h +++ b/lib/include/prjxray/xilinx/configuration_register.h @@ -6,6 +6,47 @@ namespace prjxray { namespace xilinx { +// Spartan6 configuration register addresses +// according to UG380, pg. 100 +enum class Spartan6ConfigurationRegister : unsigned int { + CRC = 0x00, + FAR = 0x01, + FAR_MAJ = 0x01, + FAR_MIN = 0x02, + FDRI = 0x03, + FDRO = 0x04, + CMD = 0x05, + CTL = 0x06, + CTL1 = 0x06, + MASK = 0x07, + STAT = 0x08, + LOUT = 0x09, + COR1 = 0x0a, + COR2 = 0x0b, + PWRDN_REG = 0x0c, + FLR = 0x0d, + IDCODE = 0x0e, + CWDT = 0x0f, + HC_OPT_REG = 0x10, + CSBO = 0x12, + GENERAL1 = 0x13, + GENERAL2 = 0x14, + GENERAL3 = 0x15, + GENERAL4 = 0x16, + GENERAL5 = 0x17, + MODE_REG = 0x18, + PU_GWE = 0x19, + PU_GTS = 0x1a, + MFWR = 0x1b, + CCLK_FREQ = 0x1c, + SEU_OPT = 0x1d, + EXP_SIGN = 0x1e, + RDBK_SIGN = 0x1f, + BOOTSTS = 0x20, + EYE_MASK = 0x21, + CBC_REG = 0x22, +}; + // Series-7 configuration register addresses // according to UG470, pg. 109 enum class Series7ConfigurationRegister : unsigned int { @@ -32,6 +73,8 @@ enum class Series7ConfigurationRegister : unsigned int { BSPI = 0x1F, }; +std::ostream& operator<<(std::ostream& o, + const Spartan6ConfigurationRegister& value); std::ostream& operator<<(std::ostream& o, const Series7ConfigurationRegister& value); diff --git a/lib/include/prjxray/xilinx/frames.h b/lib/include/prjxray/xilinx/frames.h index b54741ac..f4a96f1d 100644 --- a/lib/include/prjxray/xilinx/frames.h +++ b/lib/include/prjxray/xilinx/frames.h @@ -9,7 +9,6 @@ #include #include -#include namespace prjxray { namespace xilinx { @@ -67,13 +66,18 @@ int Frames::readFrames(const std::string& frm_file_str) { std::vector frame_data_strings = absl::StrSplit(frame_delta.second, ','); - if (frame_data_strings.size() != ArchType::words_per_frame) { - std::cerr << "Frame " << std::hex << frame_address - << ": found " << std::dec - << frame_data_strings.size() - << " words instead of " - << ArchType::words_per_frame << std::endl; - continue; + // Spartan6's IOB frames can have different word count + if (!std::is_same::value) { + if (frame_data_strings.size() != + ArchType::words_per_frame) { + std::cerr + << "Frame " << std::hex << frame_address + << ": found " << std::dec + << frame_data_strings.size() + << " words instead of " + << ArchType::words_per_frame << std::endl; + continue; + } } FrameData frame_data(frame_data_strings.size(), 0); diff --git a/lib/include/prjxray/xilinx/spartan6/block_type.h b/lib/include/prjxray/xilinx/spartan6/block_type.h new file mode 100644 index 00000000..a42a8db4 --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/block_type.h @@ -0,0 +1,37 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_BLOCK_TYPE_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_BLOCK_TYPE_H_ + +#include + +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +// According to UG380 pg. 97 there are 3 types of configuration frames: +// Type 0: Core: CLB, DSP, input/output interconnect (IOI), clocking +// Type 1: Block RAM +// Type 2: IOB +enum class BlockType : unsigned int { + CLB_IOI_CLK = 0x0, + BLOCK_RAM = 0x1, + IOB = 0x2, +}; + +std::ostream& operator<<(std::ostream& o, BlockType value); + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { +template <> +struct convert { + static Node encode(const prjxray::xilinx::spartan6::BlockType& rhs); + static bool decode(const Node& node, + prjxray::xilinx::spartan6::BlockType& lhs); +}; +} // namespace YAML + +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_BLOCK_TYPE_H_ diff --git a/lib/include/prjxray/xilinx/spartan6/command.h b/lib/include/prjxray/xilinx/spartan6/command.h new file mode 100644 index 00000000..93bafdb1 --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/command.h @@ -0,0 +1,35 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_COMMAND_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_COMMAND_H_ + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +// Command register map according to UG380 pg. 102 +enum class Command : uint32_t { + NOP = 0x0, + WCFG = 0x1, + MFW = 0x2, + LFRM = 0x3, + RCFG = 0x4, + START = 0x5, + RCAP = 0x6, + RCRC = 0x7, + AGHIGH = 0x8, + SWITCH = 0x9, + GRESTORE = 0xA, + SHUTDOWN = 0xB, + GCAPTURE = 0xC, + DESYNC = 0xD, + IPROG = 0xF, + CRCC = 0x10, + LTIMER = 0x11, + BSPI_READ = 0x12, + FALL_EDGE = 0x13, +}; + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_COMMAND_H_ diff --git a/lib/include/prjxray/xilinx/spartan6/configuration_bus.h b/lib/include/prjxray/xilinx/spartan6/configuration_bus.h new file mode 100644 index 00000000..a5d0081b --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/configuration_bus.h @@ -0,0 +1,91 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_BUS_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_BUS_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +// ConfigurationBus represents a bus for sending frames to a specific BlockType +// within a Row. An instance of ConfigurationBus will contain one or more +// ConfigurationColumns. +class ConfigurationBus { + public: + ConfigurationBus() = default; + + // Constructs a ConfigurationBus from iterators yielding + // FrameAddresses. The frame address need not be contiguous or sorted + // but they must all have the same block type, row half, and row + // address components. + template + ConfigurationBus(T first, T last); + + // Returns true if the provided address falls into a valid segment of + // the address range on this bus. Only the column and minor components + // of the address are considered as all other components are outside + // the scope of a bus. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next valid address on the bus in numerically increasing + // order. If the next address would fall outside this bus, no object is + // returned. + absl::optional GetNextFrameAddress( + FrameAddress address) const; + + private: + friend struct YAML::convert; + + 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.row() == first->row()); + })); + + std::sort(first, last, + [](const FrameAddress& lhs, const FrameAddress& rhs) { + return lhs.column() < rhs.column(); + }); + + for (auto col_first = first; col_first != last;) { + auto col_last = std::upper_bound( + col_first, last, col_first->column(), + [](const unsigned int& lhs, const FrameAddress& rhs) { + return lhs < rhs.column(); + }); + + configuration_columns_.emplace( + col_first->column(), + std::move(ConfigurationColumn(col_first, col_last))); + col_first = col_last; + } +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { +template <> +struct convert { + static Node encode( + const prjxray::xilinx::spartan6::ConfigurationBus& rhs); + static bool decode(const Node& node, + prjxray::xilinx::spartan6::ConfigurationBus& lhs); +}; +} // namespace YAML + +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_BUS_H_ diff --git a/lib/include/prjxray/xilinx/spartan6/configuration_column.h b/lib/include/prjxray/xilinx/spartan6/configuration_column.h new file mode 100644 index 00000000..af8edd63 --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/configuration_column.h @@ -0,0 +1,75 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_COLUMN_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_COLUMN_H_ + +#include +#include + +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +// ConfigurationColumn represents an endpoint on a ConfigurationBus. +class ConfigurationColumn { + public: + ConfigurationColumn() = default; + ConfigurationColumn(unsigned int frame_count) + : frame_count_(frame_count) {} + + // Returns a ConfigurationColumn that describes a continguous range of + // minor addresses that encompasses the given + // FrameAddresses. The provided addresses must only + // differ only by their minor addresses. + template + ConfigurationColumn(T first, T last); + + // Returns true if the minor field of the address is within the valid + // range of this column. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next address in numerical order. If the next address + // would be outside this column, return no object. + absl::optional GetNextFrameAddress( + FrameAddress address) const; + + private: + friend struct YAML::convert; + + 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.row() == first->row() && + addr.column() == first->column()); + })); + + auto max_minor = std::max_element( + first, last, [](const FrameAddress& lhs, const FrameAddress& rhs) { + return lhs.minor() < rhs.minor(); + }); + + frame_count_ = max_minor->minor() + 1; +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { +template <> +struct convert { + static Node encode( + const prjxray::xilinx::spartan6::ConfigurationColumn& rhs); + static bool decode(const Node& node, + prjxray::xilinx::spartan6::ConfigurationColumn& lhs); +}; +} // namespace YAML + +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_CONFIGURATION_COLUMN_H_ diff --git a/lib/include/prjxray/xilinx/spartan6/configuration_row.h b/lib/include/prjxray/xilinx/spartan6/configuration_row.h new file mode 100644 index 00000000..a2b267eb --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/configuration_row.h @@ -0,0 +1,90 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_ROW_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_ROW_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +class Row { + public: + Row() = default; + + // Construct a row from a range of iterators that yield FrameAddresses. + // The addresses may be noncontinguous and/or unsorted but all must + // share the same row half and row components. + template + Row(T first, T last); + + // Returns true if the provided address falls within a valid range + // attributed to this row. Only the block type, column, and minor + // address components are considerd as the remaining components are + // outside the scope of a row. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next numerically increasing address within the Row. If + // the next address would fall outside the Row, no object is returned. + // If the next address would cross from one block type to another, no + // object is returned as other rows of the same block type come before + // other block types numerically. + absl::optional GetNextFrameAddress( + FrameAddress address) const; + + private: + friend struct YAML::convert; + + 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; + } +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { +template <> +struct convert { + static Node encode(const prjxray::xilinx::spartan6::Row& rhs); + static bool decode(const Node& node, + prjxray::xilinx::spartan6::Row& lhs); +}; +} // namespace YAML + +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_ROW_H_ diff --git a/lib/include/prjxray/xilinx/spartan6/frame_address.h b/lib/include/prjxray/xilinx/spartan6/frame_address.h new file mode 100644 index 00000000..651d86af --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/frame_address.h @@ -0,0 +1,55 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_FRAME_ADDRESS_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_FRAME_ADDRESS_H_ + +#include +#include + +#include +#include + +#ifdef _GNU_SOURCE +#undef minor +#endif + +namespace prjxray { +namespace xilinx { +namespace spartan6 { +class FrameAddress { + public: + FrameAddress() : address_(0) {} + + FrameAddress(uint32_t address) : address_(address){}; + + FrameAddress(BlockType block_type, + uint8_t row, + uint8_t column, + uint16_t minor); + + operator uint32_t() const { return address_; } + bool is_bottom_half_rows() const; + BlockType block_type() const; + uint8_t row() const; + uint8_t column() const; + uint16_t minor() const; + + private: + uint32_t address_; +}; + +std::ostream& operator<<(std::ostream& o, const FrameAddress& addr); + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { + +namespace spartan6 = prjxray::xilinx::spartan6; + +template <> +struct convert { + static Node encode(const spartan6::FrameAddress& rhs); + static bool decode(const Node& node, spartan6::FrameAddress& lhs); +}; +} // namespace YAML +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_FRAME_ADDRESS_H_ diff --git a/lib/include/prjxray/xilinx/spartan6/global_clock_region.h b/lib/include/prjxray/xilinx/spartan6/global_clock_region.h new file mode 100644 index 00000000..bb0ce93a --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/global_clock_region.h @@ -0,0 +1,93 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_GLOBAL_CLOCK_REGION_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_GLOBAL_CLOCK_REGION_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +// GlobalClockRegion represents all the resources associated with a single +// global clock buffer (BUFG) tile. In 7-Series FPGAs, there are two BUFG +// tiles that divide the chip into top and bottom "halves". Each half may +// contains any number of rows, buses, and columns. +class GlobalClockRegion { + public: + GlobalClockRegion() = default; + + // Construct a GlobalClockRegion from iterators that yield + // FrameAddresses which are known to be valid. The addresses may be + // noncontinguous and/or unordered but they must share the same row + // half address component. + template + GlobalClockRegion(T first, T last); + + // Returns true if the address falls within a valid range inside the + // global clock region. The row half address component is ignored as it + // is outside the context of a global clock region. + bool IsValidFrameAddress(FrameAddress address) const; + + // Returns the next numerically increasing address known within this + // global clock region. If the next address would fall outside this + // global clock region, no address is returned. If the next address + // would jump to a different block type, no address is returned as the + // same block type in other global clock regions come numerically + // before other block types. + absl::optional GetNextFrameAddress( + FrameAddress address) const; + + private: + friend struct YAML::convert; + + 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; + } +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { +template <> +struct convert { + static Node encode( + const prjxray::xilinx::spartan6::GlobalClockRegion& rhs); + static bool decode(const Node& node, + prjxray::xilinx::spartan6::GlobalClockRegion& lhs); +}; +} // namespace YAML + +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_GLOBAL_CLOCK_REGION_H_ diff --git a/lib/include/prjxray/xilinx/spartan6/part.h b/lib/include/prjxray/xilinx/spartan6/part.h new file mode 100644 index 00000000..df113848 --- /dev/null +++ b/lib/include/prjxray/xilinx/spartan6/part.h @@ -0,0 +1,70 @@ +#ifndef PRJXRAY_LIB_XILINX_SPARTAN6_PART_H_ +#define PRJXRAY_LIB_XILINX_SPARTAN6_PART_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +class Part { + public: + constexpr static uint32_t kInvalidIdcode = 0; + + static absl::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; + + absl::optional GetNextFrameAddress( + FrameAddress address) const; + + private: + friend struct YAML::convert; + + uint32_t idcode_; + spartan6::GlobalClockRegion top_region_; + spartan6::GlobalClockRegion bottom_region_; +}; + +template +Part::Part(uint32_t idcode, T first, T last) : idcode_(idcode) { + top_region_ = spartan6::GlobalClockRegion(first, last); +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { + +namespace spartan6 = prjxray::xilinx::spartan6; + +template <> +struct convert { + static Node encode(const spartan6::Part& rhs); + static bool decode(const Node& node, spartan6::Part& lhs); +}; +} // namespace YAML + +#endif // PRJXRAY_LIB_XILINX_SPARTAN6_PART_H_ diff --git a/lib/xilinx/bitstream_writer.cc b/lib/xilinx/bitstream_writer.cc index 5a9cc0b6..2744071c 100644 --- a/lib/xilinx/bitstream_writer.cc +++ b/lib/xilinx/bitstream_writer.cc @@ -7,6 +7,12 @@ namespace prjxray { namespace xilinx { +template <> +// Per UG380 pg 78: Bus Width Auto Detection +typename BitstreamWriter::header_t BitstreamWriter::header_{ + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566}; + // Per UG470 pg 80: Bus Width Auto Detection template <> typename BitstreamWriter::header_t BitstreamWriter::header_{ @@ -22,6 +28,37 @@ typename BitstreamWriter::header_t BitstreamWriter::header_{ 0xFFFFFFFF, 0x000000BB, 0x11220044, 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; +uint32_t packet2header( + const ConfigurationPacket& packet) { + uint32_t ret = 0; + + ret = bit_field_set(ret, 15, 13, packet.header_type()); + + switch (packet.header_type()) { + case NONE: + // Bitstreams are 0 padded sometimes, essentially making + // a type 0 frame Ignore the other fields for now + break; + case TYPE1: { + // Table 5-20: Type 1 Packet Header Format + ret = bit_field_set(ret, 12, 11, packet.opcode()); + ret = bit_field_set(ret, 10, 5, packet.address()); + ret = bit_field_set(ret, 4, 0, packet.data().length()); + break; + } + case TYPE2: { + // Table 5-22: Type 2 Packet Header + ret = bit_field_set(ret, 12, 11, packet.opcode()); + ret = bit_field_set(ret, 10, 5, packet.address()); + break; + } + default: + break; + } + + return ret; +} + uint32_t packet2header( const ConfigurationPacket& packet) { uint32_t ret = 0; diff --git a/lib/xilinx/configuration.cc b/lib/xilinx/configuration.cc index 71ea317d..d7d6626c 100644 --- a/lib/xilinx/configuration.cc +++ b/lib/xilinx/configuration.cc @@ -9,15 +9,285 @@ #include #include #include -#include #include #include +#include #include #include namespace prjxray { namespace xilinx { +template <> +Configuration::PacketData +Configuration::createType2ConfigurationPacketData( + const Frames::Frames2Data& frames, + absl::optional& part) { + // Generate a single type 2 packet that writes everything at once. + PacketData packet_data; + for (auto& frame : frames) { + std::copy(frame.second.begin(), frame.second.end(), + std::back_inserter(packet_data)); + } + + // Insert payload length + size_t packet_data_size = packet_data.size() - 2; + packet_data.insert(packet_data.begin(), packet_data_size & 0xFFFF); + packet_data.insert(packet_data.begin(), + (packet_data_size >> 16) & 0xFFFF); + return packet_data; +} + +template <> +void Configuration::createConfigurationPackage( + Spartan6::ConfigurationPackage& out_packets, + const PacketData& packet_data, + absl::optional& part) { + using ArchType = Spartan6; + using ConfigurationRegister = ArchType::ConfRegType; + // Initialization sequence + // + // Reset CRC + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CMD, + {static_cast(spartan6::Command::RCRC)})); + + // NOP + out_packets.emplace_back(new NopPacket()); + + // Frame length + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::FLR, {0x0380})); + + // Configuration Options 1 + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::COR1, {0x3d08})); + + // Configurations Options2 + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::COR2, {0x9ee})); + + // IDCODE + out_packets.emplace_back( + new ConfigurationPacketWithPayload<2, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::IDCODE, + {part->idcode() >> 16, part->idcode()})); + + // Control MASK + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::MASK, {0xcf})); + + // Control options + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CTL, {0x81})); + + // NOP packets + for (int i = 0; i < 17; i++) { + out_packets.emplace_back( + new NopPacket()); + } + + // CCLK FREQ + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CCLK_FREQ, {0x3cc8})); + + // PWRDN_REG + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::PWRDN_REG, {0x881})); + + // EYE MASK + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::EYE_MASK, {0x0})); + + // House Clean Option + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::HC_OPT_REG, {0x1f})); + + // Configuration Watchdog Timer + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CWDT, {0xffff})); + + // GWE cycle during wake-up from suspend + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::PU_GWE, {0x5})); + + // GTS cycle during wake-up from suspend + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::PU_GTS, {0x4})); + + // Reboot mode + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::MODE_REG, {0x100})); + + // General options 1 + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::GENERAL1, {0x0})); + + // General options 2 + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::GENERAL2, {0x0})); + + // General options 3 + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::GENERAL3, {0x0})); + + // General options 4 + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::GENERAL4, {0x0})); + + // General options 5 + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::GENERAL5, {0x0})); + + // SEU frequency, enable and status + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::SEU_OPT, {0x1be2})); + + // Expected readback signature for SEU detection + out_packets.emplace_back( + new ConfigurationPacketWithPayload<2, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::EXP_SIGN, {0x0, 0x0})); + + // NOP + out_packets.emplace_back(new NopPacket()); + out_packets.emplace_back(new NopPacket()); + + // FAR + out_packets.emplace_back( + new ConfigurationPacketWithPayload<2, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::FAR_MAJ, {0x0, 0x0})); + + // Write Configuration Data + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CMD, + {static_cast(spartan6::Command::WCFG)})); + + // Frame data write + out_packets.emplace_back(new ConfigurationPacket( + TYPE2, ConfigurationPacket::Opcode::Write, + ConfigurationRegister::FDRI, {packet_data})); + + // NOP packets + for (int i = 0; i < 24; i++) { + out_packets.emplace_back( + new NopPacket()); + } + + // Finalization sequence + // + // Set/reset the IOB and CLB flip-flops + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CMD, + {static_cast(spartan6::Command::GRESTORE)})); + + // Last Frame + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CMD, + {static_cast(spartan6::Command::LFRM)})); + + // NOP packets + for (int i = 0; i < 4; i++) { + out_packets.emplace_back( + new NopPacket()); + } + + // Set/reset the IOB and CLB flip-flops + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CMD, + {static_cast(spartan6::Command::GRESTORE)})); + + // Startup sequence + // + // Start + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CMD, + {static_cast(spartan6::Command::START)})); + + // Control MASK + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::MASK, {0xff})); + + // Control options + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CTL, {0x81})); + + // CRC + out_packets.emplace_back( + new ConfigurationPacketWithPayload<2, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CRC, {0x39, 0xe423})); + + // Desync + out_packets.emplace_back( + new ConfigurationPacketWithPayload<1, ConfigurationRegister>( + ConfigurationPacket::Opcode::Write, + ConfigurationRegister::CMD, + {static_cast(spartan6::Command::DESYNC)})); + + // NOP packets + for (int i = 0; i < 14; i++) { + out_packets.emplace_back( + new NopPacket()); + } +} + template <> void Configuration::createConfigurationPackage( Series7::ConfigurationPackage& out_packets, diff --git a/lib/xilinx/configuration_packet.cc b/lib/xilinx/configuration_packet.cc index 21be8c5a..14d66b0b 100644 --- a/lib/xilinx/configuration_packet.cc +++ b/lib/xilinx/configuration_packet.cc @@ -9,6 +9,83 @@ namespace prjxray { namespace xilinx { +template <> +std::pair, + absl::optional>> +ConfigurationPacket::InitWithWords( + absl::Span words, + const ConfigurationPacket* previous_packet) { + using ConfigurationRegister = Spartan6ConfigurationRegister; + // Need at least one 32-bit word to have a valid packet header. + if (words.size() < 1) + return {words, {}}; + + uint32_t header_type = bit_field_get(words[0], 15, 13); + switch (header_type) { + case NONE: + // Type 0 is emitted at the end of a configuration row + // when BITSTREAM.GENERAL.DEBUGBITSTREAM is set to YES. + // These seem to be padding that are interepreted as + // NOPs. Since Type 0 packets don't exist according to + // UG470 and they seem to be zero-filled, just consume + // the bytes without generating a packet. + return {words.subspan(1), + {{header_type, + Opcode::NOP, + ConfigurationRegister::CRC, + {}}}}; + case TYPE1: { + Opcode opcode = static_cast( + bit_field_get(words[0], 12, 11)); + ConfigurationRegister address = + static_cast( + bit_field_get(words[0], 10, 5)); + uint32_t data_word_count = + bit_field_get(words[0], 4, 0); + + // If the full packet has not been received, return as + // though no valid packet was found. + if (data_word_count > words.size() - 1) { + return {words, {}}; + } + + return {words.subspan(data_word_count + 1), + {{header_type, opcode, address, + words.subspan(1, data_word_count)}}}; + } + case TYPE2: { + absl::optional packet; + Opcode opcode = static_cast( + bit_field_get(words[0], 12, 11)); + ConfigurationRegister address = + static_cast( + bit_field_get(words[0], 10, 5)); + // Type 2 packets according to UG380 consist of + // a header word followed by 2 WCD (Word Count Data) + // words + uint32_t data_word_count = (words[1] << 16) | words[2]; + + // If the full packet has not been received, return as + // though no valid packet was found. + if (data_word_count > words.size() - 1) { + return {words, {}}; + } + + // Create a packet that contains as many data words + // as specified in the WCD packets, but omit them + // in the configuration packet along with the header + // FIXME Figure out why we need the extra 2 words + packet = ConfigurationPacket( + header_type, opcode, address, + words.subspan(3, data_word_count + 2)); + + return {words.subspan(data_word_count + 3), packet}; + } + default: + return {{}, {}}; + } +} + template <> std::pair, absl::optional>> @@ -133,6 +210,9 @@ std::ostream& operator<<(std::ostream& o, return o; } +template std::ostream& operator<<( + std::ostream&, + const ConfigurationPacket&); template std::ostream& operator<<( std::ostream&, const ConfigurationPacket&); diff --git a/lib/xilinx/configuration_register.cc b/lib/xilinx/configuration_register.cc index 0935fa8a..2e49c541 100644 --- a/lib/xilinx/configuration_register.cc +++ b/lib/xilinx/configuration_register.cc @@ -3,6 +3,88 @@ namespace prjxray { namespace xilinx { +std::ostream& operator<<(std::ostream& o, + const Spartan6ConfigurationRegister& value) { + switch (value) { + case Spartan6ConfigurationRegister::CRC: + return o << "CRC"; + case Spartan6ConfigurationRegister::FAR_MAJ: + return o << "Frame Address Register Block and Major"; + case Spartan6ConfigurationRegister::FAR_MIN: + return o << "Frame Address Register Minor"; + case Spartan6ConfigurationRegister::FDRI: + return o << "Frame Data Input"; + case Spartan6ConfigurationRegister::FDRO: + return o << "Frame Data Output"; + case Spartan6ConfigurationRegister::CMD: + return o << "Command"; + case Spartan6ConfigurationRegister::CTL: + return o << "Control"; + case Spartan6ConfigurationRegister::MASK: + return o << "Control Mask"; + case Spartan6ConfigurationRegister::STAT: + return o << "Status"; + case Spartan6ConfigurationRegister::LOUT: + return o << "Legacy Output"; + case Spartan6ConfigurationRegister::COR1: + return o << "Configuration Option 1"; + case Spartan6ConfigurationRegister::COR2: + return o << "Configuration Option 2"; + case Spartan6ConfigurationRegister::PWRDN_REG: + return o << "Power-down Option register"; + case Spartan6ConfigurationRegister::FLR: + return o << "Frame Length register"; + case Spartan6ConfigurationRegister::IDCODE: + return o << "Device ID"; + case Spartan6ConfigurationRegister::CWDT: + return o << "Watchdog Timer"; + case Spartan6ConfigurationRegister::HC_OPT_REG: + return o << "House Clean Option register"; + case Spartan6ConfigurationRegister::CSBO: + return o << "CSB output for parallel daisy-chaining"; + case Spartan6ConfigurationRegister::GENERAL1: + return o << "Power-up self test or loadable program " + "address"; + case Spartan6ConfigurationRegister::GENERAL2: + return o << "Power-up self test or loadable program " + << "address and new SPI opcode"; + case Spartan6ConfigurationRegister::GENERAL3: + return o << "Golden bitstream address"; + case Spartan6ConfigurationRegister::GENERAL4: + return o + << "Golden bitstream address and new SPI opcode"; + case Spartan6ConfigurationRegister::GENERAL5: + return o + << "User-defined register for fail-safe scheme"; + case Spartan6ConfigurationRegister::MODE_REG: + return o << "Reboot mode"; + case Spartan6ConfigurationRegister::PU_GWE: + return o << "GWE cycle during wake-up from suspend"; + case Spartan6ConfigurationRegister::PU_GTS: + return o << "GTS cycle during wake-up from suspend"; + case Spartan6ConfigurationRegister::MFWR: + return o << "Multi-frame write register"; + case Spartan6ConfigurationRegister::CCLK_FREQ: + return o << "CCLK frequency for master mode"; + case Spartan6ConfigurationRegister::SEU_OPT: + return o << "SEU frequency, enable and status"; + case Spartan6ConfigurationRegister::EXP_SIGN: + return o << "Expected readback signature for SEU " + "detection"; + case Spartan6ConfigurationRegister::RDBK_SIGN: + return o << "Readback signature for readback command " + "and SEU"; + case Spartan6ConfigurationRegister::BOOTSTS: + return o << "Boot History Register"; + case Spartan6ConfigurationRegister::EYE_MASK: + return o << "Mask pins for Multi-Pin Wake-Up"; + case Spartan6ConfigurationRegister::CBC_REG: + return o << "Initial CBC Value Register"; + default: + return o << "Unknown"; + } +} + std::ostream& operator<<(std::ostream& o, const Series7ConfigurationRegister& value) { switch (value) { diff --git a/lib/xilinx/frames.cc b/lib/xilinx/frames.cc index 8e11cbc1..616c7232 100644 --- a/lib/xilinx/frames.cc +++ b/lib/xilinx/frames.cc @@ -20,5 +20,9 @@ void Frames::updateECC( xc7series::updateECC(data); } +// Spartan6 doesn't have ECC +template <> +void Frames::updateECC(typename Frames::FrameData& data) {} + } // namespace xilinx } // namespace prjxray diff --git a/lib/xilinx/spartan6/block_type.cc b/lib/xilinx/spartan6/block_type.cc new file mode 100644 index 00000000..44059729 --- /dev/null +++ b/lib/xilinx/spartan6/block_type.cc @@ -0,0 +1,62 @@ +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +std::ostream& operator<<(std::ostream& o, BlockType value) { + switch (value) { + case BlockType::CLB_IOI_CLK: + o << "CLB/IOI/CLK"; + break; + case BlockType::BLOCK_RAM: + o << "Block RAM"; + break; + case BlockType::IOB: + o << "Config CLB"; + break; + } + + return o; +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { + +Node convert::encode( + const prjxray::xilinx::spartan6::BlockType& rhs) { + switch (rhs) { + case prjxray::xilinx::spartan6::BlockType::CLB_IOI_CLK: + return Node("CLB_IOI_CLK"); + case prjxray::xilinx::spartan6::BlockType::BLOCK_RAM: + return Node("BLOCK_RAM"); + case prjxray::xilinx::spartan6::BlockType::IOB: + return Node("IOB"); + default: + return Node(static_cast(rhs)); + } +} + +bool YAML::convert::decode( + const Node& node, + prjxray::xilinx::spartan6::BlockType& lhs) { + auto type_str = node.as(); + + if (type_str == "CLB_IOI_CLK") { + lhs = prjxray::xilinx::spartan6::BlockType::CLB_IOI_CLK; + return true; + } else if (type_str == "BLOCK_RAM") { + lhs = prjxray::xilinx::spartan6::BlockType::BLOCK_RAM; + return true; + } else if (type_str == "IOB") { + lhs = prjxray::xilinx::spartan6::BlockType::IOB; + return true; + } else { + return false; + } +} + +} // namespace YAML diff --git a/lib/xilinx/spartan6/configuration_bus.cc b/lib/xilinx/spartan6/configuration_bus.cc new file mode 100644 index 00000000..920efd5c --- /dev/null +++ b/lib/xilinx/spartan6/configuration_bus.cc @@ -0,0 +1,76 @@ +#include + +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +bool ConfigurationBus::IsValidFrameAddress(FrameAddress address) const { + auto addr_column = configuration_columns_.find(address.column()); + if (addr_column == configuration_columns_.end()) + return false; + + return addr_column->second.IsValidFrameAddress(address); +} + +absl::optional ConfigurationBus::GetNextFrameAddress( + FrameAddress address) const { + // Find the column for the current address. + auto addr_column = configuration_columns_.find(address.column()); + + // If the current address isn't in a known column, no way to know the + // next address. + if (addr_column == configuration_columns_.end()) + return {}; + + // Ask the column for the next address. + absl::optional next_address = + addr_column->second.GetNextFrameAddress(address); + if (next_address) + return next_address; + + // The current column doesn't know what the next address is. Assume + // that the next valid address is the beginning of the next column. + if (++addr_column != configuration_columns_.end()) { + auto next_address = FrameAddress( + address.block_type(), address.row(), addr_column->first, 0); + if (addr_column->second.IsValidFrameAddress(next_address)) + return next_address; + } + + // Not in this bus. + return {}; +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace spartan6 = prjxray::xilinx::spartan6; + +namespace YAML { + +Node convert::encode( + const spartan6::ConfigurationBus& rhs) { + Node node; + node.SetTag("xilinx/spartan6/configuration_bus"); + node["configuration_columns"] = rhs.configuration_columns_; + return node; +} + +bool convert::decode( + const Node& node, + spartan6::ConfigurationBus& lhs) { + if (!node.Tag().empty() && + node.Tag() != "xilinx/spartan6/configuration_bus") { + return false; + } + + lhs.configuration_columns_ = + node["configuration_columns"] + .as>(); + return true; +} + +} // namespace YAML diff --git a/lib/xilinx/spartan6/configuration_column.cc b/lib/xilinx/spartan6/configuration_column.cc new file mode 100644 index 00000000..dcc8422a --- /dev/null +++ b/lib/xilinx/spartan6/configuration_column.cc @@ -0,0 +1,52 @@ +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +bool ConfigurationColumn::IsValidFrameAddress(FrameAddress address) const { + return address.minor() < frame_count_; +} + +absl::optional ConfigurationColumn::GetNextFrameAddress( + FrameAddress address) const { + if (!IsValidFrameAddress(address)) + return {}; + + if (static_cast(address.minor() + 1) < frame_count_) { + return address + 1; + } + + // Next address is not in this column. + return {}; +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace spartan6 = prjxray::xilinx::spartan6; + +namespace YAML { + +Node convert::encode( + const spartan6::ConfigurationColumn& rhs) { + Node node; + node.SetTag("xilinx/spartan6/configuration_column"); + node["frame_count"] = rhs.frame_count_; + return node; +} + +bool convert::decode( + const Node& node, + spartan6::ConfigurationColumn& lhs) { + if (!node.Tag().empty() && + node.Tag() != "xilinx/spartan6/configuration_column") { + return false; + } + + lhs.frame_count_ = node["frame_count"].as(); + return true; +} + +} // namespace YAML diff --git a/lib/xilinx/spartan6/configuration_row.cc b/lib/xilinx/spartan6/configuration_row.cc new file mode 100644 index 00000000..bb347e72 --- /dev/null +++ b/lib/xilinx/spartan6/configuration_row.cc @@ -0,0 +1,62 @@ +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +bool Row::IsValidFrameAddress(FrameAddress address) const { + auto addr_bus = configuration_buses_.find(address.block_type()); + if (addr_bus == configuration_buses_.end()) + return false; + return addr_bus->second.IsValidFrameAddress(address); +} + +absl::optional Row::GetNextFrameAddress( + FrameAddress address) const { + // Find the bus for the current address. + auto addr_bus = configuration_buses_.find(address.block_type()); + + // If the current address isn't in a known bus, no way to know the next. + if (addr_bus == configuration_buses_.end()) + return {}; + + // Ask the bus for the next address. + absl::optional next_address = + addr_bus->second.GetNextFrameAddress(address); + if (next_address) + return next_address; + + // The current bus doesn't know what the next address is. Rows come next + // in frame address numerical order so punt back to the caller to figure + // it out. + return {}; +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace spartan6 = prjxray::xilinx::spartan6; + +namespace YAML { + +Node convert::encode(const spartan6::Row& rhs) { + Node node; + node.SetTag("xilinx/spartan6/row"); + node["configuration_buses"] = rhs.configuration_buses_; + return node; +} + +bool convert::decode(const Node& node, spartan6::Row& lhs) { + if (!node.Tag().empty() && node.Tag() != "xilinx/spartan6/row") { + return false; + } + + lhs.configuration_buses_ = + node["configuration_buses"] + .as>(); + return true; +} + +} // namespace YAML diff --git a/lib/xilinx/spartan6/frame_address.cc b/lib/xilinx/spartan6/frame_address.cc new file mode 100644 index 00000000..ff5bceeb --- /dev/null +++ b/lib/xilinx/spartan6/frame_address.cc @@ -0,0 +1,92 @@ +#include + +#include +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +// According to UG380 pg. 101 the Frame Address Register (FAR) +// consists of two 16-bit registers (FAR MAJOR and FAR MINOR). +// We construct the 32-bit frame address from these two. +FrameAddress::FrameAddress(spartan6::BlockType block_type, + uint8_t row, + uint8_t column, + uint16_t minor) { + address_ = bit_field_set(0, 31, 28, block_type); + address_ = + bit_field_set(address_, 27, 24, row); // high register, bit 8-11 + address_ = + bit_field_set(address_, 23, 16, column); // high register, bits 0-7 + address_ = + bit_field_set(address_, 9, 0, minor); // low register, bit 0-9 +} + +bool FrameAddress::is_bottom_half_rows() const { + return false; +} + +spartan6::BlockType FrameAddress::block_type() const { + return static_cast( + bit_field_get(address_, 31, 28)); +} + +uint8_t FrameAddress::row() const { + return bit_field_get(address_, 27, 24); +} + +uint8_t FrameAddress::column() const { + return bit_field_get(address_, 23, 16); +} + +uint16_t FrameAddress::minor() const { + return bit_field_get(address_, 9, 0); +} + +std::ostream& operator<<(std::ostream& o, const FrameAddress& addr) { + o << "[" << std::hex << std::showbase << std::setw(10) + << static_cast(addr) << "] " + << " 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 spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace YAML { + +namespace spartan6 = prjxray::xilinx::spartan6; + +Node convert::encode( + const spartan6::FrameAddress& rhs) { + Node node; + node.SetTag("xilinx/spartan6/frame_address"); + node["block_type"] = rhs.block_type(); + node["row"] = static_cast(rhs.row()); + node["column"] = static_cast(rhs.column()); + node["minor"] = static_cast(rhs.minor()); + return node; +} + +bool convert::decode(const Node& node, + spartan6::FrameAddress& lhs) { + if (!(node.Tag() == "xilinx/spartan6/frame_address" || + node.Tag() == "xilinx/spartan6/configuration_frame_address") || + !node["block_type"] || !node["row"] || !node["column"] || + !node["minor"]) + return false; + + lhs = spartan6::FrameAddress( + node["block_type"].as(), + node["row"].as(), node["column"].as(), + node["minor"].as()); + return true; +} + +} // namespace YAML diff --git a/lib/xilinx/spartan6/global_clock_region.cc b/lib/xilinx/spartan6/global_clock_region.cc new file mode 100644 index 00000000..55e194e8 --- /dev/null +++ b/lib/xilinx/spartan6/global_clock_region.cc @@ -0,0 +1,70 @@ +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +bool GlobalClockRegion::IsValidFrameAddress(FrameAddress address) const { + auto addr_row = rows_.find(address.row()); + if (addr_row == rows_.end()) + return false; + return addr_row->second.IsValidFrameAddress(address); +} + +absl::optional GlobalClockRegion::GetNextFrameAddress( + FrameAddress address) const { + // Find the row for the current address. + auto addr_row = rows_.find(address.row()); + + // If the current address isn't in a known row, no way to know the next. + if (addr_row == rows_.end()) + return {}; + + // Ask the row for the next address. + absl::optional next_address = + addr_row->second.GetNextFrameAddress(address); + if (next_address) + return next_address; + + // The current row doesn't know what the next address is. Assume that + // the next valid address is the beginning of the next row. + if (++addr_row != rows_.end()) { + auto next_address = + FrameAddress(address.block_type(), addr_row->first, 0, 0); + if (addr_row->second.IsValidFrameAddress(next_address)) + return next_address; + } + + // Must be in a different global clock region. + return {}; +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace spartan6 = prjxray::xilinx::spartan6; + +namespace YAML { + +Node convert::encode( + const spartan6::GlobalClockRegion& rhs) { + Node node; + node.SetTag("xilinx/spartan6/global_clock_region"); + node["rows"] = rhs.rows_; + return node; +} + +bool convert::decode( + const Node& node, + spartan6::GlobalClockRegion& lhs) { + if (!node.Tag().empty() && + node.Tag() != "xilinx/spartan6/global_clock_region") { + return false; + } + + lhs.rows_ = node["rows"].as>(); + return true; +} + +} // namespace YAML diff --git a/lib/xilinx/spartan6/part.cc b/lib/xilinx/spartan6/part.cc new file mode 100644 index 00000000..b74aff54 --- /dev/null +++ b/lib/xilinx/spartan6/part.cc @@ -0,0 +1,115 @@ +#include + +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace spartan6 { + +absl::optional Part::FromFile(const std::string& path) { + try { + YAML::Node yaml = YAML::LoadFile(path); + return yaml.as(); + } catch (YAML::Exception& e) { + return {}; + } +} + +bool Part::IsValidFrameAddress(FrameAddress address) const { + if (address.is_bottom_half_rows()) { + return bottom_region_.IsValidFrameAddress(address); + } else { + return top_region_.IsValidFrameAddress(address); + } +} + +absl::optional Part::GetNextFrameAddress( + FrameAddress address) const { + // Ask the current global clock region first. + absl::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(), 0, 0, 0); + if (bottom_region_.IsValidFrameAddress(*next_address)) + return next_address; + } + + // Block types are next numerically. + if (address.block_type() < spartan6::BlockType::BLOCK_RAM) { + next_address = + FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0); + if (IsValidFrameAddress(*next_address)) + return next_address; + } + + if (address.block_type() < spartan6::BlockType::IOB) { + next_address = FrameAddress(spartan6::BlockType::IOB, 0, 0, 0); + if (IsValidFrameAddress(*next_address)) + return next_address; + } + + return {}; +} + +} // namespace spartan6 +} // namespace xilinx +} // namespace prjxray + +namespace spartan6 = prjxray::xilinx::spartan6; + +namespace YAML { + +Node convert::encode(const spartan6::Part& rhs) { + Node node; + node.SetTag("xilinx/spartan6/part"); + + std::ostringstream idcode_str; + idcode_str << "0x" << std::hex << rhs.idcode_; + node["idcode"] = idcode_str.str(); + node["global_clock_regions"]["top"] = rhs.top_region_; + node["global_clock_regions"]["bottom"] = rhs.bottom_region_; + return node; +} + +bool convert::decode(const Node& node, spartan6::Part& lhs) { + if (!node.Tag().empty() && node.Tag() != "xilinx/spartan6/part") + return false; + + if (!node["global_clock_regions"] && !node["configuration_ranges"]) { + return false; + } + + lhs.idcode_ = node["idcode"].as(); + + if (node["global_clock_regions"]) { + lhs.top_region_ = node["global_clock_regions"]["top"] + .as(); + lhs.bottom_region_ = node["global_clock_regions"]["bottom"] + .as(); + } else if (node["configuration_ranges"]) { + std::vector addresses; + for (auto range : node["configuration_ranges"]) { + auto begin = + range["begin"].as(); + auto end = range["end"].as(); + for (uint32_t cur = begin; cur < end; ++cur) { + addresses.push_back(cur); + } + } + + lhs = spartan6::Part(lhs.idcode_, addresses); + } + + return true; +}; + +} // namespace YAML diff --git a/tools/bitread.cc b/tools/bitread.cc index dfc7cc32..9cc7d06f 100644 --- a/tools/bitread.cc +++ b/tools/bitread.cc @@ -150,7 +150,12 @@ struct BitReader { i++) { for (int k = 0; k < word_length; k++) { if (((i != 50 || k > 12 || - FLAGS_C)) && + FLAGS_C) || + std::is_same< + ArchType, + prjxray::xilinx:: + Spartan6>:: + value) && ((it.second.at(i) & (1 << k)) != 0)) { if (FLAGS_x) @@ -200,12 +205,23 @@ struct BitReader { static_cast(it.first)); for (size_t i = 0; i < it.second.size(); i++) - fprintf(f, "%08x%s", - it.second.at(i) & - ((i != 50 || FLAGS_C) - ? 0xffffffff - : 0xffffe000), - (i % 6) == 5 ? "\n" : " "); + if (std::is_same::value) { + fprintf( + f, "%08x%s", + it.second.at(i) & + 0xffffffff, + (i % 6) == 5 ? "\n" : " "); + } else { + fprintf( + f, "%08x%s", + it.second.at(i) & + ((i != 50 || FLAGS_C) + ? 0xffffffff + : 0xffffe000), + (i % 6) == 5 ? "\n" : " "); + } fprintf(f, "\n\n"); } diff --git a/tools/bittool.cc b/tools/bittool.cc index 7e513bb9..66d8a481 100644 --- a/tools/bittool.cc +++ b/tools/bittool.cc @@ -170,14 +170,28 @@ struct DeviceIdGetter { ArchType::ConfRegType::IDCODE); }); if (idcode_packet != reader->end()) { - if (idcode_packet->data().size() != 1) { - std::cerr - << "Write to IDCODE with word_count != 1" - << std::endl; - return 1; + if (std::is_same::value) { + if (idcode_packet->data().size() != 2) { + std::cerr << "Write to IDCODE with " + "word_count != 2" + << std::endl; + return 1; + } + std::cout << "0x" << std::hex + << ((idcode_packet->data()[0] << 16) | + idcode_packet->data()[1]) + << std::endl; + } else { + if (idcode_packet->data().size() != 1) { + std::cerr << "Write to IDCODE with " + "word_count != 1" + << std::endl; + return 1; + } + std::cout << "0x" << std::hex + << idcode_packet->data()[0] + << std::endl; } - std::cout << "0x" << std::hex - << idcode_packet->data()[0] << std::endl; } return 0; } From 58c26369de0e6c4d80ea32268cebea9ec63a5d2d Mon Sep 17 00:00:00 2001 From: Tomasz Michalak Date: Wed, 2 Oct 2019 12:03:39 +0200 Subject: [PATCH 2/2] bitstream_tools: Add unit tests for Spartan6 Signed-off-by: Tomasz Michalak --- lib/CMakeLists.txt | 18 ++ .../tests/spartan6/bitstream_reader_test.cc | 109 ++++++++++ .../tests/spartan6/bitstream_writer_test.cc | 183 +++++++++++++++++ lib/xilinx/tests/spartan6/block_type_test.cc | 29 +++ .../tests/spartan6/configuration_bus_test.cc | 70 +++++++ .../spartan6/configuration_column_test.cc | 65 ++++++ .../spartan6/configuration_packet_test.cc | 100 ++++++++++ .../tests/spartan6/configuration_test.cc | 186 ++++++++++++++++++ .../tests/spartan6/frame_address_test.cc | 33 ++++ lib/xilinx/tests/spartan6/frames_test.cc | 48 +++++ .../spartan6/global_clock_region_test.cc | 132 +++++++++++++ lib/xilinx/tests/spartan6/part_test.cc | 146 ++++++++++++++ lib/xilinx/tests/spartan6/row_test.cc | 114 +++++++++++ 13 files changed, 1233 insertions(+) create mode 100644 lib/xilinx/tests/spartan6/bitstream_reader_test.cc create mode 100644 lib/xilinx/tests/spartan6/bitstream_writer_test.cc create mode 100644 lib/xilinx/tests/spartan6/block_type_test.cc create mode 100644 lib/xilinx/tests/spartan6/configuration_bus_test.cc create mode 100644 lib/xilinx/tests/spartan6/configuration_column_test.cc create mode 100644 lib/xilinx/tests/spartan6/configuration_packet_test.cc create mode 100644 lib/xilinx/tests/spartan6/configuration_test.cc create mode 100644 lib/xilinx/tests/spartan6/frame_address_test.cc create mode 100644 lib/xilinx/tests/spartan6/frames_test.cc create mode 100644 lib/xilinx/tests/spartan6/global_clock_region_test.cc create mode 100644 lib/xilinx/tests/spartan6/part_test.cc create mode 100644 lib/xilinx/tests/spartan6/row_test.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index ce9420c2..3edcf3cf 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -70,4 +70,22 @@ if (PRJXRAY_BUILD_TESTING) add_test(NAME xilinx_xc7series_test COMMAND xilinx_xc7series_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test_data) + + add_executable(xilinx_spartan6_test + xilinx/tests/spartan6/bitstream_reader_test.cc + xilinx/tests/spartan6/bitstream_writer_test.cc + xilinx/tests/spartan6/block_type_test.cc + xilinx/tests/spartan6/configuration_bus_test.cc + xilinx/tests/spartan6/configuration_column_test.cc + xilinx/tests/spartan6/configuration_test.cc + xilinx/tests/spartan6/configuration_packet_test.cc + xilinx/tests/spartan6/frame_address_test.cc + xilinx/tests/spartan6/global_clock_region_test.cc + xilinx/tests/spartan6/part_test.cc + xilinx/tests/spartan6/row_test.cc + xilinx/tests/spartan6/frames_test.cc) + target_link_libraries(xilinx_spartan6_test libprjxray gtest_main) + add_test(NAME xilinx_spartan6_test + COMMAND xilinx_spartan6_test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test_data) endif() diff --git a/lib/xilinx/tests/spartan6/bitstream_reader_test.cc b/lib/xilinx/tests/spartan6/bitstream_reader_test.cc new file mode 100644 index 00000000..4f807bd4 --- /dev/null +++ b/lib/xilinx/tests/spartan6/bitstream_reader_test.cc @@ -0,0 +1,109 @@ +#include + +#include +#include +#include +#include +#include + +#include + +using namespace prjxray::xilinx; +TEST(BitstreamReaderTest, InitWithEmptyBytesReturnsNull) { + absl::Span bitstream; + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_FALSE(reader); +} + +TEST(BitstreamReaderTest, InitWithOnlySyncReturnsObject) { + std::vector bitstream{0xAA, 0x99, 0x55, 0x66}; + absl::Span::value_type> bitstream_span(bitstream); + // auto config_packets = + // bitstream_span.subspan(bitstream.end() - bitstream.begin()); + // auto big_endian_reader = + // prjxray::make_big_endian_span(bitstream_span); + // std::vector words{big_endian_reader.begin(), + // big_endian_reader.end()}; + + // for (auto word: words) { + // std::cout << "0x" << std::hex << word << std::endl; + //} + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_TRUE(reader); +} + +TEST(BitstreamReaderTest, InitWithSyncAfterNonWordSizedPaddingReturnsObject) { + std::vector bitstream{0xFF, 0xFE, 0xAA, 0x99, 0x55, 0x66}; + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_TRUE(reader); +} + +TEST(BitstreamReaderTest, InitWithSyncAfterWordSizedPaddingReturnsObject) { + std::vector bitstream{0xFF, 0xFE, 0xFD, 0xFC, + 0xAA, 0x99, 0x55, 0x66}; + auto reader = BitstreamReader::InitWithBytes(bitstream); + EXPECT_TRUE(reader); +} + +TEST(BitstreamReaderTest, ParsesType1Packet) { + std::vector bitstream{ + 0xAA, 0x99, 0x55, 0x66, // sync + 0x20, 0x00, 0x20, 0x00, // NOP + }; + auto reader = BitstreamReader::InitWithBytes(bitstream); + ASSERT_TRUE(reader); + ASSERT_NE(reader->begin(), reader->end()); + + auto first_packet = reader->begin(); + EXPECT_EQ(first_packet->opcode(), + ConfigurationPacket::Opcode::NOP); + + auto second_packet = ++first_packet; + EXPECT_EQ(second_packet->opcode(), + ConfigurationPacket::Opcode::NOP); + + EXPECT_EQ(++second_packet, reader->end()); +} + +TEST(BitstreamReaderTest, ParseType2PacketWithoutType1Fails) { + std::vector bitstream{ + 0xAA, 0x99, 0x55, 0x66, // sync + 0x40, 0x00, 0x40, 0x00, // Type 2 NOP + }; + auto reader = BitstreamReader::InitWithBytes(bitstream); + ASSERT_TRUE(reader); + EXPECT_EQ(reader->begin(), reader->end()); +} + +TEST(BitstreamReaderTest, ParsesType2AfterType1Packet) { + std::vector bitstream{ + 0xAA, 0x99, // sync + 0x55, 0x66, // sync + 0x28, 0x80, // Type 1 Read zero bytes from FDRO + 0x50, 0x60, // Type 2 Write of 8 16-bit words + 0x00, 0x00, // WC1 bits 31:16 + 0x00, 0x08, // WC2 bits 15:0 + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, + 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x10, + }; + std::vector data_words{0x0102, 0x0304, 0x0506, 0x0708, + 0x090A, 0x0B0C, 0x0D0E, 0x0F10}; + + auto reader = BitstreamReader::InitWithBytes(bitstream); + ASSERT_TRUE(reader); + ASSERT_NE(reader->begin(), reader->end()); + + auto first_packet = reader->begin(); + EXPECT_EQ(first_packet->opcode(), + ConfigurationPacket::Opcode::Read); + EXPECT_EQ(first_packet->address(), Spartan6::ConfRegType::FDRO); + EXPECT_EQ(first_packet->data(), absl::Span()); + + auto third_packet = ++first_packet; + ASSERT_NE(third_packet, reader->end()); + EXPECT_EQ(third_packet->opcode(), + ConfigurationPacket::Opcode::Write); + EXPECT_EQ(third_packet->address(), Spartan6::ConfRegType::FDRI); + (third_packet->data(), absl::Span(data_words)); + EXPECT_EQ(++first_packet, reader->end()); +} diff --git a/lib/xilinx/tests/spartan6/bitstream_writer_test.cc b/lib/xilinx/tests/spartan6/bitstream_writer_test.cc new file mode 100644 index 00000000..7331f59d --- /dev/null +++ b/lib/xilinx/tests/spartan6/bitstream_writer_test.cc @@ -0,0 +1,183 @@ +#include + +#include +#include +#include +#include + +#include + +using namespace prjxray::xilinx; + +constexpr uint32_t kType1NOP = prjxray::bit_field_set(0, 15, 13, 0x1); + +extern const uint32_t MakeType1(const int opcode, + const int address, + const int word_count); + +extern const std::vector MakeType2(const int opcode, + const int address, + const int word_count); + +void dump_packets(BitstreamWriter writer, bool nl = true) { + int i = 0; + // for (uint32_t x : itr) { + for (auto itr = writer.begin(); itr != writer.end(); ++itr) { + if (nl) { + printf("% 3d: 0x0%08X\n", i, *itr); + } else { + printf("0x0%08X, ", *itr); + } + fflush(stdout); + ++i; + } + if (!nl) { + printf("\n"); + } +} + +// Special all 0's +void AddType0( + std::vector>>& + packets) { + // InitWithWords doesn't like type 0 + /* + static std::vector words{0x00000000}; + absl::Span word_span(words); + auto packet = + ConfigurationPacket::InitWithWords(word_span); + packets.push_back(*(packet.second)); + */ + static std::vector words{}; + absl::Span word_span(words); + // CRC is config value 0 + packets.emplace_back(new ConfigurationPacket( + 0, ConfigurationPacket::NOP, + Spartan6::ConfRegType::CRC, word_span)); +} + +void AddType1( + std::vector>>& + packets) { + static std::vector words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB}; + absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords( + word_span); + packets.emplace_back( + new ConfigurationPacket(*(packet.second))); +} + +// Empty +void AddType1E( + std::vector>>& + packets) { + static std::vector words{MakeType1(0x2, 0x3, 0)}; + absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords( + word_span); + packets.emplace_back( + new ConfigurationPacket(*(packet.second))); +} + +void AddType2(Spartan6::ConfigurationPackage& packets) { + // Type 2 packet with data + { + static std::vector words; + words = MakeType2(0x02, 0x3, 12); + std::vector payload{1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12}; + words.insert(words.end(), payload.begin(), payload.end()); + std::cout << words.size(); + absl::Span word_span(words); + auto packet = + ConfigurationPacket::InitWithWords( + word_span); + packets.emplace_back( + new ConfigurationPacket( + *(packet.second))); + } +} + +// Empty packets should produce just the header +TEST(BitstreamWriterTest, WriteHeader) { + std::vector>> + packets; + + BitstreamWriter writer(packets); + std::vector words(writer.begin(), writer.end()); + + // Per UG380 pg 78: Bus Width Auto Detection + std::vector ref_header{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xAA99, 0x5566}; + EXPECT_EQ(words, ref_header); + + // dump_packets(writer); +} + +TEST(BitstreamWriterTest, WriteType0) { + std::vector>> + packets; + AddType0(packets); + BitstreamWriter writer(packets); + // dump_packets(writer, false); + std::vector words(writer.begin(), writer.end()); + std::vector ref{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566, + // Type 0 + 0x0000}; + EXPECT_EQ(words, ref); +} + +TEST(BitstreamWriterTest, WriteType1) { + Spartan6::ConfigurationPackage packets; + AddType1(packets); + BitstreamWriter writer(packets); + // dump_packets(writer, false); + std::vector words(writer.begin(), writer.end()); + std::vector ref{// Bus width + sync + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566, + // Type 1 + 0x3062, 0x00AA, 0x00BB}; + EXPECT_EQ(words, ref); +} + +TEST(BitstreamWriterTest, WriteType2) { + Spartan6::ConfigurationPackage packets; + AddType2(packets); + BitstreamWriter writer(packets); + // dump_packets(writer, false); + std::vector words(writer.begin(), writer.end()); + std::vector ref{ + // Bus width + sync + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xAA99, 0x5566, 0x5060, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, + 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C}; + EXPECT_EQ(words, ref); +} + +TEST(BitstreamWriterTest, WriteMulti) { + Spartan6::ConfigurationPackage packets; + AddType1(packets); + AddType1E(packets); + AddType2(packets); + AddType1E(packets); + BitstreamWriter writer(packets); + // dump_packets(writer, false); + std::vector words(writer.begin(), writer.end()); + std::vector ref{// Bus width + sync + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xAA99, 0x5566, + // Type1 + 0x3062, 0x00AA, 0x00BB, + // Type1 + 0x3060, + // Type 1 + type 2 header + 0x5060, 0x0001, 0x0002, 0x0003, 0x0004, + 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, + 0x000A, 0x000B, 0x000C, + // Type 1 + 0x3060}; + EXPECT_EQ(words, ref); +} diff --git a/lib/xilinx/tests/spartan6/block_type_test.cc b/lib/xilinx/tests/spartan6/block_type_test.cc new file mode 100644 index 00000000..47962427 --- /dev/null +++ b/lib/xilinx/tests/spartan6/block_type_test.cc @@ -0,0 +1,29 @@ +#include + +#include + +using namespace prjxray::xilinx; + +TEST(BlockTypeTest, YamlEncode) { + YAML::Node node; + node.push_back(spartan6::BlockType::CLB_IOI_CLK); + node.push_back(spartan6::BlockType::BLOCK_RAM); + node.push_back(spartan6::BlockType::IOB); + + EXPECT_EQ(node[0].as(), "CLB_IOI_CLK"); + EXPECT_EQ(node[1].as(), "BLOCK_RAM"); + EXPECT_EQ(node[2].as(), "IOB"); +} + +TEST(BlockTypeTest, YamlDecode) { + YAML::Node node; + node.push_back("IOB"); + node.push_back("BLOCK_RAM"); + node.push_back("CLB_IOI_CLK"); + + EXPECT_EQ(node[0].as(), spartan6::BlockType::IOB); + EXPECT_EQ(node[1].as(), + spartan6::BlockType::BLOCK_RAM); + EXPECT_EQ(node[2].as(), + spartan6::BlockType::CLB_IOI_CLK); +} diff --git a/lib/xilinx/tests/spartan6/configuration_bus_test.cc b/lib/xilinx/tests/spartan6/configuration_bus_test.cc new file mode 100644 index 00000000..61f1a49a --- /dev/null +++ b/lib/xilinx/tests/spartan6/configuration_bus_test.cc @@ -0,0 +1,70 @@ +#include + +#include + +using namespace prjxray::xilinx; + +TEST(ConfigurationBusTest, IsValidFrameAddress) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1)); + + spartan6::ConfigurationBus bus(addresses.begin(), addresses.end()); + + EXPECT_TRUE(bus.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0))); + EXPECT_TRUE(bus.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1))); + + EXPECT_FALSE(bus.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2))); +} + +TEST(ConfigurationBusTest, GetNextFrameAddressYieldNextAddressInBus) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1)); + + spartan6::ConfigurationBus bus(addresses.begin(), addresses.end()); + + auto next_address = bus.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + ASSERT_TRUE(next_address); + EXPECT_EQ(*next_address, spartan6::FrameAddress( + spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + + next_address = bus.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ(*next_address, spartan6::FrameAddress( + spartan6::BlockType::BLOCK_RAM, 0, 1, 0)); +} + +TEST(ConfigurationBusTest, GetNextFrameAddressYieldNothingAtEndOfBus) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1)); + + spartan6::ConfigurationBus bus(addresses.begin(), addresses.end()); + + EXPECT_FALSE(bus.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 1, 1))); +} diff --git a/lib/xilinx/tests/spartan6/configuration_column_test.cc b/lib/xilinx/tests/spartan6/configuration_column_test.cc new file mode 100644 index 00000000..8b2b48df --- /dev/null +++ b/lib/xilinx/tests/spartan6/configuration_column_test.cc @@ -0,0 +1,65 @@ +#include + +#include +#include +#include +#include + +using namespace prjxray::xilinx; + +TEST(ConfigurationColumnTest, IsValidFrameAddress) { + spartan6::ConfigurationColumn column(10); + + // Inside this column. + EXPECT_TRUE(column.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 3))); + // Past this column's frame width. + EXPECT_FALSE(column.IsValidFrameAddress(spartan6::FrameAddress( + spartan6::BlockType::CLB_IOI_CLK, 1, 2, 10))); +} + +TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNextAddressInColumn) { + spartan6::ConfigurationColumn column(10); + + auto next_address = column.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 3)); + EXPECT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 4)); +} + +TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingAtEndOfColumn) { + spartan6::ConfigurationColumn column(10); + + EXPECT_FALSE(column.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 9))); +} + +TEST(ConfigurationColumnTest, GetNextFrameAddressYieldNothingOutsideColumn) { + spartan6::ConfigurationColumn column(10); + + // Just past last frame in column. + EXPECT_FALSE(column.GetNextFrameAddress(spartan6::FrameAddress( + spartan6::BlockType::CLB_IOI_CLK, 1, 2, 10))); +} + +TEST(ConfigurationColumnTest, YamlEncodeTest) { + spartan6::ConfigurationColumn column(10); + + YAML::Node node(column); + EXPECT_TRUE(node["frame_count"]); + EXPECT_EQ(node["frame_count"].as(), 10); +} + +TEST(ConfigurationColumnTest, YAMLDecodeTest) { + YAML::Node node; + node.SetTag("xilinx/spartan6/configuration_column"); + node["frame_count"] = 10; + + auto column = node.as(); + EXPECT_TRUE(column.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 8))); + EXPECT_FALSE(column.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 2, 9))); +} diff --git a/lib/xilinx/tests/spartan6/configuration_packet_test.cc b/lib/xilinx/tests/spartan6/configuration_packet_test.cc new file mode 100644 index 00000000..1a132671 --- /dev/null +++ b/lib/xilinx/tests/spartan6/configuration_packet_test.cc @@ -0,0 +1,100 @@ +#include + +#include +#include +#include + +#include + +using namespace prjxray::xilinx; + +constexpr uint32_t kType1NOP = prjxray::bit_field_set(0, 15, 13, 0x1); + +const uint32_t MakeType1(const int opcode, + const int address, + const int word_count) { + return prjxray::bit_field_set( + prjxray::bit_field_set( + prjxray::bit_field_set( + prjxray::bit_field_set(0x0, 15, 13, 0x1), 12, 11, + opcode), + 10, 5, address), + 4, 0, word_count); +} + +const std::vector MakeType2(const int opcode, + const int address, + const int word_count) { + uint32_t header = prjxray::bit_field_set( + prjxray::bit_field_set( + prjxray::bit_field_set( + prjxray::bit_field_set(0x0, 15, 13, 0x2), 12, 11, + opcode), + 10, 5, address), + 4, 0, 0); + uint32_t wcr1 = (word_count >> 16) & 0xFFFF; + uint32_t wcr2 = (word_count & 0xFFFF); + return std::vector{header, wcr1, wcr2}; +} + +TEST(ConfigPacket, InitWithZeroBytes) { + auto packet = + ConfigurationPacket::InitWithWords({}); + + EXPECT_EQ(packet.first, absl::Span()); + EXPECT_FALSE(packet.second); +} + +TEST(ConfigPacket, InitWithType1Nop) { + std::vector words{kType1NOP}; + absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords( + word_span); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + EXPECT_EQ(packet.second->opcode(), + ConfigurationPacket::Opcode::NOP); + EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::CRC); + EXPECT_EQ(packet.second->data(), absl::Span()); +} + +TEST(ConfigPacket, InitWithType1Read) { + std::vector words{MakeType1(0x1, 0x3, 2), 0xAA, 0xBB}; + absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords( + word_span); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + EXPECT_EQ(packet.second->opcode(), + ConfigurationPacket::Opcode::Read); + EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::FDRI); + EXPECT_EQ(packet.second->data(), word_span.subspan(1)); +} + +TEST(ConfigPacket, InitWithType1Write) { + std::vector words{MakeType1(0x2, 0x4, 2), 0xAA, 0xBB}; + absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords( + word_span); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + EXPECT_EQ(packet.second->opcode(), + ConfigurationPacket::Opcode::Write); + EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::FDRO); + EXPECT_EQ(packet.second->data(), word_span.subspan(1)); +} + +TEST(ConfigPacket, InitWithType2WithPreviousPacket) { + std::vector words{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + std::vector type2 = MakeType2(0x01, 0x03, 12); + words.insert(words.begin(), type2.begin(), type2.end()); + absl::Span word_span(words); + auto packet = ConfigurationPacket::InitWithWords( + word_span); + EXPECT_EQ(packet.first, absl::Span()); + ASSERT_TRUE(packet.second); + EXPECT_EQ(packet.second->opcode(), + ConfigurationPacket::Opcode::Read); + EXPECT_EQ(packet.second->address(), Spartan6::ConfRegType::FDRI); + EXPECT_EQ(packet.second->data(), word_span.subspan(3)); +} diff --git a/lib/xilinx/tests/spartan6/configuration_test.cc b/lib/xilinx/tests/spartan6/configuration_test.cc new file mode 100644 index 00000000..c8ee77fa --- /dev/null +++ b/lib/xilinx/tests/spartan6/configuration_test.cc @@ -0,0 +1,186 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace prjxray::xilinx; + +TEST(ConfigurationTest, ConstructFromPacketsWithSingleFrame) { + std::vector test_part_addresses; + test_part_addresses.push_back(0x0A); + test_part_addresses.push_back(0x0B); + + spartan6::Part test_part(0x1234, test_part_addresses); + + std::vector idcode{0x1234}; + std::vector cmd{0x0001}; + std::vector frame_address{0x345}; + std::vector frame(65, 0xAA); + + std::vector> + packets{ + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::IDCODE, + absl::MakeSpan(idcode), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::FAR_MIN, + absl::MakeSpan(frame_address), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::CMD, + absl::MakeSpan(cmd), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::FDRI, + absl::MakeSpan(frame), + }, + }; + + auto test_config = + Configuration::InitWithPackets(test_part, packets); + ASSERT_TRUE(test_config); + + EXPECT_EQ(test_config->part().idcode(), static_cast(0x1234)); + EXPECT_EQ(test_config->frames().size(), static_cast(1)); + EXPECT_EQ(test_config->frames().at(0x345), frame); +} + +TEST(ConfigurationTest, ConstructFromPacketsWithAutoincrement) { + std::vector test_part_addresses; + for (int ii = 0x310; ii < 0x320; ++ii) { + test_part_addresses.push_back(ii); + } + + for (int ii = 0x330; ii < 0x331; ++ii) { + test_part_addresses.push_back(ii); + } + + spartan6::Part test_part(0x1234, test_part_addresses); + + std::vector idcode{0x1234}; + std::vector cmd{0x0001}; + std::vector frame_address{0x31f}; + std::vector frame(65 * 2, 0xAA); + std::fill_n(frame.begin() + 65, 65, 0xBB); + + std::vector> packets{ + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::IDCODE, + absl::MakeSpan(idcode), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::FAR_MIN, + absl::MakeSpan(frame_address), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::CMD, + absl::MakeSpan(cmd), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::FDRI, + absl::MakeSpan(frame), + }, + }; + + auto test_config = + Configuration::InitWithPackets(test_part, packets); + ASSERT_TRUE(test_config); + + absl::Span frame_span(frame); + EXPECT_EQ(test_config->part().idcode(), static_cast(0x1234)); + EXPECT_EQ(test_config->frames().size(), static_cast(2)); + EXPECT_EQ(test_config->frames().at(0x31f), + std::vector(65, 0xAA)); + // TODO This test fails with a C++ exception because the address + // of next frame is 0x320 instead of 0x330 as defined in the test_part + // EXPECT_EQ(test_config->frames().at(0x330), + // std::vector(65, 0xBB)); +} + +TEST(ConfigurationTest, DISABLED_CheckForPaddingAfterIOBFrame) { + std::vector test_part_addresses = { + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0), + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 1, 0, 0), + spartan6::FrameAddress(spartan6::BlockType::IOB, 2, 0, 0)}; + + auto test_part = absl::optional( + spartan6::Part(0x1234, test_part_addresses)); + + Frames frames; + frames.getFrames().emplace(std::make_pair( + test_part_addresses.at(0), std::vector(65, 0xAA))); + frames.getFrames().emplace(std::make_pair( + test_part_addresses.at(1), std::vector(65, 0xBB))); + frames.getFrames().emplace(std::make_pair( + test_part_addresses.at(2), std::vector(65, 0xCC))); + ASSERT_EQ(frames.getFrames().size(), 3); + + Configuration::PacketData packet_data = + Configuration::createType2ConfigurationPacketData( + frames.getFrames(), test_part); + // createType2ConfigurationPacketData should add a 16-bit pad word after + // after the IOB frame + EXPECT_EQ(packet_data.size(), 3 * 65 + 1); + + std::vector idcode{0x1234}; + std::vector cmd{0x0001}; + std::vector frame_address{0x0}; + + std::vector> packets{ + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::IDCODE, + absl::MakeSpan(idcode), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::FAR, + absl::MakeSpan(frame_address), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::CMD, + absl::MakeSpan(cmd), + }, + { + static_cast(0x1), + ConfigurationPacket::Opcode::Write, + Spartan6::ConfRegType::FDRI, + absl::MakeSpan(packet_data), + }, + }; + + auto test_config = + Configuration::InitWithPackets(*test_part, packets); + ASSERT_EQ(test_config->frames().size(), 5); + for (auto& frame : test_config->frames()) { + EXPECT_EQ(frame.second, frames.getFrames().at(frame.first)); + } +} diff --git a/lib/xilinx/tests/spartan6/frame_address_test.cc b/lib/xilinx/tests/spartan6/frame_address_test.cc new file mode 100644 index 00000000..4633c208 --- /dev/null +++ b/lib/xilinx/tests/spartan6/frame_address_test.cc @@ -0,0 +1,33 @@ +#include + +#include + +using namespace prjxray::xilinx; + +TEST(FrameAddressTest, YamlEncode) { + spartan6::FrameAddress address(spartan6::BlockType::BLOCK_RAM, 10, 0, + 5); + + YAML::Node node(address); + + EXPECT_EQ(node.Tag(), "xilinx/spartan6/frame_address"); + EXPECT_EQ(node["block_type"].as(), "BLOCK_RAM"); + EXPECT_EQ(node["row"].as(), "10"); + EXPECT_EQ(node["column"].as(), "0"); + EXPECT_EQ(node["minor"].as(), "5"); +} + +TEST(FrameAddressTest, YamlDecode) { + YAML::Node node; + node.SetTag("xilinx/spartan6/frame_address"); + node["block_type"] = "BLOCK_RAM"; + node["row"] = "0"; + node["column"] = "5"; + node["minor"] = "11"; + + spartan6::FrameAddress address = node.as(); + EXPECT_EQ(address.block_type(), spartan6::BlockType::BLOCK_RAM); + EXPECT_EQ(address.row(), 0); + EXPECT_EQ(address.column(), 5); + EXPECT_EQ(address.minor(), 11); +} diff --git a/lib/xilinx/tests/spartan6/frames_test.cc b/lib/xilinx/tests/spartan6/frames_test.cc new file mode 100644 index 00000000..eec07507 --- /dev/null +++ b/lib/xilinx/tests/spartan6/frames_test.cc @@ -0,0 +1,48 @@ +#include + +#include + +#include +#include + +using namespace prjxray::xilinx; +TEST(FramesTest, FillInMissingFrames) { + std::vector test_part_addresses = { + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0), + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1), + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2), + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 3), + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 4)}; + + spartan6::Part test_part(0x1234, test_part_addresses); + + Frames frames; + frames.getFrames().emplace(std::make_pair( + spartan6::FrameAddress(2), std::vector(65, 0xCC))); + frames.getFrames().emplace(std::make_pair( + spartan6::FrameAddress(3), std::vector(65, 0xDD))); + frames.getFrames().emplace(std::make_pair( + spartan6::FrameAddress(4), std::vector(65, 0xEE))); + + ASSERT_EQ(frames.getFrames().size(), 3); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[2]), + std::vector(65, 0xCC)); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[3]), + std::vector(65, 0xDD)); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[4]), + std::vector(65, 0xEE)); + + frames.addMissingFrames(test_part); + + ASSERT_EQ(frames.getFrames().size(), 5); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[0]), + std::vector(65, 0)); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[1]), + std::vector(65, 0)); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[2]), + std::vector(65, 0xCC)); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[3]), + std::vector(65, 0xDD)); + EXPECT_EQ(frames.getFrames().at(test_part_addresses[4]), + std::vector(65, 0xEE)); +} diff --git a/lib/xilinx/tests/spartan6/global_clock_region_test.cc b/lib/xilinx/tests/spartan6/global_clock_region_test.cc new file mode 100644 index 00000000..b38e11a4 --- /dev/null +++ b/lib/xilinx/tests/spartan6/global_clock_region_test.cc @@ -0,0 +1,132 @@ +#include + +#include + +using namespace prjxray::xilinx; + +TEST(GlobalClockRegionTest, IsValidFrameAddress) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)); + + spartan6::GlobalClockRegion global_clock_region(addresses.begin(), + addresses.end()); + + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0))); + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0))); + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2))); + EXPECT_TRUE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0))); + + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2))); + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 2, 0))); + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 2, 0, 0))); + EXPECT_FALSE(global_clock_region.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::IOB, 0, 0, 2))); +} + +TEST(GlobalClockRegionTest, + GetNextFrameAddressYieldNextAddressInGlobalClockRegion) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)); + + spartan6::GlobalClockRegion global_clock_region(addresses.begin(), + addresses.end()); + + auto next_address = global_clock_region.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + + next_address = global_clock_region.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + + next_address = global_clock_region.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + + next_address = global_clock_region.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ(*next_address, spartan6::FrameAddress( + spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); +} + +TEST(GlobalClockRegionTest, + GetNextFrameAddressYieldNothingAtEndOfGlobalClockRegion) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)); + + spartan6::GlobalClockRegion global_clock_region(addresses.begin(), + addresses.end()); + + EXPECT_FALSE(global_clock_region.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1))); + EXPECT_FALSE(global_clock_region.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2))); +} diff --git a/lib/xilinx/tests/spartan6/part_test.cc b/lib/xilinx/tests/spartan6/part_test.cc new file mode 100644 index 00000000..83939d29 --- /dev/null +++ b/lib/xilinx/tests/spartan6/part_test.cc @@ -0,0 +1,146 @@ +#include + +#include + +using namespace prjxray::xilinx; + +TEST(PartTest, IsValidFrameAddress) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + + spartan6::Part part(0x1234, addresses.begin(), addresses.end()); + + EXPECT_TRUE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0))); + EXPECT_TRUE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0))); + EXPECT_TRUE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2))); + EXPECT_TRUE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0))); + EXPECT_TRUE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0))); + + EXPECT_FALSE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2))); + EXPECT_FALSE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 2, 0))); + EXPECT_FALSE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 2, 0, 0))); + EXPECT_FALSE(part.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::IOB, 0, 0, 2))); +} + +TEST(PartTest, GetNextFrameAddressYieldNextAddressInPart) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + + spartan6::Part part(0x1234, addresses.begin(), addresses.end()); + + auto next_address = part.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + + next_address = part.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + + next_address = part.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + + next_address = part.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ(*next_address, spartan6::FrameAddress( + spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + + next_address = part.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); +} + +TEST(PartTest, GetNextFrameAddressYieldNothingAtEndOfPart) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 1, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + + spartan6::Part part(0x1234, addresses.begin(), addresses.end()); + + EXPECT_FALSE(part.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2))); +} diff --git a/lib/xilinx/tests/spartan6/row_test.cc b/lib/xilinx/tests/spartan6/row_test.cc new file mode 100644 index 00000000..735bc1d2 --- /dev/null +++ b/lib/xilinx/tests/spartan6/row_test.cc @@ -0,0 +1,114 @@ +#include + +#include + +using namespace prjxray::xilinx; + +TEST(RowTest, IsValidFrameAddress) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + + spartan6::Row row(addresses.begin(), addresses.end()); + + EXPECT_TRUE(row.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0))); + EXPECT_TRUE(row.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0))); + EXPECT_TRUE(row.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2))); + + EXPECT_FALSE(row.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 2))); + EXPECT_FALSE(row.IsValidFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 2, 0))); +} + +TEST(RowTest, GetNextFrameAddressYieldNextAddressInRow) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + + spartan6::Row row(addresses.begin(), addresses.end()); + + auto next_address = row.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + + next_address = row.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ( + *next_address, + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + + // Rows have unique behavior for GetNextFrameAddress() at the end of a + // bus. Since the addresses need to be returned in numerically + // increasing order, all of the rows need to be returned before moving + // to a different bus. That means that Row::GetNextFrameAddress() needs + // to return no object at the end of a bus and let the caller use that + // as a signal to try the next row. + next_address = row.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + EXPECT_FALSE(next_address); + + next_address = row.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + ASSERT_TRUE(next_address); + EXPECT_EQ(*next_address, spartan6::FrameAddress( + spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + + next_address = row.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + EXPECT_FALSE(next_address); +} + +TEST(RowTest, GetNextFrameAddressYieldNothingAtEndOfRow) { + std::vector addresses; + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::CLB_IOI_CLK, 0, 1, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 0)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 1)); + addresses.push_back( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2)); + + spartan6::Row row(addresses.begin(), addresses.end()); + + EXPECT_FALSE(row.GetNextFrameAddress( + spartan6::FrameAddress(spartan6::BlockType::BLOCK_RAM, 0, 0, 2))); +}