// Copyright (2019-2023) Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-or-later #include "FPGAAcquisitionDevice.h" #include #include void FPGAAcquisitionDevice::StartSendingWorkRequests() { stop_work_requests = false; send_work_request_future = std::async(std::launch::async, &FPGAAcquisitionDevice::SendWorkRequestThread, this); } void FPGAAcquisitionDevice::Finalize() { read_work_completion_future.get(); stop_work_requests = true; send_work_request_future.get(); FPGA_EndAction(); while (!HW_IsIdle()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); } void FPGAAcquisitionDevice::ReadWorkCompletionThread() { uint32_t values[16]; Completion c{}; bool quit_loop = false; do { while (!HW_ReadMailbox(values)) std::this_thread::sleep_for(std::chrono::microseconds(10)); c = parse_hw_completion(values); if (c.data_collection_id == data_collection_id) { work_completion_queue.PutBlocking(c); if (c.type == Completion::Type::End) quit_loop = true; } } while (!quit_loop); } void FPGAAcquisitionDevice::SendWorkRequestThread() { while (!stop_work_requests) { WorkRequest wr{}; if (work_request_queue.Get(wr)) { if ( !HW_SendWorkRequest(wr.handle)) { work_request_queue.Put(wr); std::this_thread::sleep_for(std::chrono::microseconds(10)); } } else { std::this_thread::sleep_for(std::chrono::microseconds(10)); } } } void FPGAAcquisitionDevice::InitializeCalibration(const DiffractionExperiment &experiment, const JFCalibration &calib) { auto offset = experiment.GetFirstModuleOfDataStream(data_stream); if (calib.GetModulesNum() != experiment.GetModulesNum()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch regarding module count in calibration and experiment description"); if (calib.GetStorageCellNum() != experiment.GetStorageCellNumber()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch regarding storage cell count in calibration and experiment description"); size_t modules = experiment.GetModulesNum(data_stream); size_t storage_cells = experiment.GetStorageCellNumber(); if (modules * (3 + 3 * storage_cells) > buffer_device.size()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Not enough host/FPGA buffers to load all calibration constants"); for (int m = 0; m < modules; m++) { calib.GainCalibration(m).ExportG0(buffer_device[m]); calib.GainCalibration(m).ExportG1(buffer_device[m + modules]); calib.GainCalibration(m).ExportG2(buffer_device[m + modules * 2]); } for (int s = 0; s < storage_cells; s++) { auto mask = calib.CalculateMask(experiment, s); for (int m = 0; m < modules; m++) { auto pedestal_g0 = calib.Pedestal(offset + m, 0, s).GetPedestal(); auto pedestal_g1 = calib.Pedestal(offset + m, 1, s).GetPedestal(); auto pedestal_g2 = calib.Pedestal(offset + m, 2, s).GetPedestal(); for (int i = 0; i < RAW_MODULE_SIZE; i++) { if (experiment.GetApplyPixelMaskInFPGA() && (mask[(offset + m) * RAW_MODULE_SIZE + i] != 0)) { buffer_device[(3 + 0 * storage_cells + s) * modules + m][i] = 16384; buffer_device[(3 + 1 * storage_cells + s) * modules + m][i] = 16384; buffer_device[(3 + 2 * storage_cells + s) * modules + m][i] = 16384; } else { buffer_device[(3 + 0 * storage_cells + s) * modules + m][i] = pedestal_g0[i]; buffer_device[(3 + 1 * storage_cells + s) * modules + m][i] = pedestal_g1[i]; buffer_device[(3 + 2 * storage_cells + s) * modules + m][i] = pedestal_g2[i]; } } } } HW_LoadCalibration(modules, storage_cells); } void FPGAAcquisitionDevice::FillActionRegister(const DiffractionExperiment& x, ActionConfig &job) { std::random_device rd; std::uniform_int_distribution dist; data_collection_id = dist(rd); job.nmodules = x.GetModulesNum(data_stream); job.nframes = x.GetFrameNum(); job.one_over_energy = std::lround((1<<20)/ x.GetPhotonEnergy_keV()); job.nstorage_cells = x.GetStorageCellNumber() - 1; job.mode = data_collection_id << 16; if (fpga_non_blocking_mode) job.mode |= MODE_NONBLOCKING_ON_WR; if ((x.GetDetectorMode() == DetectorMode::Conversion) && x.GetConversionOnFPGA()) job.mode |= MODE_CONV; } void FPGAAcquisitionDevice::Start(const DiffractionExperiment &experiment) { if (!HW_IsIdle()) throw(JFJochException(JFJochExceptionCategory::AcquisitionDeviceError, "Hardware action running prior to start of data acquisition")); ActionConfig cfg_in{}, cfg_out{}; FillActionRegister(experiment, cfg_in); HW_WriteActionRegister(&cfg_in); HW_ReadActionRegister(&cfg_out); if (cfg_out.mode != cfg_in.mode) throw JFJochException(JFJochExceptionCategory::AcquisitionDeviceError, "Mismatch between expected and actual values of configuration registers (mode)"); if (cfg_out.nframes != cfg_in.nframes) throw JFJochException(JFJochExceptionCategory::AcquisitionDeviceError, "Mismatch between expected and actual values of configuration registers (Frames per trigger)"); if (cfg_out.nmodules != cfg_in.nmodules) throw JFJochException(JFJochExceptionCategory::AcquisitionDeviceError, "Mismatch between expected and actual values of configuration registers (#modules)"); FPGA_StartAction(experiment); read_work_completion_future = std::async(std::launch::async, &FPGAAcquisitionDevice::ReadWorkCompletionThread, this); } ActionConfig FPGAAcquisitionDevice::ReadActionRegister() { ActionConfig cfg{}; HW_ReadActionRegister(&cfg); return cfg; } inline void FIFO_check(JFJochProtoBuf::FPGAStatus &fpga_status, const std::string &name, uint32_t fifo_register, uint16_t pos_empty, uint16_t pos_full) { auto fifo_status = fpga_status.add_fifo_status(); fifo_status->set_name(name); if (std::bitset<32>(fifo_register).test(pos_empty)) fifo_status->set_value(JFJochProtoBuf::FPGAFIFOStatusEnum::EMPTY); if (std::bitset<32>(fifo_register).test(pos_full)) fifo_status->set_value(JFJochProtoBuf::FPGAFIFOStatusEnum::FULL); fifo_status->set_value(JFJochProtoBuf::FPGAFIFOStatusEnum::PARTIAL); } inline void CheckHostWriterErr(JFJochProtoBuf::FPGAStatus &output, uint32_t status_register, uint32_t bit, const std::string &name) { if (status_register & (1 << (24+bit))) output.add_host_writer_err(name); } JFJochProtoBuf::FPGAStatus FPGAAcquisitionDevice::GetStatus() const { ActionStatus status{}; ActionEnvParams env{}; HW_GetStatus(&status); HW_GetEnvParams(&env); JFJochProtoBuf::FPGAStatus ret; auto full_status_register = status.ctrl_reg; ret.set_full_status_register(full_status_register); ret.set_stalls_hbm(status.pipeline_stalls_hbm); ret.set_stalls_host(status.pipeline_stalls_host); ret.set_max_modules(status.max_modules); ret.set_git_sha1(status.git_sha1); ret.set_hbm_size_bytes(status.hbm_size_bytes); FIFO_check(ret, "UDP", status.fifo_status, 6, 7); FIFO_check(ret, "Conversion input (data)", status.fifo_status, 0, 1); FIFO_check(ret, "Conversion input (cmd)", status.fifo_status, 2, 3); FIFO_check(ret, "Work Request", status.fifo_status, 12, 13); FIFO_check(ret, "Work Completion", status.fifo_status, 14, 15); FIFO_check(ret, "Writer input (data)", status.fifo_status, 16, 17); FIFO_check(ret, "Writer input (cmd)", status.fifo_status, 18, 19); FIFO_check(ret, "C2H (data)", status.fifo_status, 8, 9); FIFO_check(ret, "C2H (cmd)", status.fifo_status, 10, 11); FIFO_check(ret, "H2C (data)", status.fifo_status, 20, 21); FIFO_check(ret, "H2C (cmd)", status.fifo_status, 22, 23); ret.set_fpga_idle(HW_IsIdle()); ret.set_packets_ether(status.packets_eth); ret.set_packets_udp(status.packets_udp); ret.set_packets_icmp(status.packets_icmp); ret.set_packets_jfjoch(status.packets_processed); ret.set_packets_sls(status.packets_sls); ret.set_error_eth(status.udp_err_eth); ret.set_error_packet_len(status.udp_err_len); ret.set_cancel_bit(full_status_register & (1<<2)); ret.set_host_writer_idle(full_status_register & (1<<4)); CheckHostWriterErr(ret, full_status_register, 0, "Alignment error"); CheckHostWriterErr(ret, full_status_register, 1, "TLAST error"); CheckHostWriterErr(ret, full_status_register, 2, "Work request parity error"); CheckHostWriterErr(ret, full_status_register, 3, "Handle error"); CheckHostWriterErr(ret, full_status_register, 4, "Null pointer"); CheckHostWriterErr(ret, full_status_register, 5, "Module number exceeded"); ret.set_mailbox_status_reg(env.mailbox_status_reg); ret.set_mailbox_err_reg(env.mailbox_err_reg); ret.set_fpga_temp_degc(env.fpga_temp_C); ret.set_current_edge_12v_a(static_cast(env.fpga_pcie_12V_I_mA) / 1000.0); ret.set_voltage_edge_12v_v(static_cast(env.fpga_pcie_12V_V_mV) / 1000.0); ret.set_current_edge_3p3v_a(static_cast(env.fpga_pcie_3p3V_I_mA) / 1000.0); ret.set_voltage_edge_3p3v_v(static_cast(env.fpga_pcie_3p3V_V_mV) / 1000.0); ret.set_pcie_c2h_beats(env.pcie_c2h_beats); ret.set_pcie_h2c_beats(env.pcie_h2c_beats); ret.set_pcie_c2h_descriptors(env.pcie_c2h_descriptors); ret.set_pcie_h2c_descriptors(env.pcie_h2c_descriptors); ret.set_pcie_c2h_status(env.pcie_c2h_status); ret.set_pcie_h2c_status(env.pcie_h2c_status); ret.set_ethernet_rx_aligned(env.ethernet_aligned); ret.set_hbm_temp_0_degc(env.hbm_0_temp_C); ret.set_hbm_temp_1_degc(env.hbm_1_temp_C); ret.set_slowest_head(counters.GetSlowestHead()); return ret; } void FPGAAcquisitionDevice::SetFPGANonBlockingMode(bool input) { fpga_non_blocking_mode = input; } void FPGAAcquisitionDevice::SetCustomInternalGeneratorFrame(const std::vector &v) { if (v.size() != RAW_MODULE_SIZE) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Error in size of custom internal generator frame"); for (int i = 0; i < RAW_MODULE_SIZE; i++) internal_pkt_gen_frame[i] = v[i]; } std::vector FPGAAcquisitionDevice::GetInternalGeneratorFrame() const { return internal_pkt_gen_frame; } FPGAAcquisitionDevice::FPGAAcquisitionDevice(uint16_t data_stream) : AcquisitionDevice(data_stream), internal_pkt_gen_frame(RAW_MODULE_SIZE) { for (int i = 0; i < RAW_MODULE_SIZE; i++) internal_pkt_gen_frame[i] = i % 65536; }