diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 21ae0404..d5b65b45 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(libprjxray xilinx/xc7series/configuration_frame_range.cc xilinx/xc7series/configuration_packet.cc xilinx/xc7series/configuration_register.cc + xilinx/xc7series/part.cc ) target_include_directories(libprjxray PUBLIC "include") target_link_libraries(libprjxray absl::optional absl::strings absl::span yaml-cpp) @@ -62,4 +63,11 @@ if (PRJXRAY_BUILD_TESTING) libprjxray gtest_main) add_test(NAME xilinx_xc7series_configuration_packet_test COMMAND xilinx_xc7series_configuration_packet_test) + + add_executable(xilinx_xc7series_part_test + xilinx/xc7series/part_test.cc) + target_link_libraries(xilinx_xc7series_part_test + libprjxray gtest_main) + add_test(NAME xilinx_xc7series_part_test + COMMAND xilinx_xc7series_part_test) endif() diff --git a/lib/include/prjxray/xilinx/xc7series/part.h b/lib/include/prjxray/xilinx/xc7series/part.h new file mode 100644 index 00000000..7d674f33 --- /dev/null +++ b/lib/include/prjxray/xilinx/xc7series/part.h @@ -0,0 +1,55 @@ +#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_PART_H_ +#define PRJXRAY_LIB_XILINX_XC7SERIES_PART_H_ + +#include + +#include +#include +#include + +namespace prjxray { +namespace xilinx { +namespace xc7series { + +class Part { + public: + 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_(0), frame_ranges_() {} + + Part(uint32_t idcode, + const std::vector &ranges) + : idcode_(idcode), frame_ranges_(std::move(ranges)) {} + + uint32_t idcode() const { return idcode_; } + + const std::vector& + configuration_frame_ranges() const { return frame_ranges_; } + + absl::optional + GetNextConfigurationFrameAddress( + ConfigurationFrameAddress address) const; + + private: + uint32_t idcode_; + + // Ranges are expected to be non-overlapping. + std::vector frame_ranges_; +}; + +} // namespace xc7series +} // namespace xilinx +} // namespace prjxray + +namespace YAML { +template<> +struct convert { + static Node encode(const prjxray::xilinx::xc7series::Part &rhs); + static bool decode(const Node& node, + prjxray::xilinx::xc7series::Part &lhs); +}; +} // namespace YAML + +#endif // PRJXRAY_LIB_XILINX_XC7SERIES_PART_H_ diff --git a/lib/xilinx/xc7series/part.cc b/lib/xilinx/xc7series/part.cc new file mode 100644 index 00000000..456565af --- /dev/null +++ b/lib/xilinx/xc7series/part.cc @@ -0,0 +1,82 @@ +#include + +#include +#include + +namespace prjxray { +namespace xilinx { +namespace xc7series { + +absl::optional Part::FromFile(const std::string &path) { + try { + YAML::Node yaml = YAML::LoadFile(path); + return yaml.as(); + } catch (YAML::Exception e) { + return {}; + } +} + +absl::optional +Part::GetNextConfigurationFrameAddress(ConfigurationFrameAddress address) const { + // Start with the next linear address. + ConfigurationFrameAddress target_address(address + 1); + + // The address space is non-continguous. If the next linear address + // happens to fall in a valid range, that's the next address. + // Otherwise, find the closest valid range and use it's beginning + // address. + absl::optional closest_address; + int32_t closest_distance; + for (auto iter = frame_ranges_.begin(); + iter != frame_ranges_.end(); + ++iter) { + if (iter->Contains(target_address)) { + return target_address; + } + + int32_t distance = iter->begin() - target_address; + if (distance > 0 && + (!closest_address || + distance < closest_distance)) { + closest_address = iter->begin(); + closest_distance = distance; + } + } + + return closest_address; +} + +} // namespace xc7series +} // namespace xilinx +} // namespace prjxray + +namespace xc7series = prjxray::xilinx::xc7series; + +namespace YAML { + +Node convert::encode(const xc7series::Part &rhs) { + Node node; + node.SetTag("xilinx/xc7series/part"); + + std::ostringstream idcode_str; + idcode_str << "0x" << std::hex << rhs.idcode(); + node["idcode"] = idcode_str.str(); + + node["configuration_ranges"] = rhs.configuration_frame_ranges(); + return node; +} + +bool convert::decode(const Node &node, xc7series::Part &lhs) { + if (node.Tag() != "xilinx/xc7series/part" || + !node["idcode"] || + !node["configuration_ranges"]) return false; + + lhs = xc7series::Part( + node["idcode"].as(), + node["configuration_ranges"].as< + std::vector>()); + return true; +} + +} // namespace YAML; + diff --git a/lib/xilinx/xc7series/part_test.cc b/lib/xilinx/xc7series/part_test.cc new file mode 100644 index 00000000..0c65d7de --- /dev/null +++ b/lib/xilinx/xc7series/part_test.cc @@ -0,0 +1,40 @@ +#include + +#include + +namespace xc7series = prjxray::xilinx::xc7series; + +TEST(PartTest, GetNextAddressWhereNextIsInValidRange) { + std::vector ranges; + ranges.push_back(xc7series::ConfigurationFrameRange(0x0, 0xF)); + ranges.push_back(xc7series::ConfigurationFrameRange(0x20, 0x2F)); + + xc7series::Part part(0x1234, ranges); + + auto next_address = part.GetNextConfigurationFrameAddress(0x4); + EXPECT_TRUE(next_address); + EXPECT_EQ(static_cast(0x5), *next_address); +} + +TEST(PartTest, GetNextAddressWhereNextIsBetweenRanges) { + std::vector ranges; + ranges.push_back(xc7series::ConfigurationFrameRange(0x0, 0xF)); + ranges.push_back(xc7series::ConfigurationFrameRange(0x20, 0x2F)); + + xc7series::Part part(0x1234, ranges); + + auto next_address = part.GetNextConfigurationFrameAddress(0xF); + EXPECT_TRUE(next_address); + EXPECT_EQ(static_cast(0x20), *next_address); +} + +TEST(PartTest, GetNextAddressWhereNextWouldBePastLastRange) { + std::vector ranges; + ranges.push_back(xc7series::ConfigurationFrameRange(0x0, 0xF)); + ranges.push_back(xc7series::ConfigurationFrameRange(0x20, 0x2F)); + + xc7series::Part part(0x1234, ranges); + + auto next_address = part.GetNextConfigurationFrameAddress(0x2F); + EXPECT_FALSE(next_address); +}