diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d5b65b45..8000b610 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -57,6 +57,13 @@ if (PRJXRAY_BUILD_TESTING) add_test(NAME xilinx_xc7series_configuration_frame_address_test COMMAND xilinx_xc7series_configuration_frame_address_test) + add_executable(xilinx_xc7series_configuration_test + xilinx/xc7series/configuration_test.cc) + target_link_libraries(xilinx_xc7series_configuration_test + libprjxray gtest_main absl::span) + add_test(NAME xilinx_xc7series_configuration_test + COMMAND xilinx_xc7series_configuration_test) + add_executable(xilinx_xc7series_configuration_packet_test xilinx/xc7series/configuration_packet_test.cc) target_link_libraries(xilinx_xc7series_configuration_packet_test diff --git a/lib/include/prjxray/xilinx/xc7series/configuration.h b/lib/include/prjxray/xilinx/xc7series/configuration.h new file mode 100644 index 00000000..8d9984a5 --- /dev/null +++ b/lib/include/prjxray/xilinx/xc7series/configuration.h @@ -0,0 +1,145 @@ +#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_H_ +#define PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_H_ + +#include + +#include +#include +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace xc7series { + +class Configuration { + public: + using FrameMap = std::map>; + + template + static absl::optional InitWithPackets( + const Part& part, Collection &packets); + + Configuration(const Part& part, const FrameMap &frames) + : part_(part), frames_(std::move(frames)) {} + + const Part& part() const { return part_; } + const FrameMap& frames() const { return frames_; } + + private: + static constexpr int kWordsPerFrame = 101; + + Part part_; + FrameMap frames_; +}; + +template +absl::optional Configuration::InitWithPackets( + const Part& part, Collection &packets) { + // Registers that can be directly written to. + uint32_t command_register = 0; + uint32_t frame_address_register = 0; + uint32_t mask_register = 0; + uint32_t ctl1_register = 0; + + // Internal state machine for writes. + bool start_new_write = false; + ConfigurationFrameAddress current_frame_address = 0; + + Configuration::FrameMap frames; + for (auto packet : packets) { + if (packet.opcode() != ConfigurationPacket::Opcode::Write) { + continue; + } + + switch (packet.address()) { + case ConfigurationRegister::MASK: + if (packet.data().size() < 1) continue; + mask_register = packet.data()[0]; + break; + case ConfigurationRegister::CTL1: + if (packet.data().size() < 1) continue; + ctl1_register = packet.data()[0] & mask_register; + break; + case ConfigurationRegister::CMD: + if (packet.data().size() < 1) continue; + command_register = packet.data()[0]; + // Writes to CMD trigger an immediate action. In the case of + // WCFG, that is just setting a flag for the next FDIR. + if (command_register == 0x1) { + start_new_write = true; + } + break; + case ConfigurationRegister::IDCODE: + // This really should be a one-word write. + if (packet.data().size() < 1) continue; + + // If the IDCODE doesn't match our expected part, + // consider the bitstream invalid. + if (packet.data()[0] != part.idcode()) { + return {}; + } + break; + case ConfigurationRegister::FAR: + // This really should be a one-word write. + if (packet.data().size() < 1) continue; + frame_address_register = packet.data()[0]; + + // Per UG470, the command present in the CMD register + // is executed each time the FAR register is laoded + // with a new value. As we only care about WCFG + // commands, just check that here. CTRL1 is completely + // undocumented but looking at generated bitstreams, bit 21 + // is used when per-frame CRC is enabled. Setting this + // bit seems to inhibit the re-execution of CMD during a + // FAR write. In practice, this is used so FAR writes + // can be added in the bitstream to show progress + // markers without impacting the actual write + // operation. + if (bit_field_get(ctl1_register, 21, 21) == 0 && + command_register == 0x1) { + start_new_write = true; + } + break; + case ConfigurationRegister::FDRI: { + if (start_new_write) { + current_frame_address = frame_address_register; + start_new_write = false; + } + + // 7-series frames are 101-words long. Writes to this + // register can be multiples of that to do + // auto-incrementing block writes. + int frames_written = packet.data().size() / + kWordsPerFrame; + + for (int ii = 0; ii < frames_written; ++ii) { + frames[current_frame_address] = + packet.data().subspan( + ii * kWordsPerFrame, + kWordsPerFrame); + auto next_address = + part.GetNextConfigurationFrameAddress( + current_frame_address); + if (next_address) { + current_frame_address = *next_address; + } + } + break; + } + default: + break; + } + } + + return Configuration(part, frames); +} + + +} // namespace xc7series +} // namespace xilinx +} // namespace prjxray + +#endif // PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_H_ diff --git a/lib/xilinx/xc7series/configuration_test.cc b/lib/xilinx/xc7series/configuration_test.cc new file mode 100644 index 00000000..a1f2a623 --- /dev/null +++ b/lib/xilinx/xc7series/configuration_test.cc @@ -0,0 +1,111 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace xc7series = prjxray::xilinx::xc7series; + +TEST(ConfigurationTest, ConstructFromPacketsWithSingleFrame) { + std::vector test_part_ranges; + test_part_ranges.push_back( + xc7series::ConfigurationFrameRange(0x4567, 0x4568)); + + xc7series::Part test_part(0x1234, test_part_ranges); + + std::vector idcode{0x1234}; + std::vector cmd{0x0001}; + std::vector frame_address{0x4567}; + std::vector frame(101, 0xAA); + + std::vector packets{ + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::IDCODE, + absl::MakeSpan(idcode), + }, + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::FAR, + absl::MakeSpan(frame_address), + }, + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::CMD, + absl::MakeSpan(cmd), + }, + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::FDRI, + absl::MakeSpan(frame), + }, + }; + + auto test_config = xc7series::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(0x4567), frame); +} + +TEST(ConfigurationTest, ConstructFromPacketsWithAutoincrement) { + std::vector test_part_ranges; + test_part_ranges.push_back( + xc7series::ConfigurationFrameRange(0x4560, 0x4570)); + test_part_ranges.push_back( + xc7series::ConfigurationFrameRange(0x4580, 0x4590)); + + xc7series::Part test_part(0x1234, test_part_ranges); + + std::vector idcode{0x1234}; + std::vector cmd{0x0001}; + std::vector frame_address{0x456F}; + std::vector frame(202, 0xAA); + std::fill_n(frame.begin() + 101, 101, 0xBB); + + std::vector packets{ + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::IDCODE, + absl::MakeSpan(idcode), + }, + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::FAR, + absl::MakeSpan(frame_address), + }, + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::CMD, + absl::MakeSpan(cmd), + }, + { + static_cast(0x1), + xc7series::ConfigurationPacket::Opcode::Write, + xc7series::ConfigurationRegister::FDRI, + absl::MakeSpan(frame), + }, + }; + + auto test_config = xc7series::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(0x456F), frame_span.subspan(0, 101)); + EXPECT_EQ(test_config->frames().at(0x4580), frame_span.subspan(101)); +}