diff --git a/re/src/workers/fft_accel/data/fft_data.cpp b/re/src/workers/fft_accel/data/fft_data.cpp index cf1bf1586..4722c0784 100644 --- a/re/src/workers/fft_accel/data/fft_data.cpp +++ b/re/src/workers/fft_accel/data/fft_data.cpp @@ -4,6 +4,8 @@ #include "fft_data.hpp" +#include + using namespace sem::fft_accel::data; template<> @@ -16,6 +18,10 @@ fft_data_packet::fft_data_packet(SerializedPacket &serialized_data) : auto data_span = bytes.subspan(SerializedPacket::header_byte_length); fft_data_.resize(data_span.size() / sizeof(float)); memcpy(&(*fft_data_.begin()), &(*data_span.begin()), data_span.size()); + + for (auto& sample_value : fft_data_) { + boost::endian::big_to_native_inplace(reinterpret_cast(sample_value)); + } } template<> @@ -24,5 +30,8 @@ uint8_t fft_data_packet::sequence_number() const { return header_data_.se template<> uint8_t fft_data_packet::request_id() const { return header_data_.request_id(); }; +template<> +uint16_t fft_data_packet::fft_size() const { return header_data_.fft_size(); }; + template<> const std::vector &fft_data_packet::payload_data() const { return fft_data_; }; diff --git a/re/src/workers/fft_accel/data/fft_data.hpp b/re/src/workers/fft_accel/data/fft_data.hpp index 6e8cbb4cd..646ee88e3 100644 --- a/re/src/workers/fft_accel/data/fft_data.hpp +++ b/re/src/workers/fft_accel/data/fft_data.hpp @@ -13,6 +13,7 @@ #include "packet_headers.hpp" #include +#include "boost/endian/conversion.hpp" namespace sem::fft_accel::data { @@ -68,9 +69,9 @@ namespace sem::fft_accel::data { using SerializedPacket = serialized_fft_data; fft_data_packet(const data::vector_range &data_range, uint8_t request_id, - uint8_t sequence_num) : + uint8_t sequence_num, uint16_t fft_request_size) : fft_data_(data_range.begin(), data_range.end()), - header_data_(request_id, sequence_num, max_elements){ + header_data_(request_id, sequence_num, fft_request_size){ if (fft_data_.size() > max_elements) { throw std::invalid_argument("Attempting to create fft_data_packet from vector that is too large"); } @@ -82,6 +83,8 @@ namespace sem::fft_accel::data { [[nodiscard]] uint8_t request_id() const; + [[nodiscard]] uint16_t fft_size() const; + [[nodiscard]] const std::vector &payload_data() const; private: @@ -123,10 +126,17 @@ namespace sem::fft_accel::data { data_.at(2) = static_cast(native_packet.request_id()); // 3rd byte currently unused, formerly used for CSR // TODO: Thoroughly investigate endianness of remaining fields to be serialized - data_.at(4) = static_cast(native_packet.payload_data().size()); + uint16_t size_as_big_endian = boost::endian::native_to_big(static_cast(native_packet.fft_size())); + reinterpret_cast(data_.at(4)) = size_as_big_endian; constexpr auto payload_byte_length = NumElements * sizeof(SampleType); memcpy(&(*(data_.begin() + 6)), &(*native_packet.payload_data().begin()), payload_byte_length); + + // Note: the &(*(iterator)) syntax is needed for MSVC compatibility + auto float_view = reinterpret_cast*>(&(*(data_.begin() + 6))); + for (auto& sample_value : *float_view) { + boost::endian::native_to_big_inplace(sample_value); + } } template diff --git a/re/src/workers/fft_accel/data/fft_packet_group.hpp b/re/src/workers/fft_accel/data/fft_packet_group.hpp index 141b0aaa2..d395f6bf2 100644 --- a/re/src/workers/fft_accel/data/fft_packet_group.hpp +++ b/re/src/workers/fft_accel/data/fft_packet_group.hpp @@ -77,7 +77,7 @@ namespace sem::fft_accel::data { data::vector_range data_segment{fft_data, segment_start, segment_end}; - packets_.emplace_back(data_segment, request_id, cur_index); + packets_.emplace_back(data_segment, request_id, cur_index, fft_data.size()/2); } } diff --git a/re/src/workers/fft_accel/data/test/data_test_util.hpp b/re/src/workers/fft_accel/data/test/data_test_util.hpp index 2148e0db2..15a041b6f 100644 --- a/re/src/workers/fft_accel/data/test/data_test_util.hpp +++ b/re/src/workers/fft_accel/data/test/data_test_util.hpp @@ -34,7 +34,7 @@ namespace sem::fft_accel::data::test { static std::uniform_int_distribution dis( std::numeric_limits::min(), std::numeric_limits::max() - ); + ); #endif return dis(e); } @@ -66,16 +66,19 @@ namespace sem::fft_accel::data::test { } template - inline fft_data_packet generate_random_fft_data_packet() { + inline fft_data_packet generate_random_fft_data_packet() { + auto &&vec_data = generate_random_single_packet_fft_vec_data(); + auto num_samples = static_cast(vec_data.size() / 2); return { - generate_random_single_packet_fft_vec_data(), + vec_data, + get_random(), get_random(), - get_random() + num_samples, }; } template - inline serialized_fft_data generate_random_serialized_data_packet() { + inline serialized_fft_data generate_random_serialized_data_packet() { return serialized_fft_data( generate_random_array::packet_byte_size>() ); diff --git a/re/src/workers/fft_accel/data/test/fft_data_tests.cpp b/re/src/workers/fft_accel/data/test/fft_data_tests.cpp index 4528c217e..e427a9b63 100644 --- a/re/src/workers/fft_accel/data/test/fft_data_tests.cpp +++ b/re/src/workers/fft_accel/data/test/fft_data_tests.cpp @@ -20,7 +20,8 @@ TEST(fft_accel_worker_data_packet, constructor_nothrow) { data_packet = std::make_unique>( payload_range, get_random(), - get_random() + get_random(), + payload_data.size()/2 ); ); ASSERT_NE(data_packet, nullptr); diff --git a/re/src/workers/fft_accel/runtime/fft_request_map.hpp b/re/src/workers/fft_accel/runtime/fft_request_map.hpp index 753519200..77621d658 100644 --- a/re/src/workers/fft_accel/runtime/fft_request_map.hpp +++ b/re/src/workers/fft_accel/runtime/fft_request_map.hpp @@ -162,7 +162,8 @@ namespace sem::fft_accel::runtime { } if (request.remaining_packets() == 0) { - auto &&fulfilled_request = staged_packet_groups_.at(request_id); + auto fulfilled_request = std::move(staged_packet_groups_.at(request_id)); + staged_packet_groups_.erase(request_id); unfulfilled_promises_.at(request_id).set_value(fulfilled_request); return {std::optional>({request_id, fulfilled_request.to_vector()})}; } else { diff --git a/re/src/workers/fft_accel/runtime/single_thread_dispatcher.hpp b/re/src/workers/fft_accel/runtime/single_thread_dispatcher.hpp index 68f0d550a..8cbfc51cb 100644 --- a/re/src/workers/fft_accel/runtime/single_thread_dispatcher.hpp +++ b/re/src/workers/fft_accel/runtime/single_thread_dispatcher.hpp @@ -52,8 +52,9 @@ namespace sem::fft_accel::runtime { template single_thread_dispatcher::single_thread_dispatcher() : - should_continue_running_(true), - dispatch_thread_([this]() { run_dispatch_loop(); }) {} + should_continue_running_(true) { + dispatch_thread_ = std::thread([this]() { run_dispatch_loop(); }); + } template single_thread_dispatcher::~single_thread_dispatcher() { @@ -63,8 +64,10 @@ namespace sem::fft_accel::runtime { template void single_thread_dispatcher::stop() { - std::lock_guard queue_lock(queue_mutex_); - should_continue_running_ = false; + { + std::lock_guard queue_lock(queue_mutex_); + should_continue_running_ = false; + } // Ensure that the queue variable gets the signal to stop waiting for new events event_occurred_.notify_all(); } @@ -122,7 +125,7 @@ namespace sem::fft_accel::runtime { template void single_thread_dispatcher::run_dispatch_loop() { - while (should_continue_running_.load()) { + while (should_continue_running_) { std::unique_lock queue_lock(queue_mutex_); if (!event_queue_.empty()) { diff --git a/re/src/workers/fft_accel/runtime/test/runtime_adapter_tests.cpp b/re/src/workers/fft_accel/runtime/test/runtime_adapter_tests.cpp index 1957a66ff..a52f64d0d 100644 --- a/re/src/workers/fft_accel/runtime/test/runtime_adapter_tests.cpp +++ b/re/src/workers/fft_accel/runtime/test/runtime_adapter_tests.cpp @@ -143,7 +143,8 @@ AssertionResult map_can_construct_then_receive(size_t num_packets) { response_packets.emplace_back( data::vector_range{data::test::generate_random_fft_data(packet_type::max_elements)}, constructed_request_id, - sequence_num); + sequence_num, + input_data.size()/2); } // Send responses for all packets apart from the last, expecting no error, but result should contain nullopt (request not yet fulfilled) diff --git a/re/src/workers/fft_accel/sem_fft_accel_worker.cpp b/re/src/workers/fft_accel/sem_fft_accel_worker.cpp index 03dd465be..ae7ab0c07 100644 --- a/re/src/workers/fft_accel/sem_fft_accel_worker.cpp +++ b/re/src/workers/fft_accel/sem_fft_accel_worker.cpp @@ -30,6 +30,9 @@ sem::fft_accel::Worker::Worker(const BehaviourContainer &container, const std::s } } +sem::fft_accel::Worker::~Worker() { + Terminate(); +} void sem::fft_accel::Worker::HandleConfigure() { auto endpoint = GetAttribute(std::string(AttrNames::accelerator_endpoint)).lock(); diff --git a/re/src/workers/fft_accel/sem_fft_accel_worker.hpp b/re/src/workers/fft_accel/sem_fft_accel_worker.hpp index 06826b0b1..21a3cafa8 100644 --- a/re/src/workers/fft_accel/sem_fft_accel_worker.hpp +++ b/re/src/workers/fft_accel/sem_fft_accel_worker.hpp @@ -16,6 +16,7 @@ class Worker : public ::Worker { using callback_func_type = std::function; Worker(const BehaviourContainer &container, const std::string &inst_name); + ~Worker(); const std::string &get_version() const override; diff --git a/re/src/workers/fft_accel/test/CMakeLists.txt b/re/src/workers/fft_accel/test/CMakeLists.txt index 391820ad3..06169b254 100644 --- a/re/src/workers/fft_accel/test/CMakeLists.txt +++ b/re/src/workers/fft_accel/test/CMakeLists.txt @@ -47,4 +47,6 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_$ ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/$ if(MSVC) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -endif(MSVC) \ No newline at end of file +endif(MSVC) + +add_subdirectory(fpga_client) \ No newline at end of file diff --git a/re/src/workers/fft_accel/test/fpga_client/CMakeLists.txt b/re/src/workers/fft_accel/test/fpga_client/CMakeLists.txt new file mode 100644 index 000000000..3fc0ee49f --- /dev/null +++ b/re/src/workers/fft_accel/test/fpga_client/CMakeLists.txt @@ -0,0 +1,21 @@ + +find_package(Boost 1.67.0 REQUIRED COMPONENTS program_options filesystem) + +add_executable(fpga_fft_client) + +target_compile_features(fpga_fft_client + PUBLIC + cxx_std_17 + ) + +target_sources(fpga_fft_client + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/fft_client.cpp + ) + +target_link_libraries(fpga_fft_client + PRIVATE + sem_fft_accel_fpga_worker + Boost::program_options + Boost::filesystem +) \ No newline at end of file diff --git a/re/src/workers/fft_accel/test/fpga_client/fft_client.cpp b/re/src/workers/fft_accel/test/fpga_client/fft_client.cpp new file mode 100644 index 000000000..909170eef --- /dev/null +++ b/re/src/workers/fft_accel/test/fpga_client/fft_client.cpp @@ -0,0 +1,246 @@ +// +// Created by Jackson Michael on 1/3/21. +// + +#include "boost/filesystem.hpp" +#include "boost/program_options.hpp" + +#include "component.h" +#include "print_logger.h" +#include "sem_fft_accel_worker.hpp" + +#include +#include +#include +#include + +namespace sem::fft_accel::test { + +struct config_data { + std::string fae_endpoint; + std::string input_data_path; + boost::optional expected_result_path; + uint32_t num_repeats{1}; + bool send_async{false}; +}; + +namespace cmd { +std::optional parse_args(int argc, char** argv) +{ + namespace prog_opts = boost::program_options; + + config_data data; + + prog_opts::options_description generic("Generic options"); + generic.add_options()("help,h", "Print a description of the available commands and exit"); + + prog_opts::options_description config("Client configuration options"); + config.add_options()("fae-endpoint,e", prog_opts::value(&data.fae_endpoint)->required(), + "Set the endpoint of the FFT Acceleration Engine this client should " + "connect to")("input_data,i", + prog_opts::value(&data.input_data_path)->required(), + "Specify the base filepath for the input fft data ('_r.txt' " + "and '_i.txt' will be " + "appended)")("expected_result,x", + prog_opts::value(&data.expected_result_path), + "Specify the base filepath for the expected " + "fft result ('_r.txt' and '_i.txt' " + "will be appended)")( + "num_repetitions,n", prog_opts::value(&data.num_repeats)->default_value(1), + "Specify the number of FFT calculation requests that should be made of using the supplied " + "data")("send_async,a", prog_opts::bool_switch(&data.send_async)->default_value(false), + "Set to true to send packets in parallel, by default will send in serial"); + + prog_opts::options_description cmd_options; + cmd_options.add(generic).add(config); + + prog_opts::variables_map variables_map; + prog_opts::store(prog_opts::parse_command_line(argc, argv, cmd_options), variables_map); + + if(variables_map.count("help")) { + std::cout << cmd_options << std::endl; + return {}; + } + + // At this point, check if the variables marked as required have been provided, throwing as + // needed + prog_opts::notify(variables_map); + + return data; +} +} // namespace cmd + +/// Reinterpret an integer value as a float without performing any cast conversions +float float_from_bits(uint32_t value) +{ + return *reinterpret_cast(&value); +} + +/// Convert a bitset to the equivalent IEEE754 float (not broadly tested across may platforms) +float float_from(std::bitset<32> bits) +{ + return float_from_bits(bits.to_ulong()); +} + +namespace file { +namespace filesystem = boost::filesystem; + +struct fft_dataset { + filesystem::path real_data; + filesystem::path imaginary_data; +}; + +fft_dataset get_dataset_from_base_path(const filesystem::path& path) +{ + fft_dataset fileset; + + auto real_filename = path.filename(); + real_filename += "_r.txt"; + auto imag_filename = path.filename(); + imag_filename += "_i.txt"; + + fileset.real_data = path; + fileset.real_data.remove_filename() += real_filename; + + fileset.imaginary_data = path; + fileset.imaginary_data.remove_filename() += imag_filename; + + return fileset; +} + +std::vector load_ascii_binary_fft_data(const filesystem::path& path) +{ + auto fileset = get_dataset_from_base_path(path); + + std::ifstream real_component_stream(fileset.real_data.string()); + if(!real_component_stream) { + throw std::runtime_error("Failed to open file_stream for " + path.string()); + } + std::ifstream imaginary_component_stream(fileset.imaginary_data.string()); + if(!imaginary_component_stream) { + throw std::runtime_error("Failed to open file_stream for " + path.string()); + } + + std::vector fft_data; + + while(real_component_stream.good() && imaginary_component_stream.good()) { + // Read the ascii bit field values from both files + std::bitset<32> real_component_bits; + real_component_stream >> real_component_bits; + std::bitset<32> imaginary_component_bits; + imaginary_component_stream >> imaginary_component_bits; + + // Handle the end-of-file case (one file case and both files case) + if(real_component_stream.eof() != imaginary_component_stream.eof()) { + throw std::runtime_error("Error when parsing FFT data files; reached end of one file " + "while more data remains in the other"); + } + if(real_component_stream.eof() && imaginary_component_stream.eof()) { + break; + } + + // If we didn't reach the end of the file, add the data values to the vector + fft_data.push_back(float_from(real_component_bits)); + fft_data.push_back(float_from(imaginary_component_bits)); + } + + return fft_data; +} +} // namespace file + +bool compare_and_print_mismatches(std::vector actual, std::vector expected) +{ + if(actual.size() != expected.size()) { + std::cerr << "Size of calculated FFT result doesn't match size of expected result" + << std::endl; + return false; + } + + bool mismatch_encountered = false; + for(size_t index = 0; index < actual.size(); index++) { + if(actual.at(index) != expected.at(index)) { + std::cerr << "Calculated result doesn't match with expected at index " << index << ": " + << actual.at(index) << " vs " << expected.at(index) << std::endl; + mismatch_encountered = true; + } + } + + return mismatch_encountered; +} + +} // namespace sem::fft_accel::test + +int main(int argc, char** argv) +{ + using namespace sem::fft_accel::test; + using fft_worker = sem::fft_accel::Worker; + + auto config_result = cmd::parse_args(argc, argv); + if(!config_result.has_value()) { + return 1; + } + config_data config = config_result.value(); + + auto fft_input_data = file::load_ascii_binary_fft_data(config.input_data_path); + + std::vector expected_result; + if(config.expected_result_path) { + expected_result = file::load_ascii_binary_fft_data(config.expected_result_path.value()); + } + + try { + Component client_component("FPGA_FFT_testing_client"); + auto fft_client = client_component.AddTypedWorker("test_fft_client_worker"); + + Print::Logger print_logger; + fft_client->logger().AddLogger(print_logger); + + auto attr_ref = fft_client->GetAttribute( + std::string(fft_worker::AttrNames::accelerator_endpoint)); + if(auto endpoint_attr = attr_ref.lock()) { + endpoint_attr->SetValue(config.fae_endpoint); + } else { + throw std::runtime_error("Failed to acquire lock on FAE endpoint attribute"); + } + + if(!client_component.Configure()) { + throw std::runtime_error("Client component failed to configure"); + } + + std::set requested_ids; + if(config.send_async) { + fft_client->SetResponseCallback([&expected_result, &requested_ids]( + uint8_t fft_id, std::vector data) { + if(requested_ids.count(fft_id)) { + std::cout << "Received packet with FFT_REQUEST_ID = " << fft_id << std::endl; + compare_and_print_mismatches(std::move(data), expected_result); + requested_ids.erase(fft_id); + } else { + std::cout << "Received response ID not associated with a request" << std::endl; + } + }); + } + + for(int repetition_count = 0; repetition_count < config.num_repeats; repetition_count++) { + if(config.send_async) { + int fft_id = fft_client->calculate_fft_async(fft_input_data); + std::cout << "Send packet with FFT_REQUEST_ID = " << fft_id << std::endl; + } else { + auto calculated_result = fft_client->calculate_fft(fft_input_data); + + if(config.expected_result_path) { + compare_and_print_mismatches(calculated_result, expected_result); + } + } + } + + fft_client->Terminate(); + + } catch(const std::exception& ex) { + std::cerr << "An exception was thrown from a RE system:" << std::endl + << ex.what() << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file