diff --git a/slsDetectorServers/matterhornServer/CMakeLists.txt b/slsDetectorServers/matterhornServer/CMakeLists.txt index e147cc6b8..47d7fa1f6 100644 --- a/slsDetectorServers/matterhornServer/CMakeLists.txt +++ b/slsDetectorServers/matterhornServer/CMakeLists.txt @@ -2,6 +2,7 @@ set(MATTERHORN_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/MatterhornApp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SPICommunication.cpp ) if(SLS_USE_SIMULATOR) diff --git a/slsDetectorServers/matterhornServer/include/BaseMatterhornServer.h b/slsDetectorServers/matterhornServer/include/BaseMatterhornServer.h index 3ecd0c6c5..8ea72b8a0 100644 --- a/slsDetectorServers/matterhornServer/include/BaseMatterhornServer.h +++ b/slsDetectorServers/matterhornServer/include/BaseMatterhornServer.h @@ -3,6 +3,7 @@ #include "DetectorServer.h" #include "MemoryModel.hpp" #include "RegisterDefs.hpp" +#include "SPICommunication.h" #include "SpecializedTemplates.h" #include "TCPInterface.h" #include "fmt/format.h" @@ -55,6 +56,10 @@ class BaseMatterhornServer ReturnCode set_module_position(ServerInterface &socket); + ReturnCode set_counter_mask(ServerInterface &socket); + + ReturnCode get_counter_mask(ServerInterface &socket) const; + uint64_t getNumFrames() const; void setNumFrames(const uint64_t num_frames); @@ -75,12 +80,19 @@ class BaseMatterhornServer protected: using MemoryModel = std::conditional_t< std::is_same_v, - VirtualMemoryModel, HardwareMemoryModel>; + VirtualMemoryModel, HardwareMemoryModel>; // 32 bit registers // TODO: for now in MatterhornServer and not generic Server but can be // templated on different IPCore types for each detector BusCommunication busCommunication{}; + using SPICommunicationClass = std::conditional_t< + std::is_same_v, + SPICommunication>, + SPICommunication>; + + SPICommunicationClass spiCommunication{}; + // TODO: probably virtaul server specific details, can be moved to derived // class /// @brief initial setup of detector @@ -99,6 +111,10 @@ BaseMatterhornServer::processFunction(const detFuncs function_id, ServerInterface &socket) { switch (function_id) { + case detFuncs::F_SET_COUNTER_MASK: + return set_counter_mask(socket); + case detFuncs::F_GET_COUNTER_MASK: + return get_counter_mask(socket); default: throw RuntimeError( fmt::format("Function {} not implemented", @@ -314,4 +330,61 @@ ReturnCode BaseMatterhornServer::set_module_position( return static_cast(socket.Send(ReturnCode::OK)); } +template +ReturnCode +BaseMatterhornServer::set_counter_mask(ServerInterface &socket) { + + // TODO: update properly + + uint32_t counter_mask{}; + try { + int ret = socket.Receive(counter_mask); + } catch (const SocketError &e) { + LOG(logERROR) << "Failed to receive counter mask: " << e.what(); + return ReturnCode::FAIL; + } + + try { + auto reg_value = spiCommunication.SPIread( + SPIRegisters::NUM_COUNTERS.register_, + 0); // TODO: how to handle different chip ids -> e.g. broadcast do + // we want it to be configurable for different chip ids? - + // Command overload for some of the SPI registers + + setSPIRegisterField(reg_value, SPIRegisters::NUM_COUNTERS, + counter_mask); + + spiCommunication.SPIwrite(SPIRegisters::NUM_COUNTERS.register_, 0, + reg_value); + } catch (const std::exception &e) { + LOG(logERROR) << "Failed to set counter mask: " << e.what(); + return ReturnCode::FAIL; + } + + return static_cast(socket.Send(ReturnCode::OK)); +} + +template +ReturnCode BaseMatterhornServer::get_counter_mask( + ServerInterface &socket) const { + + // TODO: update properly + + std::vector reg_value{}; + try { + reg_value = spiCommunication.SPIread( + SPIRegisters::NUM_COUNTERS.register_, + 0); // TODO: how to handle different chip ids - + } catch (const std::exception &e) { + LOG(logERROR) << "Failed to read counter mask from SPI register: " + << e.what(); + return ReturnCode::FAIL; + } + + uint32_t counter_mask = + getSPIRegisterField(reg_value, SPIRegisters::NUM_COUNTERS); + + return static_cast(socket.sendResult(counter_mask)); +} + } // namespace sls \ No newline at end of file diff --git a/slsDetectorServers/matterhornServer/include/MatterhornDefs.hpp b/slsDetectorServers/matterhornServer/include/MatterhornDefs.hpp new file mode 100644 index 000000000..7123af31a --- /dev/null +++ b/slsDetectorServers/matterhornServer/include/MatterhornDefs.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + +namespace sls { + +namespace MatterhornDefs { + +constexpr uint8_t NUM_CHIPS_PER_MODULE = 8; + +struct MatterhornSPIRegisters { + + constexpr static std::array spiregisters{ + SPIRegisters::SPI_REG_ConfigCML, + SPIRegisters::SPI_REG_ManualSelector, + SPIRegisters::SPI_REG_CoreRSTUnit, + SPIRegisters::SPI_REG_StoreRSTUnit, + SPIRegisters::SPI_REG_Trimbits, + SPIRegisters::SPI_REG_McGyver, + SPIRegisters::SPI_REG_McGyver_par_load, + SPIRegisters::SPI_REG_ActionReg, + SPIRegisters::SPI_REG_InternalDACs, + SPIRegisters::SPI_REG_ChecksumReg}; + + constexpr static uint8_t NUM_CHIPS_PER_MODULE = + MatterhornDefs::NUM_CHIPS_PER_MODULE; +}; + +} // namespace MatterhornDefs + +} // namespace sls \ No newline at end of file diff --git a/slsDetectorServers/matterhornServer/include/SPICommunication.h b/slsDetectorServers/matterhornServer/include/SPICommunication.h new file mode 100644 index 000000000..c2320f568 --- /dev/null +++ b/slsDetectorServers/matterhornServer/include/SPICommunication.h @@ -0,0 +1,172 @@ +#include "MemoryModel.hpp" +#include "SPIRegisterDefs.hpp" +#include "SPIRegisterHelperStructs.hpp" +#include "fmt/format.h" +#include "sls/logger.h" +#include +#include + +namespace sls { + +/// @brief abstract base class for SPI communication +template class SPICommunication { + + public: + SPICommunication(); + + ~SPICommunication(); + + std::vector SPIread(const SPIRegister &spi_reg, + const uint8_t chip_id) const; + + void SPIwrite(const SPIRegister &spi_reg, const uint8_t chip_id, + const std::vector &data); +}; + +template +SPICommunication::SPICommunication() { + static_cast(this)->map_to_memory(); +} + +template +SPICommunication::~SPICommunication() { + static_cast(this)->unmap_memory(); +} + +template +std::vector +SPICommunication::SPIread(const SPIRegister &spi_reg, + const uint8_t chip_id) const { + return static_cast(this)->spi_read( + spi_reg.n_bytes, chip_id, spi_reg.spi_register_id); +} + +template +void SPICommunication::SPIwrite( + const SPIRegister &spi_reg, const uint8_t chip_id, + const std::vector &data) { + + if (data.size() != spi_reg.n_bytes) { + LOG(logERROR) << fmt::format("Data size {} does not match number of " + "bytes {} for SPI register {}", + data.size(), spi_reg.n_bytes, + spi_reg.spi_register_id); + throw std::runtime_error( + fmt::format("Data size {} does not match number of bytes {} for " + "SPI register {}", + data.size(), spi_reg.n_bytes, spi_reg.spi_register_id)); + } + + static_cast(this)->spi_write( + chip_id, spi_reg.spi_register_id, data); + + static_cast(this)->spi_write( + chip_id, SPIRegisters::SPI_REG_ExtraClocks.spi_register_id, + std::vector{ + std::byte{0x00}}); // extra clock trigger to actually load the + // new value into the register +} + +/** + * Non destructive read from SPI register. Read n_bytes by shifting in + * dummy data while keeping csn 0 after the operation. Shift the read out + * data back in to restore the register. + */ +class HardwareSPICommunication + : public SPICommunication { + + public: + void map_to_memory(); + + void unmap_memory(); + + void spi_write(const uint8_t chip_id, const uint8_t register_id, + const std::vector &data); + + std::vector spi_read(const size_t n_bytes, const uint8_t chip_id, + const uint8_t register_id) const; + + private: + int spi_filedescriptor = -1; +}; + +// template // non +// type template parameters only for c++20 +template // TODO add a type trait to ensure it stores + // all fields +class VirtualSPICommunication + : public SPICommunication> { + + public: + VirtualSPICommunication() { + // TODO should it be in the constructor? + /* + (virtual_registers.emplace( + SPIRegisters.spi_register_id, + VirtualMemoryModel{SPIRegisters.spi_register_id, + SPIRegisters.n_bytes * + NUM_CHIPS_PER_MODULE}), + ...); + */ + + for (const auto ® : SPIRegisters::spiregisters) { + virtual_registers.emplace( + reg.spi_register_id, + VirtualMemoryModel{ + reg.spi_register_id, + reg.n_bytes * SPIRegisters::NUM_CHIPS_PER_MODULE}); + } + } + + void map_to_memory() { + + // resize the virtual register memory to the correct size based on + // the defined SPI registers + for (auto &[register_id, register_memory] : virtual_registers) { + register_memory.mapToMemory(); + } + } + + void unmap_memory() { // do nothing + } + + std::vector spi_read(const size_t n_bytes, const uint8_t chip_id, + const uint8_t register_id) const { + + auto mapped_register = + virtual_registers.at(register_id).getMappedMemoryPtr(); + + mapped_register += + chip_id * n_bytes; // TODO: how to handle different chip ids -> + // e.g. broadcast do we want it to be + // configurable for different chip ids? + + // TODO: should I emulate the shifting in of dummy data and shifting + // out of the register data here to be more realistic? + std::vector output_data(n_bytes); + std::memcpy(output_data.data(), mapped_register, n_bytes); + + return output_data; + } + + void spi_write(const uint8_t chip_id, const uint8_t register_id, + const std::vector &data) { + + auto mapped_register = + virtual_registers.at(register_id).getMappedMemoryPtr(); + + mapped_register += + chip_id * data.size(); // TODO: how to handle different + // chip ids -> e.g. broadcast do + + // TODO: should I emulate the shifting in of dummy data and shifting + // out of + std::memcpy(mapped_register, data.data(), data.size()); + } + + private: + /// @brief map of register id to virtual memory model for each register + std::map> virtual_registers{}; +}; + +} // namespace sls diff --git a/slsDetectorServers/matterhornServer/include/SPIRegisterDefs.hpp b/slsDetectorServers/matterhornServer/include/SPIRegisterDefs.hpp new file mode 100644 index 000000000..f55b26096 --- /dev/null +++ b/slsDetectorServers/matterhornServer/include/SPIRegisterDefs.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "SPIRegisterHelperStructs.hpp" +#include + +namespace sls { + +namespace SPIRegisters { + +// SPI registers +constexpr SPIRegister SPI_REG_ConfigUnit{0, 8}; + +constexpr SPIRegister SPI_REG_ConfigCML{1, 1}; + +constexpr SPIRegister SPI_REG_ManualSelector{2, 2}; + +constexpr SPIRegister SPI_REG_CoreRSTUnit{3, 4}; + +constexpr SPIRegister SPI_REG_StoreRSTUnit{4, 2}; + +constexpr SPIRegister SPI_REG_Trimbits{5, 256}; + +constexpr SPIRegister SPI_REG_McGyver{6, 512}; + +constexpr SPIRegister SPI_REG_McGyver_par_load{7, 512}; + +constexpr SPIRegister SPI_REG_ActionReg{11, 1}; + +constexpr SPIRegister SPI_REG_InternalDACs{13, 4}; + +constexpr SPIRegister SPI_REG_ChecksumReg{14, 32}; + +// Used to generate extra clocks after writing to trigger the load of the new +// value +constexpr SPIRegister SPI_REG_ExtraClocks{12, + 1}; // TODO: dont know what size is + +// SPI register fields +constexpr SPIRegisterField OUTPUT_MODE{SPI_REG_ConfigUnit, 4, 3}; + +/// @brief first two bits starting counter, second two bits number of counters +/// to read e.g. 0001 -> read counter 0 and 1, 0100 -> read counter 1 +constexpr SPIRegisterField NUM_COUNTERS{SPI_REG_ConfigUnit, 8, 4}; + +/// @brief 00-> 16 bit, 01 -> 8 bit, 10 -> 4 bit, 11 -> reserved 16 bit +constexpr SPIRegisterField DYNAMIC_RANGE{SPI_REG_ConfigUnit, 14, 2}; + +// TODO: continue defining the rest of the fields as needed + +} // namespace SPIRegisters + +} // namespace sls \ No newline at end of file diff --git a/slsDetectorServers/matterhornServer/include/SPIRegisterHelperStructs.hpp b/slsDetectorServers/matterhornServer/include/SPIRegisterHelperStructs.hpp new file mode 100644 index 000000000..ca9b78156 --- /dev/null +++ b/slsDetectorServers/matterhornServer/include/SPIRegisterHelperStructs.hpp @@ -0,0 +1,84 @@ +#pragma once +#include "fmt/format.h" +#include +#include + +namespace sls { + +struct SPIRegister { + /// @brief SPI register ID (0-15) + uint16_t spi_register_id{}; + + /// @brief number of bytes in the register + uint64_t n_bytes{}; +}; + +struct SPIRegisterField { + /// @brief SPI register to which teh field belongs + SPIRegister register_{}; + + /// @brief least significant bit position of the field in the register + uint64_t lsb_position{}; + + /// @brief number of bits in the field + /// TODO: can it be larger? + uint32_t num_bits{}; +}; + +// TODO: maybe change uint32_t but max field size is 32 bits so should be fine +// for now +void inline setSPIRegisterField(std::vector ®ister_value, + const SPIRegisterField &field, + uint32_t field_value) { + + // check that the field value can fit in the bitmask + if (field_value > field.num_bits) { + throw std::invalid_argument(fmt::format( + "Value {} cannot fit in field {}", field_value, field.num_bits)); + } + + constexpr uint8_t bits_per_byte = 8; + + // TODO: mmh doesnt feel very modern - maybe better to cast to uint32_t, + // alignment issues? + for (std::size_t i = 0; i < field.num_bits; ++i) { + std::size_t offset = field.lsb_position + i; + std::size_t byte_index = offset / bits_per_byte; + std::size_t bit_index = offset % bits_per_byte; + + std::byte mask = std::byte(1) << bit_index; + + register_value[byte_index] &= + ~mask; // clear the bit in the register value + + register_value[byte_index] |= std::byte( + ((field_value >> i) & 0x1) + << bit_index); // set the new field value bit in the register value + } +} + +uint32_t inline getSPIRegisterField( + const std::vector ®ister_value, + const SPIRegisterField &field) { + + uint32_t field_value = 0; + constexpr uint8_t bits_per_byte = 8; + + for (std::size_t i = 0; i < field.num_bits; ++i) { + std::size_t offset = field.lsb_position + i; + std::size_t byte_index = offset / bits_per_byte; + std::size_t bit_index = offset % bits_per_byte; + + std::byte mask = std::byte(1) << bit_index; + + field_value |= + (static_cast((register_value[byte_index] & mask) >> + bit_index) + << i); // extract the field value bit from the register value and + // set it in the correct position in the field value + } + + return field_value; +} + +} // namespace sls \ No newline at end of file diff --git a/slsDetectorServers/matterhornServer/include/SpecializedTemplates.h b/slsDetectorServers/matterhornServer/include/SpecializedTemplates.h index 4f57e825a..f051ccca9 100644 --- a/slsDetectorServers/matterhornServer/include/SpecializedTemplates.h +++ b/slsDetectorServers/matterhornServer/include/SpecializedTemplates.h @@ -1,5 +1,6 @@ #pragma once #include "ArmBusCommunication.hpp" +#include "MemoryModel.hpp" #include "RegisterDefs.hpp" /** @@ -7,7 +8,6 @@ * @short contains specializations of the template classes for the Matterhorn * server implementation */ - namespace sls { template @@ -20,7 +20,7 @@ struct IpCoreRegisterBlock { } private: - std::map memoryblocks_{ + std::map memoryblocks_{ {IPCore::MH_RO_SM_AXI, MemoryModel{static_cast(IPCore::MH_RO_SM_AXI), IPCORE_REGISTER_BLOCK_SIZE}}, diff --git a/slsDetectorServers/matterhornServer/src/SPICommunication.cpp b/slsDetectorServers/matterhornServer/src/SPICommunication.cpp new file mode 100644 index 000000000..d18f71382 --- /dev/null +++ b/slsDetectorServers/matterhornServer/src/SPICommunication.cpp @@ -0,0 +1,122 @@ +#include "SPICommunication.h" +#include +#include +#include +#include + +namespace sls { + +void HardwareSPICommunication::map_to_memory() { + + // TODO device can change + int spi_filedescriptor = open("/dev/spidev2.0", O_RDWR); // TODO use O_SYNC? + + LOG(logINFO) << fmt::format("SPI Read: opened spidev2.0 with fd={}", + spi_filedescriptor); + + if (spi_filedescriptor < 0) { + LOG(logERROR) << "Could not open /dev/spidev2.0"; + throw std::runtime_error("Could not open /dev/spidev2.0"); + } +} + +void HardwareSPICommunication::unmap_memory() { + if (spi_filedescriptor >= 0) { + close(spi_filedescriptor); + LOG(logINFO) << "SPI Read: closed spidev2.0"; + } +} + +std::vector +HardwareSPICommunication::spi_read(const size_t n_bytes, const uint8_t chip_id, + const uint8_t register_id) const { + + // allocate dummy data to shift out the data (first byte is command byte) + std::vector dummy_data(n_bytes + 1); + + // First byte of the message is 4 bits chip_id then 4 bits register_id + dummy_data[0] = + static_cast(((chip_id & 0xF) << 4) | (register_id & 0xF)); + + // allocate data buffer to read out data into + std::vector read_data_buffer( + n_bytes + 1, std::byte{0x00}); // TODO: is it neccessary to initialize? + + spi_ioc_transfer send_cmd{}; + send_cmd.len = n_bytes + 1; // +1 for the command byte + send_cmd.tx_buf = reinterpret_cast(dummy_data.data()); + send_cmd.rx_buf = reinterpret_cast(read_data_buffer.data()); + + // 0 - Normal operation, 1 - CSN remains zero after operation + // We use cs_change = 1 to not close the SPI transaction and + // allow for shifting the read out data back in to restore the + // regitster + send_cmd.cs_change = 1; + + // transfer here + if (ioctl(spi_filedescriptor, SPI_IOC_MESSAGE(1), &send_cmd) < 0) { + + LOG(logERROR) << fmt::format("SPI write failed with {}:{}", errno, + strerror(errno)); + throw std::runtime_error( + fmt::format("SPI write failed with {}:{}", errno, strerror(errno))); + } + + // copy the read out data back to the dummy data buffer to shift it back in + send_cmd.tx_buf = send_cmd.rx_buf; + + send_cmd.cs_change = + 0; // end the SPI transaction after shifting back in the data + + if (ioctl(spi_filedescriptor, SPI_IOC_MESSAGE(1), &send_cmd) < 0) { + + LOG(logERROR) << fmt::format("SPI write failed with {}:{}", errno, + strerror(errno)); + throw std::runtime_error( + fmt::format("SPI write failed with {}:{}", errno, strerror(errno))); + } + + // copy data to output buffer + std::vector output_data(n_bytes); + std::memcpy(output_data.data(), + reinterpret_cast(send_cmd.tx_buf) + 1, n_bytes); + + return output_data; +} + +void HardwareSPICommunication::spi_write(const uint8_t chip_id, + const uint8_t register_id, + const std::vector &data) { + + const size_t n_bytes = data.size(); + + // First byte of the message is 4 bits chip_id then 4 bits register_id + std::vector write_data(n_bytes + 1); // +1 for the command byte + + write_data[0] = + static_cast(((chip_id & 0xF) << 4) | (register_id & 0xF)); + + std::memcpy(write_data.data() + 1, data.data(), n_bytes); + + std::vector read_back_buffer(n_bytes + + 1); // TODO: do we need this? + + spi_ioc_transfer send_cmd{}; + send_cmd.len = n_bytes + 1; // +1 for the command byte + send_cmd.tx_buf = reinterpret_cast(write_data.data()); + send_cmd.rx_buf = reinterpret_cast(read_back_buffer.data()); + + send_cmd.cs_change = + 0; // end the SPI transaction after the write (we dont need to shift + // back in data here since we are not doing a read) + + if (ioctl(spi_filedescriptor, SPI_IOC_MESSAGE(1), &send_cmd) < 0) { + + LOG(logERROR) << fmt::format("SPI write failed with {}:{}", errno, + strerror(errno)); + throw std::runtime_error( + fmt::format("SPI write failed with {}:{}", errno, strerror(errno))); + } +} + +} // namespace sls \ No newline at end of file diff --git a/slsDetectorServers/slsDetectorServer_cpp/include/MemoryModel.hpp b/slsDetectorServers/slsDetectorServer_cpp/include/MemoryModel.hpp index 06a6892cb..4131fb486 100644 --- a/slsDetectorServers/slsDetectorServer_cpp/include/MemoryModel.hpp +++ b/slsDetectorServers/slsDetectorServer_cpp/include/MemoryModel.hpp @@ -1,3 +1,4 @@ +#pragma once #include "fmt/format.h" #include #include @@ -30,22 +31,28 @@ class HardwareMemoryModel { /// @brief class to handle memory mapping and access for virtual IP cores (e.g. /// use software implementation of memory) -class VirtualMemoryModel { +template class VirtualMemoryModel { public: VirtualMemoryModel(const uint32_t IPcore_base_address, - const size_t size_memory_space_); + const size_t size_memory_space_) + : IPCore_base_address(IPcore_base_address), + size_memory_space(size_memory_space_) {} ~VirtualMemoryModel() = default; - void mapToMemory(); + void mapToMemory() { + mapped_memory.resize( + size_memory_space / + sizeof(DataType)); // TODO: should it be zero initialized? + } - uint32_t *getMappedMemoryPtr(); + DataType *getMappedMemoryPtr() { return mapped_memory.data(); } - const uint32_t *getMappedMemoryPtr() const; + const DataType *getMappedMemoryPtr() const { return mapped_memory.data(); } private: - std::vector mapped_memory{}; + std::vector mapped_memory{}; /// @brief offset of the IP core base address in the memory space, used for /// mapping diff --git a/slsDetectorServers/slsDetectorServer_cpp/src/MemoryModel.cpp b/slsDetectorServers/slsDetectorServer_cpp/src/MemoryModel.cpp index ed379ace3..0ba3b365a 100644 --- a/slsDetectorServers/slsDetectorServer_cpp/src/MemoryModel.cpp +++ b/slsDetectorServers/slsDetectorServer_cpp/src/MemoryModel.cpp @@ -51,23 +51,3 @@ void HardwareMemoryModel::unmapMemory() { } HardwareMemoryModel::~HardwareMemoryModel() { unmapMemory(); } - -VirtualMemoryModel::VirtualMemoryModel(const uint32_t IPcore_base_address, - const size_t size_memory_space_) - : IPCore_base_address(IPcore_base_address), - size_memory_space(size_memory_space_) {} - -void VirtualMemoryModel::mapToMemory() { - - mapped_memory.resize( - size_memory_space / - sizeof(uint32_t)); // TODO: should it be zero initialized? -} - -uint32_t *VirtualMemoryModel::getMappedMemoryPtr() { - return mapped_memory.data(); -} - -const uint32_t *VirtualMemoryModel::getMappedMemoryPtr() const { - return mapped_memory.data(); -} \ No newline at end of file