mirror of https://github.com/openXC7/prjxray.git
lib: xc7series: class to hold a configuration
Currently is able to parse IDCODE and frames from a sequence of ConfigurationPackets. Signed-off-by: Rick Altherr <kc8apf@kc8apf.net> Signed-off-by: Tim 'mithro' Ansell <mithro@mithis.com>
This commit is contained in:
parent
6a17a92d83
commit
bd3fa58584
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
#ifndef PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_H_
|
||||
#define PRJXRAY_LIB_XILINX_XC7SERIES_CONFIGURATION_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <absl/types/span.h>
|
||||
#include <prjxray/bit_ops.h>
|
||||
#include <prjxray/xilinx/xc7series/bitstream_reader.h>
|
||||
#include <prjxray/xilinx/xc7series/configuration_frame_address.h>
|
||||
#include <prjxray/xilinx/xc7series/part.h>
|
||||
|
||||
namespace prjxray {
|
||||
namespace xilinx {
|
||||
namespace xc7series {
|
||||
|
||||
class Configuration {
|
||||
public:
|
||||
using FrameMap = std::map<ConfigurationFrameAddress,
|
||||
absl::Span<uint32_t>>;
|
||||
|
||||
template<typename Collection>
|
||||
static absl::optional<Configuration> 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<typename Collection>
|
||||
absl::optional<Configuration> 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_
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
#include <prjxray/xilinx/xc7series/configuration.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <absl/types/span.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <prjxray/xilinx/xc7series/configuration_packet.h>
|
||||
#include <prjxray/xilinx/xc7series/configuration_register.h>
|
||||
#include <prjxray/xilinx/xc7series/part.h>
|
||||
|
||||
namespace xc7series = prjxray::xilinx::xc7series;
|
||||
|
||||
TEST(ConfigurationTest, ConstructFromPacketsWithSingleFrame) {
|
||||
std::vector<xc7series::ConfigurationFrameRange> test_part_ranges;
|
||||
test_part_ranges.push_back(
|
||||
xc7series::ConfigurationFrameRange(0x4567, 0x4568));
|
||||
|
||||
xc7series::Part test_part(0x1234, test_part_ranges);
|
||||
|
||||
std::vector<uint32_t> idcode{0x1234};
|
||||
std::vector<uint32_t> cmd{0x0001};
|
||||
std::vector<uint32_t> frame_address{0x4567};
|
||||
std::vector<uint32_t> frame(101, 0xAA);
|
||||
|
||||
std::vector<xc7series::ConfigurationPacket> packets{
|
||||
{
|
||||
static_cast<unsigned int>(0x1),
|
||||
xc7series::ConfigurationPacket::Opcode::Write,
|
||||
xc7series::ConfigurationRegister::IDCODE,
|
||||
absl::MakeSpan(idcode),
|
||||
},
|
||||
{
|
||||
static_cast<unsigned int>(0x1),
|
||||
xc7series::ConfigurationPacket::Opcode::Write,
|
||||
xc7series::ConfigurationRegister::FAR,
|
||||
absl::MakeSpan(frame_address),
|
||||
},
|
||||
{
|
||||
static_cast<unsigned int>(0x1),
|
||||
xc7series::ConfigurationPacket::Opcode::Write,
|
||||
xc7series::ConfigurationRegister::CMD,
|
||||
absl::MakeSpan(cmd),
|
||||
},
|
||||
{
|
||||
static_cast<unsigned int>(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<uint32_t>(0x1234));
|
||||
EXPECT_EQ(test_config->frames().size(), static_cast<size_t>(1));
|
||||
EXPECT_EQ(test_config->frames().at(0x4567), frame);
|
||||
}
|
||||
|
||||
TEST(ConfigurationTest, ConstructFromPacketsWithAutoincrement) {
|
||||
std::vector<xc7series::ConfigurationFrameRange> 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<uint32_t> idcode{0x1234};
|
||||
std::vector<uint32_t> cmd{0x0001};
|
||||
std::vector<uint32_t> frame_address{0x456F};
|
||||
std::vector<uint32_t> frame(202, 0xAA);
|
||||
std::fill_n(frame.begin() + 101, 101, 0xBB);
|
||||
|
||||
std::vector<xc7series::ConfigurationPacket> packets{
|
||||
{
|
||||
static_cast<unsigned int>(0x1),
|
||||
xc7series::ConfigurationPacket::Opcode::Write,
|
||||
xc7series::ConfigurationRegister::IDCODE,
|
||||
absl::MakeSpan(idcode),
|
||||
},
|
||||
{
|
||||
static_cast<unsigned int>(0x1),
|
||||
xc7series::ConfigurationPacket::Opcode::Write,
|
||||
xc7series::ConfigurationRegister::FAR,
|
||||
absl::MakeSpan(frame_address),
|
||||
},
|
||||
{
|
||||
static_cast<unsigned int>(0x1),
|
||||
xc7series::ConfigurationPacket::Opcode::Write,
|
||||
xc7series::ConfigurationRegister::CMD,
|
||||
absl::MakeSpan(cmd),
|
||||
},
|
||||
{
|
||||
static_cast<unsigned int>(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<uint32_t> frame_span(frame);
|
||||
EXPECT_EQ(test_config->part().idcode(), static_cast<uint32_t>(0x1234));
|
||||
EXPECT_EQ(test_config->frames().size(), static_cast<size_t>(2));
|
||||
EXPECT_EQ(test_config->frames().at(0x456F), frame_span.subspan(0, 101));
|
||||
EXPECT_EQ(test_config->frames().at(0x4580), frame_span.subspan(101));
|
||||
}
|
||||
Loading…
Reference in New Issue