├── example ├── demo_python │ ├── __init__.py │ ├── GpioStatus.py │ ├── ProjectConsts.py │ ├── TrafficGen.py │ └── axi_dma_demo.py └── demo_cpp │ ├── inc │ ├── BoostPCH.hpp │ ├── UioGpioStatus.hpp │ ├── AxiTrafficGenLfsr.hpp │ ├── DataHandlerPrint.hpp │ ├── UioTrafficGen.hpp │ ├── ZupExampleProjectConsts.hpp │ └── Z7ioExampleProjectConsts.hpp │ ├── tests │ ├── test_UioGpioStatus.cpp │ └── test_UioTrafficGen.cpp │ └── src │ ├── UioTrafficGen.cpp │ ├── DataHandlerPrint.cpp │ └── axi_dma_demo.cpp ├── udmaio-config.cmake.in ├── docs ├── dma_mgmt_components.png ├── index.html ├── dma_mgmt_components.drawio └── doxygen-header.html ├── pyudmaio ├── pyproject.toml ├── pyudmaio │ ├── __init__.py │ ├── _UioReg.pyi │ ├── __init__.pyi │ └── _UioReg.py ├── src │ ├── DataHandlerPython.hpp │ └── DataHandlerPython.cpp └── setup.py ├── .gitmodules ├── inc └── udmaio │ ├── BoostPCH.hpp │ ├── Logging.hpp │ ├── DmaBufferAbstract.hpp │ ├── UDmaBuf.hpp │ ├── UioAxiDmaIf.hpp │ ├── FpgaMemBufferOverAxi.hpp │ ├── FpgaMemBufferOverXdma.hpp │ ├── DataHandlerSync.hpp │ ├── DataHandlerAbstract.hpp │ ├── ConcurrentQueue.hpp │ ├── UioIf.hpp │ ├── UioAxiVdmaIf.hpp │ ├── UioConfig.hpp │ ├── UioMemSgdma.hpp │ ├── RegAccessor.hpp │ ├── FrameFormat.hpp │ └── HwAccessor.hpp ├── .gitignore ├── cross-cmake.sh ├── src ├── UioIf.cpp ├── DmaBufferAbstract.cpp ├── Logging.cpp ├── DataHandlerSync.cpp ├── UDmaBuf.cpp ├── HwAccessor.cpp ├── DataHandlerAbstract.cpp ├── UioAxiDmaIf.cpp ├── FrameFormat.cpp ├── UioConfig.cpp ├── UioMemSgdma.cpp └── UioAxiVdmaIf.cpp ├── .github └── workflows │ └── doxygen.yml ├── .clang-format ├── LICENSE.txt ├── CMakeLists.txt ├── README.md └── tests └── test_UioMemSgdma.cpp /example/demo_python/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /udmaio-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | -------------------------------------------------------------------------------- /docs/dma_mgmt_components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroTCA-Tech-Lab/libudmaio/HEAD/docs/dma_mgmt_components.png -------------------------------------------------------------------------------- /pyudmaio/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel", 5 | "pybind11>=2.6.0", 6 | "bitstruct" 7 | ] 8 | 9 | build-backend = "setuptools.build_meta" 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "doxygen-awesome-css"] 2 | path = doxygen-awesome-css 3 | url = https://github.com/jothepro/doxygen-awesome-css.git 4 | [submodule "pyudmaio/pybind11"] 5 | path = pyudmaio/pybind11 6 | url = https://github.com/pybind/pybind11.git 7 | -------------------------------------------------------------------------------- /inc/udmaio/BoostPCH.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | -------------------------------------------------------------------------------- /example/demo_cpp/inc/BoostPCH.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build* 2 | /pyudmaio/build* 3 | /pyudmaio/pyudmaio/*.so 4 | /.vscode 5 | /.cache 6 | *.pyc 7 | *.egg-info 8 | 9 | # vim-specific things 10 | *~ 11 | [._]*.un~ 12 | 13 | # doxygen 14 | docs/libudmaio 15 | docs/example 16 | 17 | # venv in demo_python 18 | /example/demo_python/bin 19 | /example/demo_python/lib 20 | /example/demo_python/lib64 21 | /example/demo_python/pyvenv.cfg 22 | 23 | #...or anywhere else 24 | .venv 25 | -------------------------------------------------------------------------------- /pyudmaio/pyudmaio/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 2 | 3 | from pyudmaio.binding import UioIf, ConfigUio, ConfigXdma, UioDeviceLocation, UioRegion 4 | from pyudmaio.binding import FpgaMemBufferOverXdma, FpgaMemBufferOverAxi, UDmaBuf, UioAxiDmaIf, UioMemSgdma, DataHandler 5 | from pyudmaio.binding import LogLevel, set_logging_level 6 | from pyudmaio.binding import FrameFormat 7 | from pyudmaio._UioReg import UioReg 8 | -------------------------------------------------------------------------------- /cross-cmake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Cross-compiling with Visual Studio Code and Petalinux SDK: 4 | # Put following stuff into .vscode/settings.json (replace the value for PETALINUX_SDK with the actual location): 5 | # { 6 | # "cmake.environment": { "PETALINUX_SDK": "/mnt/yocto_500G/petalinux/environment-setup-aarch64-xilinx-linux" }, 7 | # "cmake.configureSettings": { "TARGET_HW": "ZUP" }, 8 | # "cmake.cmakePath": "${workspaceRoot}/cross-cmake.sh" 9 | # } 10 | # 11 | source ${PETALINUX_SDK} 12 | cmake "${@}" 13 | -------------------------------------------------------------------------------- /example/demo_python/GpioStatus.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 2 | 3 | from pyudmaio import UioIf, UioReg 4 | 5 | 6 | class GpioStatus(UioIf): 7 | def __init__(self, cfg): 8 | super().__init__('GpioStatus', cfg) 9 | self.GpioStatus = UioReg(self, 0x00000000, ( 10 | ('Reserved', 'u31'), 11 | ('DDR4_INIT_CMPLT', 'u1'), 12 | )) 13 | 14 | def is_ddr4_init_calib_complete(self): 15 | self.GpioStatus.rd() 16 | return self.GpioStatus.DDR4_INIT_CMPLT != 0 17 | -------------------------------------------------------------------------------- /src/UioIf.cpp: -------------------------------------------------------------------------------- 1 | #include "udmaio/UioIf.hpp" 2 | 3 | namespace udmaio { 4 | 5 | UioIf::UioIf(std::string name, UioDeviceLocation dev_loc) : _hw{dev_loc.hw_acc()}, _lg{_hw->_lg} { 6 | _lg.channel(name); 7 | } 8 | 9 | UioIf::~UioIf() {} 10 | 11 | void UioIf::arm_interrupt() { 12 | _hw->arm_interrupt(); 13 | } 14 | 15 | uint32_t UioIf::wait_for_interrupt() { 16 | return _hw->wait_for_interrupt(); 17 | } 18 | 19 | int UioIf::get_fd_int() const { 20 | return _hw->get_fd_int(); 21 | } 22 | 23 | void UioIf::enable_debug(bool enable) { 24 | _hw->enable_debug(enable); 25 | } 26 | } // namespace udmaio 27 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | libudmaio documentation 9 | 10 | 11 | 12 | 13 |

Doxygen source code documentation for libudmaio

14 | 15 |

16 | Library documentation 17 |

18 |

19 | Example project documentation 20 |

21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /pyudmaio/pyudmaio/_UioReg.pyi: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import bitstruct as bitstruct 3 | __all__ = ['UioReg', 'UioRegTest', 'bitstruct'] 4 | class UioReg: 5 | def __getattr__(self, name): 6 | ... 7 | def __init__(self, intf, offs, fields = None): 8 | ... 9 | def __setattr__(self, name, value): 10 | ... 11 | def rd(self): 12 | ... 13 | def wr(self, val = None): 14 | ... 15 | class UioRegTest: 16 | def __init__(self): 17 | ... 18 | def _rd32(self, offs): 19 | ... 20 | def _wr32(self, offs, val): 21 | ... 22 | def test(self): 23 | ... 24 | -------------------------------------------------------------------------------- /.github/workflows/doxygen.yml: -------------------------------------------------------------------------------- 1 | name: Doxygen to GitHub Pages 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3.3.0 12 | with: 13 | submodules: True 14 | 15 | - name: Doxygen build (libudmaio) 16 | uses: mattnotmitt/doxygen-action@1.9.5 17 | 18 | - name: Doxygen build (example) 19 | uses: mattnotmitt/doxygen-action@1.9.5 20 | with: 21 | working-directory: ./example 22 | 23 | - name: Doxygen deploy 24 | uses: peaceiris/actions-gh-pages@v3.9.1 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | publish_dir: ./docs 28 | -------------------------------------------------------------------------------- /example/demo_cpp/tests/test_UioGpioStatus.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE UioGpioStatus 2 | 3 | #define BOOST_TEST_DYN_LINK 4 | 5 | #include "UioGpioStatus.hpp" 6 | #include "udmaio/HwAccessor.hpp" 7 | #include 8 | 9 | BOOST_AUTO_TEST_CASE(UioGpioStatus_readback) { 10 | udmaio::HwAccessorPtr mock_hw = std::make_shared(4096); 11 | udmaio::UioDeviceLocation dev_loc{mock_hw}; 12 | 13 | auto gpio_stat = UioGpioStatus(dev_loc); 14 | gpio_stat.enable_debug(true); 15 | 16 | mock_hw->_wr32(0, 0); 17 | BOOST_TEST(gpio_stat.is_ddr4_init_calib_complete() == false); 18 | 19 | mock_hw->_wr32(0, 1); 20 | BOOST_TEST(gpio_stat.is_ddr4_init_calib_complete() == true); 21 | } 22 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Chromium 3 | 4 | IndentWidth: 4 5 | 6 | IncludeCategories: 7 | - Regex: "<[a-zA-Z0-9_]+>" # standard headers (no extension) 8 | Priority: 2 9 | - Regex: '<[a-zA-Z0-9/]+\.(h|hpp)>' # library headers (with extension) 10 | Priority: 3 11 | - Regex: ".*" 12 | Priority: 4 13 | 14 | IncludeBlocks: Regroup 15 | 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AllowAllArgumentsOnNextLine: false 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | AccessModifierOffset: -2 21 | 22 | BinPackArguments: false 23 | BinPackParameters: false 24 | ExperimentalAutoDetectBinPacking: false 25 | BreakBeforeBraces: Attach 26 | BreakConstructorInitializers: BeforeComma 27 | 28 | ReflowComments: false 29 | ColumnLimit: 100 30 | -------------------------------------------------------------------------------- /inc/udmaio/Logging.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace udmaio { 15 | 16 | using bls = boost::log::trivial::severity_level; 17 | using boost_logger = boost::log::sources::severity_channel_logger_mt; 18 | 19 | struct Logger { 20 | mutable boost_logger _lg; 21 | 22 | Logger() : _lg{} {}; 23 | Logger(std::string name) : _lg{boost::log::keywords::channel = name} {}; 24 | static void set_level(bls lvl); 25 | static void init(const int channel_padding); 26 | }; 27 | 28 | }; // namespace udmaio 29 | -------------------------------------------------------------------------------- /pyudmaio/pyudmaio/__init__.pyi: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from pyudmaio._UioReg import UioReg 3 | from pyudmaio.binding import ConfigUio 4 | from pyudmaio.binding import ConfigXdma 5 | from pyudmaio.binding import DataHandler 6 | from pyudmaio.binding import FpgaMemBufferOverAxi 7 | from pyudmaio.binding import FpgaMemBufferOverXdma 8 | from pyudmaio.binding import FrameFormat 9 | from pyudmaio.binding import LogLevel 10 | from pyudmaio.binding import UDmaBuf 11 | from pyudmaio.binding import UioAxiDmaIf 12 | from pyudmaio.binding import UioDeviceLocation 13 | from pyudmaio.binding import UioIf 14 | from pyudmaio.binding import UioMemSgdma 15 | from pyudmaio.binding import UioRegion 16 | from pyudmaio.binding.pybind11_detail_function_record_v1_system_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_1 import set_logging_level 17 | from . import _UioReg 18 | from . import binding 19 | __all__ = ['ConfigUio', 'ConfigXdma', 'DataHandler', 'FpgaMemBufferOverAxi', 'FpgaMemBufferOverXdma', 'FrameFormat', 'LogLevel', 'UDmaBuf', 'UioAxiDmaIf', 'UioDeviceLocation', 'UioIf', 'UioMemSgdma', 'UioReg', 'UioRegion', 'binding', 'set_logging_level'] 20 | -------------------------------------------------------------------------------- /src/DmaBufferAbstract.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2023 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/DmaBufferAbstract.hpp" 13 | 14 | namespace udmaio { 15 | 16 | void DmaBufferAbstract::append_from_buf(const UioRegion& buf_info, 17 | std::vector& out) const { 18 | size_t old_size = out.size(); 19 | size_t new_size = old_size + buf_info.size; 20 | out.resize(new_size); 21 | 22 | copy_from_buf(out.data() + old_size, buf_info); 23 | } 24 | 25 | } // namespace udmaio 26 | -------------------------------------------------------------------------------- /src/Logging.cpp: -------------------------------------------------------------------------------- 1 | #include "udmaio/Logging.hpp" 2 | 3 | namespace udmaio { 4 | 5 | namespace bl = boost::log; 6 | namespace expr = boost::log::expressions; 7 | 8 | void Logger::set_level(bls lvl) { 9 | bl::core::get()->set_filter(bl::trivial::severity >= lvl); 10 | } 11 | 12 | BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string) 13 | 14 | void Logger::init(const int channel_padding) { 15 | bl::add_common_attributes(); 16 | bl::add_console_log( 17 | std::cout, 18 | bl::keywords::format = (expr::stream << expr::format_date_time( 19 | "TimeStamp", 20 | "%Y-%m-%d %H:%M:%S.%f") 21 | 22 | << " [" << std::left << std::setw(5) 23 | << std::setfill(' ') << bl::trivial::severity << "] " 24 | 25 | << std::right << std::setw(channel_padding) 26 | << std::setfill(' ') << channel 27 | 28 | << ": " << expr::smessage)); 29 | } 30 | 31 | } // namespace udmaio 32 | -------------------------------------------------------------------------------- /example/demo_cpp/inc/UioGpioStatus.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include "udmaio/RegAccessor.hpp" 15 | 16 | using namespace udmaio; 17 | 18 | /// Interface to GPIO status port of the demo application 19 | class UioGpioStatus : public UioIf { 20 | struct REG_PACKED_ALIGNED GpioData { 21 | bool ddr4_init_calib_complete : 1; 22 | unsigned reserved : 31; 23 | }; 24 | 25 | RegAccessor gpio{this}; 26 | 27 | public: 28 | UioGpioStatus(UioDeviceLocation dev_loc) : UioIf("UioGpioStatus", dev_loc) {} 29 | 30 | bool is_ddr4_init_calib_complete() const { return gpio.rd().ddr4_init_calib_complete; } 31 | }; 32 | -------------------------------------------------------------------------------- /example/demo_cpp/tests/test_UioTrafficGen.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE UioTrafficGen 2 | 3 | #define BOOST_TEST_DYN_LINK 4 | 5 | #include "UioTrafficGen.hpp" 6 | #include "udmaio/HwAccessor.hpp" 7 | #include 8 | 9 | struct Fx { 10 | udmaio::HwAccessorPtr hw; 11 | udmaio::UioDeviceLocation dev_loc; 12 | 13 | UioTrafficGen traffic_gen; 14 | 15 | Fx() : hw{std::make_shared(4096)}, dev_loc{hw}, traffic_gen{dev_loc} { 16 | traffic_gen.enable_debug(true); 17 | } 18 | ~Fx() {} 19 | }; 20 | 21 | BOOST_FIXTURE_TEST_CASE(UioTrafficGen_start, Fx) { 22 | traffic_gen.start(42, 128, 10000); 23 | 24 | auto config_reg = hw->_rd32(0x34); 25 | BOOST_CHECK_MESSAGE(config_reg == (10000 << 16), "pkt_pause match"); 26 | 27 | auto tr_len = hw->_rd32(0x38); 28 | BOOST_CHECK_MESSAGE(tr_len == ((42 << 16) | 127), "tr_len match"); 29 | 30 | auto ext_tlen = hw->_rd32(0x50); 31 | BOOST_CHECK_MESSAGE(ext_tlen == 0, "ext_tlen match"); 32 | 33 | auto st_ctrl = hw->_rd32(0x30); 34 | BOOST_CHECK_MESSAGE((st_ctrl & 0b11) == 0b01, "st_ctrl match"); 35 | } 36 | 37 | BOOST_FIXTURE_TEST_CASE(UioTrafficGen_stop, Fx) { 38 | // set done bit 39 | hw->_wr32(0x30, hw->_rd32(0x30) | 0b10); 40 | 41 | traffic_gen.stop(); 42 | 43 | auto st_ctrl = hw->_rd32(0x30); 44 | BOOST_CHECK_MESSAGE((st_ctrl & 0b11) == 0b10, "st_ctrl match"); 45 | } 46 | -------------------------------------------------------------------------------- /docs/dma_mgmt_components.drawio: -------------------------------------------------------------------------------- 1 | 5VhNc9owEP01zLSHdCzLXxwTSJt0ykymHJochb3Gbm3LI0TA/fWVsOSP2BDSQCAtB0b7rLWkfftWkgd4lK6/MJJHExpAMjCNYD3A44FpDjES/xIoSsCxnBKYszgoIVQD0/g3KNBQ6DIOYNHqyClNeJy3QZ9mGfi8hRHG6KrdLaRJe9SczKEDTH2SdNEfccAjhSLDqB/cQDyP1NCerR6kRHdWwCIiAV01IHw9wCNGKS9b6XoEiYydjkvp93nL02piDDK+j4Pr38Q/kuXDV5wWBUq8m3jGLmw1N17oBUMg1q9MynhE5zQjyXWNXjG6zAKQbzWEVff5RmkuQCTAn8B5ocgkS04FFPE0UU9hHfP7RvtBvuqTaStzvFav3hiFNjLOivumUbq5trZrv42lHcsVymVtDZyCFnTJfNgRLZ2AhM2B7+iHK3qFLICmIOYj/BgkhMeP7XkQlaDzql/NoWgoGl9AKcLnw+luSk/AjHlSZk6qNi0VTc5fqe0MxYasV3KqXO9oLKZoGmrnsnWFVxsX0oVdv6KcmPKqM+OSMVI0uuWyw2L7OJZrt8cx7SeJVr6xTrtqja/IxHLIR5IsVXzGk8uB6SQi0FczJlpz2fowNScTuZeJXf1jJ3frzJRptopiDtOcbDhdibNAOwvDOElGNKFs44sDAl7oC3zBGf0FjSeO78EsrFLpERiH9e5k6nKvHLDb5tD0lL1qbOWWwqLmLm5sT5dXlQCzG3jCSTfyE0ipGu5QIXcs1/HcvpBjZGHbkh404w083PwOQ4XtPaHC2ZMKZB6LC9wvAmMMC5/FORdhkGP/d9SgfalxjsWM+15OMGewTTr7bpOnPPk4W8qeXl5357m8v7UuppwBSbs7j7hK5bK54CCFlQOLxTyBKeiutp8X4hr0rbNHmGEIjt+7TQXucGYYhxFgJS5dG4enFmB5pmoRdpuJgLJlzk/HBqDAht4yOXRcTJwjlUPUw4bRwwY+2qEBdWJ+9vVQXeerkviG94Z3URFRtySOpt+ll9RZKBWy/YRhvPy8bYMXWH3S8cwZdg4kHfzkzlRJoiEd702VY3SiePbKaZ8kzLe8cbt7Kue037fcf1A5loeePYMfSDnCrD86lx8U6i/3+PoP -------------------------------------------------------------------------------- /example/demo_cpp/inc/AxiTrafficGenLfsr.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2018-2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | /// Implements LFSR as described in "AXI Traffic Generator v3.0" 17 | class AxiTrafficGenLfsr { 18 | public: 19 | AxiTrafficGenLfsr(uint16_t seed) : val{seed} {}; 20 | 21 | /// Set seed to a specific value 22 | void set(uint16_t seed) { val = seed; } 23 | 24 | /// Advance LFSR by one iteration, return new value 25 | uint16_t advance() { 26 | uint16_t new_bit = 1 ^ (val) ^ (val >> 1) ^ (val >> 3) ^ (val >> 12); 27 | 28 | val = (new_bit << 15) | (val >> 1); 29 | return val; 30 | } 31 | 32 | /// Get value without advancing the LFSR 33 | uint16_t get() const { return val; } 34 | 35 | private: 36 | uint16_t val; 37 | }; 38 | -------------------------------------------------------------------------------- /src/DataHandlerSync.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/DataHandlerSync.hpp" 13 | 14 | namespace udmaio { 15 | 16 | DataHandlerSync::~DataHandlerSync() { 17 | if (_ioThread) { 18 | _ioThread->join(); 19 | } 20 | } 21 | 22 | void DataHandlerSync::operator()() { 23 | _ioThread = std::thread{&DataHandlerAbstract::operator(), this}; 24 | } 25 | 26 | void DataHandlerSync::process_data(std::vector bytes) { 27 | _queue.push(std::move(bytes)); 28 | } 29 | 30 | void DataHandlerSync::stop() { 31 | _queue.abort(); 32 | DataHandlerAbstract::stop(); 33 | } 34 | 35 | std::vector DataHandlerSync::read() { 36 | return _queue.pop(); 37 | } 38 | 39 | std::vector DataHandlerSync::read(std::chrono::milliseconds timeout) { 40 | return _queue.pop(timeout); 41 | } 42 | 43 | } // namespace udmaio 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022 Deutsches Elektronen-Synchrotron DESY 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /inc/udmaio/DmaBufferAbstract.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "udmaio/UioConfig.hpp" 20 | 21 | namespace udmaio { 22 | 23 | /// Base class for DMA data buffer 24 | class DmaBufferAbstract : private boost::noncopyable { 25 | protected: 26 | virtual void copy_from_buf(uint8_t* dest, const UioRegion& buf_info) const = 0; 27 | 28 | public: 29 | /// @brief Get physical region 30 | /// @return Physical address and size of DMA data buffer 31 | virtual UioRegion get_phys_region() const = 0; 32 | 33 | /// @brief Append received DMA data to vector 34 | /// @param buf_info Memory region of DMA buffer 35 | /// @param out Vector to append received data to 36 | void append_from_buf(const UioRegion& buf_info, std::vector& out) const; 37 | }; 38 | 39 | } // namespace udmaio 40 | -------------------------------------------------------------------------------- /inc/udmaio/UDmaBuf.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include "DmaBufferAbstract.hpp" 15 | #include "Logging.hpp" 16 | #include "udmaio/UioConfig.hpp" 17 | 18 | namespace udmaio { 19 | 20 | /// @brief DMA data buffer accessed over AXI/UIO, implemented w/ udmabuf 21 | /// (see https://github.com/ikwzm/udmabuf) 22 | class UDmaBuf : public DmaBufferAbstract, public Logger { 23 | int _fd; 24 | void* _mem; 25 | const UioRegion _phys; 26 | 27 | static size_t _get_size(int buf_idx); 28 | static uintptr_t _get_phys_addr(int buf_idx); 29 | void copy_from_buf(uint8_t* dest, const UioRegion& buf_info) const override; 30 | 31 | public: 32 | /// @brief Constructs a UDmaBuf 33 | /// @param buf_idx Buffer index `/dev/udmabufN` 34 | explicit UDmaBuf(int buf_idx = 0); 35 | 36 | virtual ~UDmaBuf(); 37 | 38 | UioRegion get_phys_region() const override; 39 | }; 40 | 41 | } // namespace udmaio 42 | -------------------------------------------------------------------------------- /pyudmaio/src/DataHandlerPython.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | #include 17 | 18 | #include "udmaio/DataHandlerSync.hpp" 19 | 20 | namespace py = pybind11; 21 | 22 | namespace udmaio { 23 | 24 | class DataHandlerPython : public DataHandlerSync { 25 | std::shared_ptr _dma_ptr; 26 | std::shared_ptr _desc_ptr; 27 | std::shared_ptr _mem_ptr; 28 | 29 | public: 30 | DataHandlerPython(std::shared_ptr, 31 | std::shared_ptr, 32 | std::shared_ptr, 33 | bool receive_packets = true, 34 | size_t queue_size = 64, 35 | bool rt_prio = false); 36 | 37 | void start(int nr_pkts, size_t pkt_size, bool init_only = false); 38 | py::array_t numpy_read(uint32_t ms_timeout = 0); 39 | py::array_t numpy_read_nb(); 40 | }; 41 | 42 | } // namespace udmaio 43 | -------------------------------------------------------------------------------- /example/demo_python/ProjectConsts.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 2 | 3 | from pyudmaio import UioDeviceLocation, UioRegion 4 | 5 | 6 | class ZupExampleConsts(object): 7 | AXI_GPIO_STATUS = UioDeviceLocation( 8 | 'hier_interconnect_axi_gpio_status', UioRegion( 9 | 0x0005_0000, 4 * 1024 10 | ) 11 | ) 12 | AXI_DMA_0 = UioDeviceLocation( 13 | 'hier_daq_arm_axi_dma', UioRegion( 14 | 0x0022_2000, 4 * 1024 15 | ), 'events0' 16 | ) 17 | BRAM_CTRL_0 = UioDeviceLocation( 18 | 'hier_daq_arm_axi_bram_ctrl', UioRegion( 19 | 0x0022_0000, 8 * 1024 20 | ) 21 | ) 22 | AXI_TRAFFIC_GEN_0 = UioDeviceLocation( 23 | 'hier_daq_arm_axi_traffic_gen', UioRegion( 24 | 0x0021_0000, 64 * 1024 25 | ) 26 | ) 27 | 28 | FPGA_MEM_PHYS_ADDR = 0x5_0000_0000 29 | PCIE_AXI4L_OFFSET = 0x0_a000_0000 30 | LFSR_BYTES_PER_BEAT = 16 31 | 32 | 33 | class Z7ioExampleConsts(object): 34 | AXI_GPIO_STATUS = UioDeviceLocation( 35 | 'hier_interconnect_axi_gpio_status', UioRegion( 36 | 0x0005_0000, 4 * 1024 37 | ) 38 | ) 39 | AXI_DMA_0 = UioDeviceLocation( 40 | 'hier_daq_axi_dma_0', UioRegion( 41 | 0x0022_2000, 4 * 1024 42 | ), 'events0' 43 | ) 44 | BRAM_CTRL_0 = UioDeviceLocation( 45 | 'hier_daq_axi_bram_ctrl_0', UioRegion( 46 | 0x0012_0000, 8 * 1024 47 | ) 48 | ) 49 | AXI_TRAFFIC_GEN_0 = UioDeviceLocation( 50 | 'hier_daq_axi_traffic_gen_0', UioRegion( 51 | 0x0021_0000, 64 * 1024 52 | ) 53 | ) 54 | 55 | FPGA_MEM_PHYS_ADDR = 0x0_3c00_0000 56 | PCIE_AXI4L_OFFSET = 0x0_4400_0000 57 | LFSR_BYTES_PER_BEAT = 8 58 | -------------------------------------------------------------------------------- /example/demo_cpp/inc/DataHandlerPrint.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include "AxiTrafficGenLfsr.hpp" 18 | #include "udmaio/DataHandlerAbstract.hpp" 19 | 20 | using namespace udmaio; 21 | 22 | /// DataHandler implementation that checks LFSR data and prints status / stats 23 | class DataHandlerPrint : public DataHandlerAbstract { 24 | std::optional lfsr; 25 | 26 | void process_data(std::vector bytes) override; 27 | 28 | uint64_t _counter_ok; 29 | uint64_t _counter_total; 30 | const unsigned int _num_bytes_per_beat; 31 | const uint64_t _num_bytes_expected; 32 | uint64_t _num_bytes_rcvd; 33 | 34 | public: 35 | explicit DataHandlerPrint(UioAxiDmaIf& dma, 36 | UioMemSgdma& desc, 37 | DmaBufferAbstract& mem, 38 | unsigned int num_bytes_per_beat, 39 | uint64_t num_bytes_expected, 40 | bool rt_prio); 41 | 42 | std::pair operator()(); 43 | }; 44 | -------------------------------------------------------------------------------- /inc/udmaio/UioAxiDmaIf.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "RegAccessor.hpp" 20 | #include "UioIf.hpp" 21 | #include "udmaio/rdl/AxiDma.hpp" 22 | namespace udmaio { 23 | 24 | using AxiDmaBlock = axi_dma::block; 25 | 26 | /// Interface to AXI DMA Core 27 | class UioAxiDmaIf : public UioIf, AxiDmaBlock { 28 | public: 29 | UioAxiDmaIf(UioDeviceLocation dev_loc) : UioIf{"AxiDma", dev_loc}, AxiDmaBlock{this} {} 30 | 31 | /// @brief Configure and start the AXI DMA controller 32 | /// @param start_desc Address of first SGDMA descriptor 33 | void start(uintptr_t start_desc); 34 | uintptr_t get_curr_desc(); 35 | 36 | using UioIf::arm_interrupt; 37 | 38 | /// Wait for interrupt and acknowledge it 39 | std::tuple clear_interrupt(); 40 | 41 | /// @brief Check status register and log any errors 42 | /// @return true if any error occurred 43 | bool check_for_errors(); 44 | 45 | /// @brief Dump all status register flags in the log 46 | void dump_status(); 47 | }; 48 | 49 | } // namespace udmaio 50 | -------------------------------------------------------------------------------- /inc/udmaio/FpgaMemBufferOverAxi.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "DmaBufferAbstract.hpp" 21 | #include "UioIf.hpp" 22 | 23 | namespace udmaio { 24 | 25 | /// DMA data buffer accessed over AXI/UIO, described w/ explicit address & size 26 | class FpgaMemBufferOverAxi : public DmaBufferAbstract, public UioIf { 27 | public: 28 | FpgaMemBufferOverAxi(UioDeviceLocation dev_loc) : UioIf("FpgaMemBufferOverAxi", dev_loc) {} 29 | 30 | UioRegion get_phys_region() const override { return _hw->get_phys_region(); } 31 | 32 | protected: 33 | void copy_from_buf(uint8_t* dest, const UioRegion& buf_info) const override { 34 | BOOST_LOG_SEV(_lg, bls::trace) 35 | << "FpgaMemBufferOverAxi: append_from_buf: buf_info.addr = 0x" << std::hex 36 | << buf_info.addr << std::dec; 37 | BOOST_LOG_SEV(_lg, bls::trace) 38 | << "FpgaMemBufferOverAxi: append_from_buf: buf_info.size = " << buf_info.size; 39 | uintptr_t mmap_addr = buf_info.addr - get_phys_region().addr; 40 | std::memcpy(dest, static_cast(_hw->get_virt_mem()) + mmap_addr, buf_info.size); 41 | } 42 | }; 43 | 44 | } // namespace udmaio 45 | -------------------------------------------------------------------------------- /example/demo_cpp/inc/UioTrafficGen.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include "udmaio/RegAccessor.hpp" 15 | 16 | using namespace udmaio; 17 | 18 | /// Interface to LFSR core of the demo application 19 | class UioTrafficGen : public UioIf { 20 | struct REG_PACKED_ALIGNED StControl { 21 | bool stren : 1; 22 | bool done : 1; 23 | unsigned rsvd : 22; 24 | unsigned version : 8; 25 | }; 26 | 27 | struct REG_PACKED_ALIGNED StConfig { 28 | bool ranlen : 1; 29 | bool randly : 1; 30 | bool etkts : 1; 31 | unsigned rsvd7_3 : 5; 32 | unsigned tdest : 8; 33 | unsigned pdly : 16; 34 | }; 35 | 36 | struct REG_PACKED_ALIGNED TrLength { 37 | unsigned tlen : 16; 38 | unsigned tcnt : 16; 39 | }; 40 | 41 | struct REG_PACKED_ALIGNED ExtTrLength { 42 | unsigned ext_tlen : 8; 43 | unsigned rsvd : 24; 44 | }; 45 | 46 | RegAccessor stControl{this}; 47 | RegAccessor stConfig{this}; 48 | RegAccessor trLength{this}; 49 | RegAccessor extTrLength{this}; 50 | 51 | public: 52 | UioTrafficGen(UioDeviceLocation dev_loc) : UioIf("UioTrafficGen", dev_loc) {} 53 | 54 | void start(uint16_t nr_pkts, uint32_t pkt_size, uint16_t pkt_pause); 55 | void stop(); 56 | 57 | void print_version() const; 58 | }; 59 | -------------------------------------------------------------------------------- /example/demo_cpp/src/UioTrafficGen.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "UioTrafficGen.hpp" 13 | 14 | #include 15 | 16 | void UioTrafficGen::start(uint16_t nr_pkts, uint32_t pkt_size, uint16_t pkt_pause) { 17 | BOOST_LOG_SEV(_lg, bls::trace) << "start, nr pkts = " << nr_pkts << ", pkt size = " << pkt_size; 18 | 19 | stop(); 20 | 21 | stConfig.wr({ 22 | .ranlen = 0, 23 | .randly = 0, 24 | .etkts = 0, 25 | .rsvd7_3 = 0, 26 | .tdest = 0, 27 | .pdly = pkt_pause, 28 | }); 29 | 30 | const auto num_beats_reg = pkt_size - 1; 31 | trLength.wr({ 32 | .tlen = num_beats_reg & 0xffff, 33 | .tcnt = nr_pkts, 34 | }); 35 | extTrLength.wr({ 36 | .ext_tlen = num_beats_reg >> 16, 37 | }); 38 | 39 | auto st_ctrl = stControl.rd(); 40 | st_ctrl.done = 0; 41 | st_ctrl.stren = 1; 42 | stControl.wr(st_ctrl); 43 | } 44 | 45 | void UioTrafficGen::stop() { 46 | auto st_ctrl = stControl.rd(); 47 | st_ctrl.stren = 0; 48 | if (st_ctrl.done) { 49 | BOOST_LOG_SEV(_lg, bls::trace) << "clearing done bit"; 50 | // W1C – Write 1 to Clear 51 | st_ctrl.done = 1; 52 | } 53 | stControl.wr(st_ctrl); 54 | } 55 | 56 | void UioTrafficGen::print_version() const { 57 | BOOST_LOG_SEV(_lg, bls::info) << "version = 0x" << std::hex << stControl.rd().version 58 | << std::dec; 59 | } 60 | -------------------------------------------------------------------------------- /example/demo_python/TrafficGen.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 2 | 3 | from pyudmaio import UioIf, UioReg 4 | 5 | 6 | class TrafficGen(UioIf): 7 | def __init__(self, cfg): 8 | super().__init__('TrafficGen', cfg) 9 | self.StControl = UioReg(self, 0x30, ( 10 | ('Version', 'u8'), 11 | ('Reserved', 'u22'), 12 | ('Done', 'u1'), 13 | ('STREN', 'u1') 14 | )) 15 | self.StConfig = UioReg(self, 0x34, ( 16 | ('PDLY', 'u16'), 17 | ('TDEST', 'u8'), 18 | ('Reserved', 'u5'), 19 | ('ETKTS', 'u1'), 20 | ('RANDLY', 'u1'), 21 | ('RANLEN', 'u1'), 22 | )) 23 | self.TxLength = UioReg(self, 0x38, ( 24 | ('TCNT', 'u16'), 25 | ('TLEN', 'u16'), 26 | )) 27 | self.ExTxLength = UioReg(self, 0x50, ( 28 | ('Reserved', 'u24'), 29 | ('Ext_TLEN', 'u8'), 30 | )) 31 | 32 | def start(self, nr_pkts=1, pkt_size=1024, pkt_pause=60000): 33 | print(f'start, nr pkts = {nr_pkts}, pkt size = {pkt_size}') 34 | 35 | self.stop() 36 | 37 | self.StConfig.RANLEN = 0 38 | self.StConfig.RANDLY = 0 39 | self.StConfig.ETKTS = 0 40 | self.StConfig.TDEST = 0 41 | self.StConfig.PDLY = pkt_pause 42 | self.StConfig.wr() 43 | 44 | num_beats_reg = pkt_size - 1 45 | 46 | self.TxLength.TLEN = num_beats_reg & 0xffff 47 | self.TxLength.TCNT = nr_pkts 48 | self.TxLength.wr() 49 | 50 | self.ExTxLength.Ext_TLEN = num_beats_reg >> 16 51 | self.ExTxLength.wr() 52 | 53 | self.StControl.rd() 54 | self.StControl.Done = 0 55 | self.StControl.STREN = 1 56 | self.StControl.wr() 57 | 58 | def stop(self): 59 | self.StControl.rd() 60 | self.StControl.STREN = 0 61 | if self.StControl.Done: 62 | print('clearing done bit') 63 | # W1C – Write 1 to Clear 64 | self.StControl.Done = 1 65 | self.StControl.wr() 66 | 67 | def version(self): 68 | self.StControl.rd() 69 | return self.StControl.Version 70 | -------------------------------------------------------------------------------- /inc/udmaio/FpgaMemBufferOverXdma.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "DmaBufferAbstract.hpp" 19 | 20 | namespace udmaio { 21 | 22 | /// DMA data buffer accessed over XDMA using the xdma c2h0 stream channel 23 | class FpgaMemBufferOverXdma : public DmaBufferAbstract { 24 | int _dma_fd; 25 | UioRegion _phys_region; 26 | 27 | public: 28 | /// @brief Constructs a DMA data buffer 29 | /// @param path Base path of XDMA instance in `/dev` 30 | /// @param phys_addr Physical address of DMA data buffer 31 | explicit FpgaMemBufferOverXdma(const std::string& path, uintptr_t phys_addr) 32 | : _phys_region{phys_addr, 0} { // FIXME: can we get the memory size? 33 | const std::string dev_path{path + "/c2h0"}; 34 | _dma_fd = open(dev_path.c_str(), O_RDWR); 35 | if (_dma_fd < 0) { 36 | throw std::runtime_error("could not open " + dev_path); 37 | } 38 | } 39 | virtual ~FpgaMemBufferOverXdma() { close(_dma_fd); } 40 | 41 | UioRegion get_phys_region() const override { return _phys_region; } 42 | 43 | protected: 44 | void copy_from_buf(uint8_t* dest, const UioRegion& buf_info) const override { 45 | lseek(_dma_fd, buf_info.addr, SEEK_SET); 46 | ssize_t rc = read(_dma_fd, dest, buf_info.size); 47 | if (rc < static_cast(buf_info.size)) { 48 | // TODO: error handling 49 | } 50 | } 51 | }; 52 | 53 | } // namespace udmaio 54 | -------------------------------------------------------------------------------- /inc/udmaio/DataHandlerSync.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "ConcurrentQueue.hpp" 20 | #include "DataHandlerAbstract.hpp" 21 | 22 | namespace udmaio { 23 | 24 | /// Synchronous data handler with blocking read interface 25 | class DataHandlerSync : public DataHandlerAbstract { 26 | ConcurrentQueue> _queue; ///< FIFO queue holding the received data 27 | std::optional _ioThread; ///< I/O thread 28 | 29 | public: 30 | explicit DataHandlerSync(std::string name, 31 | UioAxiDmaIf& dma, 32 | UioMemSgdma& desc, 33 | DmaBufferAbstract& mem, 34 | bool receive_packets = true, 35 | size_t queue_size = 64, 36 | bool rt_prio = false) 37 | : DataHandlerAbstract{name, dma, desc, mem, receive_packets, rt_prio}, _queue{queue_size} {} 38 | 39 | virtual ~DataHandlerSync(); 40 | 41 | /// Run the data reception 42 | void operator()(); 43 | 44 | void process_data(std::vector bytes) override; 45 | 46 | virtual void stop() override; 47 | 48 | /// @brief Blocking read 49 | /// @return Received data 50 | std::vector read(); 51 | 52 | /// @brief Blocking read with timeout 53 | /// @param timeout Timeout in milliseconds 54 | /// @return Received data 55 | std::vector read(std::chrono::milliseconds timeout); 56 | }; 57 | 58 | } // namespace udmaio 59 | -------------------------------------------------------------------------------- /example/demo_cpp/inc/ZupExampleProjectConsts.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * ____ _____________ __ __ __ _ _____ ___ _ * 3 | * / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ (R) * 4 | * / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ * 5 | * / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ * 6 | * /_____/_____//____/ /_/ T E C H N O L O G Y L A B * 7 | * * 8 | ***************************************************************************/ 9 | 10 | /* 11 | * Auto-generated by project-consts 12 | * https://msktechvcs.desy.de/techlab/software/internal/project-consts 13 | * 14 | * This file was generated with: 15 | * project-consts.py damc_fmc2zup_top.xsa -t 0xa0000000 -e axi_dma:events0 -o ZupExampleProjectConsts.hpp 16 | * 17 | */ 18 | 19 | #include "udmaio/UioConfig.hpp" 20 | 21 | // clang-format off 22 | 23 | namespace target_hw_consts { 24 | 25 | const udmaio::UioDeviceLocation axi_bram_ctrl { 26 | "hier_daq_arm_axi_bram_ctrl", 27 | { 0x0022'0000, 8 * 1024 }, 28 | }; 29 | 30 | const udmaio::UioDeviceLocation axi_dma { 31 | "hier_daq_arm_axi_dma", 32 | { 0x0022'2000, 4 * 1024 }, 33 | "events0", 34 | }; 35 | 36 | const udmaio::UioDeviceLocation axi_gpio_status { 37 | "hier_interconnect_axi_gpio_status", 38 | { 0x0005'0000, 4 * 1024 }, 39 | }; 40 | 41 | const udmaio::UioDeviceLocation axi_traffic_gen { 42 | "hier_daq_arm_axi_traffic_gen", 43 | { 0x0021'0000, 64 * 1024 }, 44 | }; 45 | 46 | const udmaio::UioDeviceLocation clk_monitor_0 { 47 | "clk_monitor_clk_monitor_0", 48 | { 0x0006'0000, 4 * 1024 }, 49 | }; 50 | 51 | const udmaio::UioDeviceLocation custom_irq_gen_apu { 52 | "hier_irq_gen_custom_irq_gen_apu", 53 | { 0x0030'0000, 4 * 1024 }, 54 | }; 55 | 56 | const udmaio::UioDeviceLocation custom_irq_gen_pcie { 57 | "hier_irq_gen_custom_irq_gen_pcie", 58 | { 0x0031'0000, 4 * 1024 }, 59 | }; 60 | 61 | const udmaio::UioDeviceLocation fmc2zup_ident_0 { 62 | "hier_interconnect_fmc2zup_ident_0", 63 | { 0x0000'0000, 256 * 1024 }, 64 | }; 65 | 66 | constexpr uintptr_t pcie_offset = 0xa000'0000UL; 67 | 68 | constexpr uintptr_t fpga_mem_phys_addr = 0x5'0000'0000UL; 69 | constexpr uint16_t lfsr_bytes_per_beat = 16; 70 | 71 | } // namespace target_hw_consts -------------------------------------------------------------------------------- /example/demo_cpp/inc/Z7ioExampleProjectConsts.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * ____ _____________ __ __ __ _ _____ ___ _ * 3 | * / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ (R) * 4 | * / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ * 5 | * / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ * 6 | * /_____/_____//____/ /_/ T E C H N O L O G Y L A B * 7 | * * 8 | ***************************************************************************/ 9 | 10 | /* 11 | * Auto-generated by project-consts 12 | * https://msktechvcs.desy.de/techlab/software/internal/project-consts 13 | * 14 | * This file was generated with: 15 | * project-consts.py damc_fmc1z7io_top.xsa -t 0x44000000 -a arm_m_axi -e axi_dma_0:events0 -o Z7ioExampleProjectConsts.hpp 16 | * 17 | */ 18 | 19 | #include "udmaio/UioConfig.hpp" 20 | 21 | // clang-format off 22 | 23 | namespace target_hw_consts { 24 | 25 | const udmaio::UioDeviceLocation axi_bram_ctrl { 26 | "hier_daq_axi_bram_ctrl_0", 27 | { 0x0012'0000, 8 * 1024 }, 28 | }; 29 | 30 | const udmaio::UioDeviceLocation axi_dma { 31 | "hier_daq_axi_dma_0", 32 | { 0x0022'2000, 4 * 1024 }, 33 | "events0", 34 | }; 35 | 36 | const udmaio::UioDeviceLocation axi_gpio_0 { 37 | "hier_io_axi_gpio_0", 38 | { 0x0040'0000, 4 * 1024 }, 39 | }; 40 | 41 | const udmaio::UioDeviceLocation axi_gpio_status { 42 | "hier_interconnect_axi_gpio_status", 43 | { 0x0005'0000, 4 * 1024 }, 44 | }; 45 | 46 | const udmaio::UioDeviceLocation axi_traffic_gen { 47 | "hier_daq_axi_traffic_gen_0", 48 | { 0x0021'0000, 64 * 1024 }, 49 | }; 50 | 51 | const udmaio::UioDeviceLocation clk_monitor_0 { 52 | "clk_monitor_clk_monitor_0", 53 | { 0x0006'0000, 4 * 1024 }, 54 | }; 55 | 56 | const udmaio::UioDeviceLocation custom_irq_gen_apu { 57 | "hier_irq_gen_custom_irq_gen_apu", 58 | { 0x0030'0000, 4 * 1024 }, 59 | }; 60 | 61 | const udmaio::UioDeviceLocation custom_irq_gen_pcie { 62 | "hier_irq_gen_custom_irq_gen_pcie", 63 | { 0x0031'0000, 4 * 1024 }, 64 | }; 65 | 66 | const udmaio::UioDeviceLocation fmc1z7io_ident { 67 | "hier_interconnect_fmc1z7io_ident", 68 | { 0x0000'0000, 256 * 1024 }, 69 | }; 70 | 71 | constexpr uintptr_t pcie_offset = 0x4400'0000UL; 72 | 73 | constexpr uint16_t lfsr_bytes_per_beat = 8; 74 | constexpr uintptr_t fpga_mem_phys_addr = 0x0'3c00'0000UL; 75 | 76 | 77 | } // namespace target_hw_consts -------------------------------------------------------------------------------- /pyudmaio/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 2 | 3 | from setuptools import setup, find_packages 4 | 5 | # Available at setup time due to pyproject.toml 6 | from pybind11.setup_helpers import Pybind11Extension, build_ext 7 | from pybind11 import get_cmake_dir 8 | 9 | import os 10 | import re 11 | import glob 12 | 13 | # Use same version for python package as for the whole library. 14 | # Assumes CMakeLists.txt is one dir above this script and contains "project(libudmaio VERSION x.y.z)" 15 | def get_version_from_cmakelists(): 16 | re_ver = re.compile(r'^project\(libudmaio\s+VERSION\s+(\d+\.\d+\.\d+)\)') 17 | script_path = os.path.dirname(os.path.realpath(__file__)) 18 | print(os.getcwd(), glob.glob("*")) 19 | with open(os.path.join(script_path, '..', 'CMakeLists.txt'), 'r') as f: 20 | for l in f: 21 | m = re_ver.match(l) 22 | if m: 23 | return m.group(1) 24 | 25 | raise RuntimeError('Version string not found in CMakeLists.txt!') 26 | 27 | __version__ = get_version_from_cmakelists() 28 | 29 | # The main interface is through Pybind11Extension. 30 | # * You can add cxx_std=11/14/17, and then build_ext can be removed. 31 | # * You can set include_pybind11=false to add the include directory yourself, 32 | # say from a submodule. 33 | # 34 | # Note: 35 | # Sort input source files if you glob sources to ensure bit-for-bit 36 | # reproducible builds (https://github.com/pybind/python_example/pull/53) 37 | 38 | ext_modules = [ 39 | Pybind11Extension( 40 | 'pyudmaio.binding', 41 | sources = [ 42 | 'src/DataHandlerPython.cpp', 43 | 'src/PythonBinding.cpp' 44 | ], 45 | # Example: passing in the version to the compiled code 46 | define_macros = [ 47 | ('VERSION_INFO', __version__), 48 | ('BOOST_ALL_DYN_LINK', None), 49 | ], 50 | libraries = [ 51 | 'udmaio' 52 | ], 53 | ), 54 | ] 55 | 56 | setup( 57 | name="pyudmaio", 58 | version=__version__, 59 | author="Patrick Huesmann, Jan Marjanovic", 60 | author_email="patrick.huesmann@desy.de", 61 | url="https://techlab.desy.de/", 62 | description="Python binding for libudmaio", 63 | long_description="", 64 | ext_modules=ext_modules, 65 | extras_require={ 66 | # "test": "pytest" 67 | }, 68 | # Currently, build_ext only provides an optional "highest supported C++ 69 | # level" feature, but in the future it may provide more features. 70 | cmdclass={ 71 | "build_ext": build_ext 72 | }, 73 | packages=find_packages(exclude=['tests']), 74 | package_data={ 75 | package: ["**/*.pyi"] for package in find_packages(exclude=['tests']) 76 | }, 77 | zip_safe=False, 78 | ) 79 | -------------------------------------------------------------------------------- /inc/udmaio/DataHandlerAbstract.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include "Logging.hpp" 21 | #include "UDmaBuf.hpp" 22 | #include "UioAxiDmaIf.hpp" 23 | #include "UioMemSgdma.hpp" 24 | #include 25 | 26 | namespace udmaio { 27 | 28 | /// Base class to implement a DMA data reception handler 29 | class DataHandlerAbstract : public Logger, private boost::noncopyable { 30 | UioAxiDmaIf& _dma; ///< AXI DMA interface 31 | UioMemSgdma& _desc; ///< SGDMA data interface 32 | DmaBufferAbstract& _mem; ///< SGDMA buffer holding the data received from AXI DMA 33 | 34 | boost::asio::io_service _svc; 35 | boost::asio::posix::stream_descriptor _sd; 36 | const bool _rt_prio; 37 | 38 | void _start_read(); 39 | void _handle_input(const boost::system::error_code& ec); 40 | 41 | protected: 42 | const bool _receive_packets; ///< Enable segmentation of stream into SOF/EOF delimited frames 43 | 44 | public: 45 | /// @brief Construct a Data Handler 46 | /// @param dma Interface to the AXI DMA core 47 | /// @param desc Interface to the SGDMA descriptors 48 | /// @param mem Interface to the memory holding the SGDMA data buffers 49 | /// @param receive_packets Receive packets/frames delimited by SOF/EOF. If not set, receive stream as-is without regard for packets 50 | /// @param rt_prio Set receiving thread to use RT scheduling priority 51 | explicit DataHandlerAbstract(std::string name, 52 | UioAxiDmaIf& dma, 53 | UioMemSgdma& desc, 54 | DmaBufferAbstract& mem, 55 | bool receive_packets = true, 56 | bool rt_prio = false); 57 | virtual ~DataHandlerAbstract(); 58 | 59 | /// @brief Stop the data reception 60 | virtual void stop(); 61 | 62 | /// @brief Run the data reception 63 | void operator()(); 64 | 65 | /// @brief Process the received data 66 | /// @param bytes Data block 67 | virtual void process_data(std::vector bytes) = 0; 68 | }; 69 | 70 | } // namespace udmaio 71 | -------------------------------------------------------------------------------- /inc/udmaio/ConcurrentQueue.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace udmaio { 22 | 23 | /// Helper class to implement a blocking FIFO between threads 24 | template 25 | class ConcurrentQueue { 26 | public: 27 | /// @brief Pop an element from the queue, block if none available 28 | /// @param timeout timeout in ms, default 0 = no timeout, return empty T if elapsed 29 | /// @return Element popped from the queue 30 | T pop(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { 31 | std::unique_lock lock{_mutex}; 32 | while (_queue.empty()) { 33 | if (_abort) { 34 | return {}; 35 | } 36 | if (timeout.count()) { 37 | if (_cond.wait_for(lock, timeout) == std::cv_status::timeout) { 38 | return {}; 39 | } 40 | } else { 41 | _cond.wait(lock); 42 | } 43 | } 44 | auto val = std::move(_queue.front()); 45 | _queue.pop(); 46 | lock.unlock(); 47 | _cond.notify_one(); 48 | return val; 49 | } 50 | 51 | /// @brief Push an element to the queue 52 | /// @param item Element to push to the queue 53 | void push(T item) { 54 | std::unique_lock lock(_mutex); 55 | if (_queue.size() >= _max_elems) { 56 | lock.unlock(); 57 | throw std::runtime_error("libudmaio: ConcurrentQueue full"); 58 | } 59 | _queue.push(std::move(item)); 60 | lock.unlock(); 61 | _cond.notify_one(); 62 | } 63 | 64 | /// @brief Abort any blocking consumers 65 | void abort() { 66 | _abort = true; 67 | _cond.notify_one(); 68 | } 69 | 70 | ConcurrentQueue(size_t max_elems = 64) : _max_elems{max_elems} {} 71 | ConcurrentQueue(const ConcurrentQueue&) = delete; 72 | ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; 73 | 74 | private: 75 | std::queue _queue; 76 | std::mutex _mutex; 77 | std::condition_variable _cond; 78 | size_t _max_elems; 79 | bool _abort = false; 80 | }; 81 | 82 | } // namespace udmaio 83 | -------------------------------------------------------------------------------- /pyudmaio/pyudmaio/_UioReg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 4 | 5 | import bitstruct 6 | 7 | 8 | class UioReg(object): 9 | def __init__(self, intf, offs, fields=None): 10 | self.dict = {} 11 | self.intf = intf 12 | self.offs = offs 13 | if fields is not None: 14 | bitfield = ''.join(list(zip(*fields))[1]) 15 | if bitstruct.calcsize(bitfield) != 32: 16 | raise RuntimeError('UioReg: Bitfield must be 32 bits in size') 17 | names = list(zip(*fields))[0] 18 | self.bs = bitstruct.compile(bitfield, names=names) 19 | self.val = 0 20 | else: 21 | self.bs = None 22 | 23 | def rd(self): 24 | val = self.intf._rd32(self.offs) 25 | if self.bs is not None: 26 | self.val = val 27 | return val 28 | 29 | def wr(self, val = None): 30 | if val is not None: 31 | self.intf._wr32(self.offs, val) 32 | else: 33 | self.intf._wr32(self.offs, self.val) 34 | 35 | def __getattr__(self, name): 36 | if name != 'dict' and name in self.dict: 37 | return self.dict[name] 38 | elif name == 'val': 39 | return int.from_bytes(self.raw_val, 'big') 40 | else: 41 | return object.__getattribute__(self, name) 42 | 43 | def __setattr__(self, name, value): 44 | if name != 'dict' and name in self.dict: 45 | self.dict[name] = value 46 | self.raw_val = self.bs.pack(self.dict) 47 | elif name == 'val': 48 | self.raw_val = value.to_bytes(4, 'big') 49 | self.dict = self.bs.unpack(self.raw_val) 50 | elif name in ['raw_val', 'dict', 'intf', 'offs', 'bs']: 51 | super().__setattr__(name, value) 52 | else: 53 | raise AttributeError(f'{name}: unknown attribute') 54 | 55 | 56 | class UioRegTest: 57 | def __init__(self): 58 | self.test_reg = UioReg(self, 0x1000, ( 59 | ('MSBh', 'u4'), 60 | ('MSBl', 'u4'), 61 | ('B2_7', 'u7'), 62 | ('B2_0', 'u1'), 63 | ('B1', 's8'), 64 | ('LSB', 'u8'), 65 | )) 66 | 67 | def _rd32(self, offs): 68 | print(f'Reading at {offs:08x}') 69 | return 0 70 | 71 | def _wr32(self, offs, val): 72 | print(f'Writing {val:08x} to {offs:08x}') 73 | 74 | def test(self): 75 | self.test_reg.rd() 76 | print(self.test_reg.raw_val) 77 | print(self.test_reg.val) 78 | print(self.test_reg.dict) 79 | self.test_reg.MSBh = 0xf 80 | self.test_reg.MSBl = 0x5 81 | self.test_reg.B2_7 = 0 82 | self.test_reg.B2_0 = 1 83 | self.test_reg.B1 = -2 84 | self.test_reg.LSB = 0xaa 85 | print(self.test_reg.raw_val) 86 | print(self.test_reg.val) 87 | print(self.test_reg.dict) 88 | self.test_reg.wr() 89 | 90 | if __name__ == '__main__': 91 | regtest = UioRegTest() 92 | regtest.test() 93 | -------------------------------------------------------------------------------- /src/UDmaBuf.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/UDmaBuf.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace udmaio { 25 | 26 | UDmaBuf::UDmaBuf(int buf_idx) 27 | : Logger("UDmaBuf"), _phys{_get_phys_addr(buf_idx), _get_size(buf_idx)} { 28 | BOOST_LOG_SEV(_lg, bls::debug) << "size = " << _phys.size; 29 | BOOST_LOG_SEV(_lg, bls::debug) << "phys addr = " << std::hex << _phys.addr << std::dec; 30 | 31 | std::string dev_path{"/dev/udmabuf" + std::to_string(buf_idx)}; 32 | _fd = ::open(dev_path.c_str(), O_RDWR | O_SYNC); 33 | if (_fd < 0) { 34 | throw std::runtime_error("could not open " + dev_path); 35 | } 36 | BOOST_LOG_SEV(_lg, bls::trace) << "fd = " << _fd << ", size = " << _phys.size; 37 | _mem = mmap(NULL, _phys.size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0); 38 | BOOST_LOG_SEV(_lg, bls::trace) << "mmap = " << _mem; 39 | if (_mem == MAP_FAILED) { 40 | throw std::runtime_error("mmap failed for " + dev_path); 41 | } 42 | } 43 | 44 | size_t UDmaBuf::_get_size(int buf_idx) { 45 | std::string path{"/sys/class/u-dma-buf/udmabuf" + std::to_string(buf_idx) + "/size"}; 46 | std::ifstream ifs{path}; 47 | if (!ifs) { 48 | throw std::runtime_error("could not find size for udmabuf " + std::to_string(buf_idx)); 49 | } 50 | std::string size_str; 51 | ifs >> size_str; 52 | return std::stoull(size_str, nullptr, 0); 53 | } 54 | 55 | uintptr_t UDmaBuf::_get_phys_addr(int buf_idx) { 56 | std::string path{"/sys/class/u-dma-buf/udmabuf" + std::to_string(buf_idx) + "/phys_addr"}; 57 | std::ifstream ifs{path}; 58 | if (!ifs) { 59 | throw std::runtime_error("could not find phys_addr for udmabuf " + std::to_string(buf_idx)); 60 | } 61 | std::string phys_addr_str; 62 | ifs >> phys_addr_str; 63 | return std::stoull(phys_addr_str, nullptr, 0); 64 | } 65 | 66 | UDmaBuf::~UDmaBuf() { 67 | munmap(_mem, _phys.size); 68 | ::close(_fd); 69 | } 70 | 71 | UioRegion UDmaBuf::get_phys_region() const { 72 | return _phys; 73 | } 74 | 75 | void UDmaBuf::copy_from_buf(uint8_t* dest, const UioRegion& buf_info) const { 76 | uintptr_t mmap_addr = buf_info.addr - _phys.addr; 77 | std::memcpy(dest, static_cast(_mem) + mmap_addr, buf_info.size); 78 | } 79 | 80 | } // namespace udmaio 81 | -------------------------------------------------------------------------------- /docs/doxygen-header.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | $projectname: $title 13 | 14 | $title 15 | 16 | 17 | 18 | $treeview 19 | $search 20 | $mathjax 21 | 22 | $extrastylesheet 23 | 24 | 25 | 28 | 29 | 30 | 31 |
32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
43 |
$projectname 44 |  $projectnumber 46 |
47 | 48 |
$projectbrief
49 |
54 |
$projectbrief
55 |
$searchbox
66 |
67 | 68 | -------------------------------------------------------------------------------- /example/demo_cpp/src/DataHandlerPrint.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "DataHandlerPrint.hpp" 13 | 14 | #include 15 | #include 16 | 17 | DataHandlerPrint::DataHandlerPrint(UioAxiDmaIf& dma, 18 | UioMemSgdma& desc, 19 | DmaBufferAbstract& mem, 20 | unsigned int num_bytes_per_beat, 21 | uint64_t num_bytes_expected, 22 | bool rt_prio) 23 | : DataHandlerAbstract{"DataHandlerPrint", dma, desc, mem, true, rt_prio} 24 | , lfsr{std::nullopt} 25 | , _counter_ok{0} 26 | , _counter_total{0} 27 | , _num_bytes_per_beat{num_bytes_per_beat} 28 | , _num_bytes_expected{num_bytes_expected} 29 | , _num_bytes_rcvd{0} {} 30 | 31 | void DataHandlerPrint::process_data(std::vector bytes) { 32 | BOOST_LOG_SEV(_lg, bls::debug) << "process data, size = " << bytes.size(); 33 | _num_bytes_rcvd += bytes.size(); 34 | 35 | if (bytes.size() == 0) { 36 | BOOST_LOG_SEV(_lg, bls::trace) << "nothing to do, exiting"; 37 | return; 38 | } 39 | 40 | const uint16_t* vals = reinterpret_cast(&bytes[0]); 41 | 42 | if (!lfsr) { 43 | uint16_t seed = vals[0]; 44 | lfsr = AxiTrafficGenLfsr{seed}; 45 | } 46 | 47 | size_t idx = 0; 48 | for (unsigned int i = 0; i < bytes.size() / _num_bytes_per_beat; i++) { 49 | uint16_t exp_val = lfsr->get(); 50 | for (unsigned int j = 0; j < (_num_bytes_per_beat / sizeof(uint16_t)); j++) { 51 | uint16_t recv_val = vals[idx]; 52 | _counter_total++; 53 | if (exp_val != recv_val) { 54 | BOOST_LOG_SEV(_lg, bls::fatal) << "mismatch, at " << idx << " recv = " << std::hex 55 | << recv_val << ", exp = " << exp_val; 56 | stop(); 57 | return; 58 | } 59 | _counter_ok++; 60 | idx++; 61 | } 62 | lfsr->advance(); 63 | } 64 | 65 | if (_num_bytes_expected != 0 && _num_bytes_rcvd >= _num_bytes_expected) { 66 | // We're done. 67 | if (_num_bytes_rcvd == _num_bytes_expected) { 68 | BOOST_LOG_SEV(_lg, bls::debug) << "Received all packets"; 69 | } else { 70 | BOOST_LOG_SEV(_lg, bls::error) << "Received more packets than expected"; 71 | } 72 | stop(); 73 | } 74 | } 75 | 76 | std::pair DataHandlerPrint::operator()() { 77 | DataHandlerAbstract::operator()(); 78 | return std::make_pair(_counter_ok, _counter_total); 79 | } 80 | -------------------------------------------------------------------------------- /inc/udmaio/UioIf.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "udmaio/HwAccessor.hpp" 28 | #include "udmaio/Logging.hpp" 29 | #include "udmaio/UioConfig.hpp" 30 | 31 | namespace udmaio { 32 | 33 | /// Base class for UIO interfaces 34 | class UioIf : private boost::noncopyable { 35 | template 36 | friend class RegAccessorBase; 37 | 38 | public: 39 | UioIf(std::string name, UioDeviceLocation dev_loc); 40 | 41 | virtual ~UioIf(); 42 | 43 | /// @brief Get file descriptor of interrupt event file 44 | /// @return Event file descriptor 45 | int get_fd_int() const; 46 | 47 | void enable_debug(bool enable); 48 | 49 | protected: 50 | HwAccessorPtr _hw; 51 | boost_logger& _lg; 52 | 53 | template > 54 | static uint32_t reg_to_raw(C data) { 55 | union { 56 | uint32_t raw_val; 57 | C cooked_val; 58 | } u; 59 | u.cooked_val = data; 60 | return u.raw_val; 61 | } 62 | 63 | template > 64 | static uint64_t reg_to_raw(C data) { 65 | union { 66 | uint64_t raw_val; 67 | C cooked_val; 68 | } u; 69 | u.cooked_val = data; 70 | return u.raw_val; 71 | } 72 | 73 | inline uint32_t _rd32(uint32_t offs) const { return _hw->_rd32(offs); } 74 | inline uint64_t _rd64(uint32_t offs) const { return _hw->_rd64(offs); } 75 | inline void _wr32(uint32_t offs, uint32_t data) { _hw->_wr32(offs, data); } 76 | inline void _wr64(uint32_t offs, uint64_t data) { _hw->_wr64(offs, data); } 77 | 78 | std::vector read_bulk(uint32_t offs, uint32_t size) { 79 | return _hw->read_bulk(offs, size); 80 | } 81 | 82 | void write_bulk(uint32_t offs, std::vector data) { 83 | return _hw->write_bulk(offs, data); 84 | } 85 | 86 | template 87 | C _rd_reg(uint32_t offs) const { 88 | return _hw->template _rd_reg(offs); 89 | } 90 | 91 | template 92 | void _wr_reg(uint32_t offs, const C& value) { 93 | _hw->template _wr_reg(offs, value); 94 | } 95 | 96 | void arm_interrupt(); 97 | uint32_t wait_for_interrupt(); 98 | }; 99 | 100 | } // namespace udmaio 101 | -------------------------------------------------------------------------------- /src/HwAccessor.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2023 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/HwAccessor.hpp" 13 | 14 | #include 15 | 16 | namespace udmaio { 17 | 18 | HwAccessor::HwAccessor() : Logger{"HwAccessor"}, _debug_enable(false) {} 19 | 20 | HwAccessor::~HwAccessor() {} 21 | 22 | void HwAccessor::enable_debug(bool enable) { 23 | _debug_enable = enable; 24 | } 25 | 26 | uint64_t HwAccessor::_rd64(uint32_t offs) const { 27 | uint64_t result; 28 | uint32_t* const tmp = reinterpret_cast(&result); 29 | tmp[0] = _rd32(offs); 30 | tmp[1] = _rd32(offs + sizeof(uint32_t)); 31 | return result; 32 | } 33 | 34 | // Default implementation uses 32 bit accesses 35 | // Can be overridden by subclass if it supports native 64 bit access 36 | void HwAccessor::_wr64(uint32_t offs, uint64_t data) { 37 | uint32_t* const tmp = reinterpret_cast(&data); 38 | _wr32(offs, tmp[0]); 39 | _wr32(offs + sizeof(uint32_t), tmp[1]); 40 | } 41 | 42 | void HwAccessor::_rd_mem32(uint32_t offs, void* __restrict__ mem, size_t size) const { 43 | uint32_t* __restrict__ ptr = reinterpret_cast(mem); 44 | for (uint32_t i = 0; i < size; i += sizeof(uint32_t)) { 45 | *ptr++ = _rd32(offs + i); 46 | } 47 | } 48 | 49 | void HwAccessor::_rd_mem64(uint32_t offs, void* __restrict__ mem, size_t size) const { 50 | uint64_t* __restrict__ ptr = reinterpret_cast(mem); 51 | for (uint32_t i = 0; i < size; i += sizeof(uint64_t)) { 52 | *ptr++ = _rd64(offs + i); 53 | } 54 | } 55 | 56 | void HwAccessor::_wr_mem32(uint32_t offs, const void* __restrict__ mem, size_t size) { 57 | // volatile needed to prevent the compiler from optimizing away the memory read 58 | const volatile uint32_t* ptr = reinterpret_cast(mem); 59 | for (uint32_t i = 0; i < size; i += sizeof(uint32_t)) { 60 | _wr32(offs + i, *ptr++); 61 | } 62 | } 63 | 64 | void HwAccessor::_wr_mem64(uint32_t offs, const void* __restrict__ mem, size_t size) { 65 | // volatile needed to prevent the compiler from optimizing away the memory read 66 | const volatile uint64_t* ptr = reinterpret_cast(mem); 67 | for (uint32_t i = 0; i < size; i += sizeof(uint64_t)) { 68 | _wr64(offs + i, *ptr++); 69 | } 70 | } 71 | 72 | void HwAccessor::arm_interrupt() {} 73 | 74 | uint32_t HwAccessor::wait_for_interrupt() { 75 | return 0; 76 | } 77 | 78 | HwAccessorAxi::HwAccessorAxi(std::string dev_path, 79 | UioRegion region, 80 | uintptr_t mmap_offs, 81 | size_t access_offs) 82 | : HwAccessorMmap{dev_path, region, mmap_offs, access_offs} {} 83 | 84 | HwAccessorAxi::~HwAccessorAxi() {} 85 | 86 | } // namespace udmaio 87 | -------------------------------------------------------------------------------- /inc/udmaio/UioAxiVdmaIf.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2023 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include "udmaio/FrameFormat.hpp" 18 | #include "udmaio/rdl/AxiVdma.hpp" 19 | 20 | namespace udmaio { 21 | 22 | using AxiVdmaBlock = axi_vdma::block; 23 | 24 | /// Interface to AXI VDMA Core 25 | class UioAxiVdmaIf : public UioIf, AxiVdmaBlock { 26 | /// @brief true if VDMA core configured for 64-bit address space 27 | const bool _long_addrs; 28 | 29 | public: 30 | UioAxiVdmaIf(UioDeviceLocation dev_loc, bool long_addrs = false) 31 | : UioIf("UioAxiVdmaIf", dev_loc), AxiVdmaBlock(this), _long_addrs{long_addrs} { 32 | enable_debug(true); 33 | } 34 | 35 | //////////////////////////////////////// 36 | // Helper functions 37 | //////////////////////////////////////// 38 | 39 | /// @brief Configure the AXI VDMA controller 40 | /// @param frm_bufs Vector of memory regions used as frame buffers 41 | void init_buffers(const std::vector& frm_bufs); 42 | 43 | /// @brief Configure the AXI VDMA controller 44 | /// @param frm_format Frame format to be set 45 | void set_frame_format(FrameFormat frm_format); 46 | 47 | /// @brief Autodetect max. number of framebuffers 48 | size_t autodetect_num_frmbufs(); 49 | 50 | //////////////////////////////////////// 51 | // Base config 52 | //////////////////////////////////////// 53 | 54 | /// @brief Configure the AXI VDMA controller 55 | /// @param frm_bufs Vector of memory regions used as frame buffers 56 | void config(const std::vector& frm_bufs); 57 | 58 | /// @brief Configure the AXI VDMA controller 59 | /// @param frm_bufs Vector of memory regions used as frame buffers 60 | /// @param frm_format Frame format to be set 61 | void config(const std::vector& frm_bufs, FrameFormat frm_format); 62 | 63 | /// @brief Reset the S2MM part of the AXI VDMA controller 64 | void reset(); 65 | 66 | /// @brief Read S2MM registers of the AXI VDMA controller and print their content 67 | void print_regs(); 68 | 69 | uint32_t get_cur_frmbuf(); 70 | 71 | void print_cur_frmbuf(); 72 | 73 | //////////////////////////////////////// 74 | // Operational functions 75 | //////////////////////////////////////// 76 | 77 | /// @brief Start the already configured AXI VDMA controller (again) 78 | void start(); 79 | 80 | /// @brief Configure frame size and start the AXI VDMA controller 81 | /// @param frm_format Frame format to be set 82 | void start(FrameFormat frm_format); 83 | 84 | /// @brief Stops the AXI VDMA controller 85 | void stop(bool wait_for_end); 86 | 87 | /// @brief Check halted bit in status register 88 | /// @return true if operation is not stopped 89 | bool isrunning(); 90 | 91 | /// @brief Check status register and log any errors 92 | /// @return true if any error occurred 93 | bool check_for_errors(); 94 | }; 95 | 96 | } // namespace udmaio 97 | -------------------------------------------------------------------------------- /pyudmaio/src/DataHandlerPython.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "DataHandlerPython.hpp" 13 | 14 | namespace udmaio { 15 | 16 | DataHandlerPython::DataHandlerPython(std::shared_ptr dma_ptr, 17 | std::shared_ptr desc_ptr, 18 | std::shared_ptr mem_ptr, 19 | bool receive_packets, 20 | size_t queue_size, 21 | bool rt_prio) 22 | : DataHandlerSync("DataHandlerPython", 23 | *dma_ptr, 24 | *desc_ptr, 25 | *mem_ptr, 26 | receive_packets, 27 | queue_size, 28 | rt_prio) 29 | , _dma_ptr(dma_ptr) 30 | , _desc_ptr(desc_ptr) 31 | , _mem_ptr(mem_ptr) {} 32 | 33 | void DataHandlerPython::start(int nr_pkts, size_t pkt_size, bool init_only) { 34 | _desc_ptr->init_buffers(_mem_ptr, nr_pkts, pkt_size); 35 | 36 | uintptr_t first_desc = _desc_ptr->get_first_desc_addr(); 37 | _dma_ptr->start(first_desc); 38 | 39 | if (!init_only) { 40 | this->operator()(); 41 | } 42 | } 43 | 44 | py::array_t DataHandlerPython::numpy_read(uint32_t ms_timeout) { 45 | if (_dma_ptr->check_for_errors()) { 46 | throw std::runtime_error("DMA has experienced an error"); 47 | } 48 | 49 | // Create vector on the heap, holding the data 50 | auto vec = new std::vector(read(std::chrono::milliseconds{ms_timeout})); 51 | // Callback for Python garbage collector 52 | py::capsule gc_callback(vec, [](void* f) { 53 | auto ptr = reinterpret_cast*>(f); 54 | delete ptr; 55 | }); 56 | // Return Numpy array, transferring ownership to Python 57 | return py::array_t({vec->size() / sizeof(uint8_t)}, // shape 58 | {sizeof(uint8_t)}, // stride 59 | reinterpret_cast(vec->data()), // data pointer 60 | gc_callback); 61 | } 62 | 63 | py::array_t DataHandlerPython::numpy_read_nb() { 64 | if (_dma_ptr->check_for_errors()) { 65 | throw std::runtime_error("DMA has experienced an error"); 66 | } 67 | 68 | auto full_bufs = 69 | _receive_packets ? _desc_ptr->get_next_packet() : _desc_ptr->get_full_buffers(); 70 | auto vec = new std::vector(_desc_ptr->read_buffers(full_bufs)); 71 | 72 | // Callback for Python garbage collector 73 | py::capsule gc_callback(vec, [](void* f) { 74 | auto ptr = reinterpret_cast*>(f); 75 | delete ptr; 76 | }); 77 | // Return Numpy array, transferring ownership to Python 78 | return py::array_t({vec->size() / sizeof(uint8_t)}, // shape 79 | {sizeof(uint8_t)}, // stride 80 | reinterpret_cast(vec->data()), // data pointer 81 | gc_callback); 82 | } 83 | 84 | } // namespace udmaio 85 | -------------------------------------------------------------------------------- /inc/udmaio/UioConfig.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace udmaio { 21 | 22 | class HwAccessor; 23 | using HwAccessorPtr = std::shared_ptr; 24 | 25 | /// DMA access mode 26 | enum class DmaMode { 27 | XDMA, ///< PCIe XDMA driver 28 | UIO ///< ARM userspace I/O 29 | }; 30 | 31 | std::istream& operator>>(std::istream& in, DmaMode& mode); 32 | 33 | /// General-purpose struct to define a memory area 34 | struct UioRegion { 35 | uintptr_t addr; ///< Start of region 36 | size_t size; ///< Size of region 37 | }; 38 | 39 | class UioConfigBase; 40 | 41 | /// Holds information where a device can be found over both UIO and XDMA 42 | class UioDeviceLocation { 43 | friend class UioConfigXdma; 44 | 45 | static std::unique_ptr _link_cfg; 46 | 47 | /// Override the regular hardware accessor (for testing purposes) 48 | HwAccessorPtr _hw_acc_override; 49 | 50 | public: 51 | UioDeviceLocation(std::string uioname, UioRegion xdmaregion, std::string xdmaevtdev = "") 52 | : uio_name(uioname), xdma_region(xdmaregion), xdma_evt_dev(xdmaevtdev) {} 53 | UioDeviceLocation(HwAccessorPtr hw_acc_override) : _hw_acc_override{hw_acc_override} {} 54 | 55 | /// Device name (from device tree) for access through UIO 56 | std::string uio_name; 57 | /// Memory-mapped region for access through XDMA 58 | UioRegion xdma_region; 59 | /// optional: Event file for access through XDMA 60 | std::string xdma_evt_dev; 61 | 62 | /// @brief Set UioIf's globally to use a AXI/UIO link 63 | static void set_link_axi(); 64 | 65 | /// @brief Set UioIf's globally to use a XDMA link 66 | /// @param xdma_path XDMA device instance directory in `/dev` 67 | /// @param pcie_offs XDMA core PCIe memory offset 68 | /// @param x7_series_mode Set the interface to Xilinx 7 series mode. PCIe connections to that device will be limited to 32 bits. 69 | static void set_link_xdma(std::string xdma_path, uintptr_t pcie_offs, bool x7_series_mode); 70 | 71 | HwAccessorPtr hw_acc() const; 72 | }; 73 | 74 | /// Base class for HwAccessor creation 75 | class UioConfigBase { 76 | public: 77 | /// @brief Mode of physical connection to the UioIf object 78 | /// @return DmaMode enum 79 | virtual DmaMode mode() = 0; 80 | 81 | virtual HwAccessorPtr hw_acc(const UioDeviceLocation& dev_loc) const = 0; 82 | }; 83 | 84 | /// Creates HwAccessor from UioDeviceLocation (UIO version) 85 | class UioConfigUio : public UioConfigBase { 86 | static int _get_uio_number(std::string_view name); 87 | static unsigned long long _get_uio_val(const std::string path); 88 | static UioRegion _get_map_region(int uio_number, int map_index); 89 | static size_t _get_map_offset(int uio_number, int map_index); 90 | 91 | public: 92 | DmaMode mode() override { return DmaMode::UIO; }; 93 | 94 | HwAccessorPtr hw_acc(const UioDeviceLocation& dev_loc) const override; 95 | }; 96 | 97 | /// Creates HwAccessor from UioDeviceLocation (XDMA version) 98 | class UioConfigXdma : public UioConfigBase { 99 | std::string _xdma_path; 100 | uintptr_t _pcie_offs; 101 | bool _x7_series_mode; 102 | 103 | public: 104 | UioConfigXdma() = delete; 105 | 106 | /// @brief Create UioConfigXdma 107 | /// @param xdma_path XDMA device path `/dev/...` 108 | /// @param pcie_offs PCIe offset in memory 109 | UioConfigXdma(std::string xdma_path, uintptr_t pcie_offs, bool x7_series_mode = false); 110 | 111 | DmaMode mode() override { return DmaMode::XDMA; }; 112 | 113 | HwAccessorPtr hw_acc(const UioDeviceLocation& dev_loc) const override; 114 | }; 115 | 116 | } // namespace udmaio 117 | -------------------------------------------------------------------------------- /src/DataHandlerAbstract.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/DataHandlerAbstract.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace udmaio { 21 | 22 | DataHandlerAbstract::DataHandlerAbstract(std::string name, 23 | UioAxiDmaIf& dma, 24 | UioMemSgdma& desc, 25 | DmaBufferAbstract& mem, 26 | bool receive_packets, 27 | bool rt_prio) 28 | : Logger(name) 29 | , _dma{dma} 30 | , _desc{desc} 31 | , _mem{mem} 32 | , _svc{} 33 | , _sd{_svc, _dma.get_fd_int()} 34 | , _rt_prio{rt_prio} 35 | , _receive_packets{receive_packets} { 36 | BOOST_LOG_SEV(_lg, bls::trace) << "ctor"; 37 | }; 38 | 39 | DataHandlerAbstract::~DataHandlerAbstract() { 40 | BOOST_LOG_SEV(_lg, bls::trace) << "dtor"; 41 | } 42 | 43 | void DataHandlerAbstract::stop() { 44 | BOOST_LOG_SEV(_lg, bls::trace) << "stop"; 45 | _svc.stop(); 46 | } 47 | 48 | void DataHandlerAbstract::_start_read() { 49 | _dma.arm_interrupt(); 50 | 51 | _sd.async_read_some( 52 | boost::asio::null_buffers(), // No actual reading - that's deferred to UioAxiDmaIf 53 | std::bind(&DataHandlerAbstract::_handle_input, 54 | this, 55 | std::placeholders::_1 // boost::asio::placeholders::error 56 | )); 57 | } 58 | 59 | void DataHandlerAbstract::_handle_input(const boost::system::error_code& ec) { 60 | if (ec) { 61 | BOOST_LOG_SEV(_lg, bls::error) << "I/O error: " << ec.message(); 62 | return; 63 | } 64 | 65 | auto [irq_count, dma_stat] = _dma.clear_interrupt(); 66 | BOOST_LOG_SEV(_lg, bls::trace) << "irq count = " << irq_count; 67 | if (dma_stat.err_irq && _dma.check_for_errors()) { 68 | BOOST_LOG_SEV(_lg, bls::fatal) 69 | << "DMA error, curr.desc 0x" << std::hex << _dma.get_curr_desc(); 70 | _dma.dump_status(); 71 | _desc.print_descs(); 72 | throw std::runtime_error("DMA engine error raised"); 73 | } 74 | 75 | if (dma_stat.ioc_irq) { 76 | bool spurious_event = true; 77 | // Receive data until there's no more incoming packets or full buffers 78 | // (esp. important in packet mode where get_next_packet() will only 79 | // return *one* packet, regardless of more incoming data in the pipeline) 80 | while (true) { 81 | auto full_bufs = _receive_packets ? _desc.get_next_packet() : _desc.get_full_buffers(); 82 | if (full_bufs.empty()) { 83 | if (spurious_event) { 84 | BOOST_LOG_SEV(_lg, bls::trace) << "spurious event, got no data"; 85 | } 86 | break; 87 | } else { 88 | auto bytes = _desc.read_buffers(full_bufs); 89 | process_data(std::move(bytes)); 90 | spurious_event = false; 91 | } 92 | } 93 | } 94 | 95 | _start_read(); 96 | } 97 | 98 | void DataHandlerAbstract::operator()() { 99 | BOOST_LOG_SEV(_lg, bls::trace) << "started"; 100 | 101 | if (_rt_prio) { 102 | // Set real-time scheduling 103 | sched_param sch_params = {.sched_priority = sched_get_priority_max(SCHED_FIFO)}; 104 | if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sch_params)) { 105 | BOOST_LOG_SEV(_lg, bls::error) 106 | << "Failed to set thread scheduling: Permission denied or invalid policy"; 107 | } 108 | } 109 | 110 | _start_read(); 111 | _svc.run(); 112 | 113 | BOOST_LOG_SEV(_lg, bls::trace) << "finished"; 114 | } 115 | 116 | } // namespace udmaio 117 | -------------------------------------------------------------------------------- /src/UioAxiDmaIf.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/UioAxiDmaIf.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "udmaio/FrameFormat.hpp" 19 | 20 | namespace axi_dma { 21 | 22 | std::ostream& operator<<(std::ostream& os, const s2mm_dmasr_t& stat) { 23 | auto fmt = [](std::string name, bool val) { return (val ? "+" : "-") + name; }; 24 | os << fmt("halted", stat.halted) << " "; 25 | os << fmt("idle", stat.idle) << " "; 26 | os << fmt("sg_incld", stat.sg_incld) << " "; 27 | os << fmt("dma_int_err", stat.dma_int_err) << " "; 28 | os << fmt("dma_slv_err", stat.dma_slv_err) << " "; 29 | os << fmt("dma_dec_err", stat.dma_dec_err) << " "; 30 | os << fmt("sg_int_err", stat.sg_int_err) << " "; 31 | os << fmt("sg_slv_err", stat.sg_slv_err) << " "; 32 | os << fmt("sg_dec_err", stat.sg_dec_err) << " "; 33 | os << fmt("ioc_irq", stat.ioc_irq) << " "; 34 | os << fmt("dly_irq", stat.dly_irq) << " "; 35 | os << fmt("err_irq", stat.err_irq); 36 | return os; 37 | } 38 | 39 | } // namespace axi_dma 40 | 41 | namespace udmaio { 42 | 43 | void UioAxiDmaIf::start(uintptr_t start_desc) { 44 | BOOST_LOG_SEV(_lg, bls::debug) << "start, start_desc = " << std::hex << start_desc << std::dec; 45 | 46 | // 0. 47 | s2mm_dmacr.wr({.reset = 1}); 48 | 49 | // 1. 50 | s2mm_curdesc.wr({.current_descriptor_pointer = static_cast(start_desc) >> 6}); 51 | s2mm_curdesc_msb.wr( 52 | (sizeof(start_desc) > sizeof(uint32_t)) ? static_cast(start_desc >> 32) : 0); 53 | 54 | // 2. 55 | auto ctrl_reg = s2mm_dmacr.rd(); 56 | BOOST_LOG_SEV(_lg, bls::trace) 57 | << "DMA ctrl = 0x" << std::hex << reg_to_raw(ctrl_reg) << std::dec; 58 | 59 | ctrl_reg.rs = 1; 60 | ctrl_reg.cyclic_bd_enable = 0; 61 | ctrl_reg.ioc_irq_en = 1; 62 | ctrl_reg.err_irq_en = 1; 63 | 64 | // 3. 65 | s2mm_dmacr.wr(ctrl_reg); 66 | 67 | BOOST_LOG_SEV(_lg, bls::trace) << "DMA control write"; 68 | 69 | // 4. 70 | s2mm_taildesc.wr({.tail_descriptor_pointer = 0x50 >> 6}); // for circular 71 | s2mm_taildesc_msb.wr( 72 | (sizeof(start_desc) > sizeof(uint32_t)) ? static_cast(start_desc >> 32) : 0); 73 | 74 | BOOST_LOG_SEV(_lg, bls::trace) 75 | << "DMA ctrl = 0x" << std::hex << reg_to_raw(s2mm_dmacr.rd()) << std::dec; 76 | } 77 | 78 | uintptr_t UioAxiDmaIf::get_curr_desc() { 79 | uintptr_t result = s2mm_curdesc.rd().current_descriptor_pointer; 80 | result <<= 6; 81 | if (sizeof(result) > sizeof(uint32_t)) { 82 | uintptr_t msb = s2mm_curdesc_msb.rd(); 83 | msb <<= 32; 84 | result |= msb; 85 | } 86 | return result; 87 | } 88 | 89 | std::tuple UioAxiDmaIf::clear_interrupt() { 90 | uint32_t irq_count = wait_for_interrupt(); 91 | 92 | auto stat = s2mm_dmasr.rd(); 93 | if (stat.ioc_irq) { 94 | BOOST_LOG_SEV(_lg, bls::trace) << "I/O IRQ"; 95 | } 96 | if (stat.err_irq) { 97 | BOOST_LOG_SEV(_lg, bls::warning) << "ERR IRQ"; 98 | } 99 | s2mm_dmasr.wr({.ioc_irq = stat.ioc_irq, .err_irq = stat.err_irq}); 100 | 101 | return std::make_tuple(irq_count, stat); 102 | } 103 | 104 | bool UioAxiDmaIf::check_for_errors() { 105 | bool has_errors = false; 106 | 107 | auto sr = s2mm_dmasr.rd(); 108 | if (sr.halted) { 109 | BOOST_LOG_SEV(_lg, bls::warning) << "DMA Halted"; 110 | } 111 | 112 | if (sr.idle) { 113 | BOOST_LOG_SEV(_lg, bls::warning) << "DMA Idle"; 114 | } 115 | 116 | if (sr.dma_int_err) { 117 | has_errors = true; 118 | BOOST_LOG_SEV(_lg, bls::fatal) << "DMA Internal Error"; 119 | } 120 | 121 | if (sr.dma_slv_err) { 122 | has_errors = true; 123 | BOOST_LOG_SEV(_lg, bls::fatal) << "DMA Slave Error"; 124 | } 125 | 126 | if (sr.dma_dec_err) { 127 | has_errors = true; 128 | BOOST_LOG_SEV(_lg, bls::fatal) << "DMA Decode Error"; 129 | } 130 | 131 | if (sr.sg_int_err) { 132 | has_errors = true; 133 | BOOST_LOG_SEV(_lg, bls::fatal) << "Scatter Gather Internal Error"; 134 | } 135 | 136 | if (sr.sg_slv_err) { 137 | has_errors = true; 138 | BOOST_LOG_SEV(_lg, bls::fatal) << "Scatter Gather Slave Error"; 139 | } 140 | 141 | if (sr.sg_dec_err) { 142 | has_errors = true; 143 | BOOST_LOG_SEV(_lg, bls::fatal) << "Scatter Gather Decode Error"; 144 | } 145 | 146 | return has_errors; 147 | } 148 | 149 | void UioAxiDmaIf::dump_status() { 150 | BOOST_LOG_SEV(_lg, bls::info) << s2mm_dmasr.rd(); 151 | } 152 | } // namespace udmaio 153 | -------------------------------------------------------------------------------- /inc/udmaio/UioMemSgdma.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include "DmaBufferAbstract.hpp" 18 | #include "RegAccessor.hpp" 19 | 20 | namespace udmaio { 21 | 22 | /// @brief Interface to AXI DMA scatter-gather buffers & descriptors 23 | /// Uses a UioIf to access DMA descriptor memory 24 | class UioMemSgdma : public UioIf { 25 | friend struct UioMemSgdmaTest; 26 | 27 | static constexpr int DESC_ADDR_STEP = 0x40; 28 | 29 | struct __attribute__((packed)) S2mmDescControl { 30 | uint32_t buffer_len : 26; 31 | bool rxeof : 1; 32 | bool rxsof : 1; 33 | uint32_t rsvd : 4; 34 | }; 35 | 36 | struct __attribute__((packed)) S2mmDescStatus { 37 | // This value indicates the amount of data received and stored in the buffer described by 38 | // this descriptor. This might or might not match the buffer length 39 | uint32_t num_stored_bytes : 26; 40 | // End of Frame. Flag indicating buffer holds the last part of packet. 41 | bool rxeof : 1; 42 | // Start of Frame. Flag indicating buffer holds first part of packet. 43 | bool rxsof : 1; 44 | // DMA Internal Error. Internal Error detected by primary AXI DataMover. 45 | bool dmainterr : 1; 46 | // DMA Slave Error. Slave Error detected by primary AXI DataMover. 47 | bool dmaslverr : 1; 48 | // DMA Decode Error. Decode Error detected by primary AXI DataMover. 49 | bool dmadecerr : 1; 50 | // Completed. This indicates to the software that the DMA Engine has 51 | // completed the transfer as described by the associated descriptor. 52 | bool cmplt : 1; 53 | }; 54 | 55 | struct __attribute__((packed, aligned(8))) S2mmDesc { 56 | uint64_t nxtdesc; 57 | uint64_t buffer_addr; 58 | uint32_t rsvd0x10; 59 | uint32_t rsvd0x14; 60 | S2mmDescControl control; 61 | S2mmDescStatus status; 62 | uint32_t app[5]; 63 | }; 64 | 65 | static_assert(sizeof(S2mmDescControl) == 4, "size of S2mmDescControl must be 4"); 66 | static_assert(sizeof(S2mmDescStatus) == 4, "size of S2mmDescStatus must be 4"); 67 | static_assert(sizeof(S2mmDesc) == 0x38, "size of S2mmDesc must be 0x34+4 for alignment"); 68 | 69 | // set excessively high number of 1024 descriptors, b/c the actual number is not known at compile time 70 | RegAccessorArray descriptors{this}; 71 | // Make a separate accessor for the statuses only, so we don't have to read/write the whole descriptor when we only want the status 72 | RegAccessorArray 73 | desc_statuses{this}; 74 | 75 | size_t _nr_cyc_desc; 76 | size_t _next_readable_buf; 77 | 78 | // Cache buffer addrs/sizes in order to not have to pull it from BRAM all the time 79 | std::vector _buf_addrs; 80 | size_t _buf_size; 81 | 82 | std::shared_ptr _mem; 83 | 84 | void write_cyc_mode(const std::vector& dst_bufs); 85 | 86 | public: 87 | UioMemSgdma(UioDeviceLocation dev_loc) : UioIf("UioMemSgdma", dev_loc) {} 88 | 89 | /// @brief Initialize SGDMA descriptors 90 | /// @param mem Memory receiving the SGDMA data 91 | /// @param num_buffers Number of descriptors / SGDMA blocks 92 | /// @param buf_size Size of each SGDMA block 93 | void init_buffers(std::shared_ptr mem, size_t num_buffers, size_t buf_size); 94 | 95 | /// @brief Print SGDMA descriptor 96 | /// @param desc SGDMA descriptor 97 | void print_desc(const S2mmDesc& desc) const; 98 | 99 | /// @brief Print all SGDMA descriptors 100 | void print_descs() const; 101 | 102 | /// @brief Get address of first SGDMA descriptor (needed for the AXI DMA I/F) 103 | /// @return Address of first SGDMA descriptor 104 | uintptr_t get_first_desc_addr() const; 105 | 106 | /// @brief Get all full SGDMA buffers 107 | /// @return Vector of buffer indices of full buffers 108 | std::vector get_full_buffers(); 109 | 110 | /// @brief Get SGDMA buffers for next packet 111 | /// @return Vector of buffer indices for next packet 112 | /// Returns only complete packets 113 | std::vector get_next_packet(); 114 | 115 | /// @brief Read data from a set of buffers 116 | /// @param indices Vector of buffer indices to read 117 | std::vector read_buffers(const std::vector indices); 118 | }; 119 | 120 | std::ostream& operator<<(std::ostream& os, const UioRegion& buf_info); 121 | 122 | } // namespace udmaio 123 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(libudmaio VERSION 1.5.2) 4 | 5 | include(CTest) 6 | 7 | find_package(Boost 1.65 COMPONENTS log program_options unit_test_framework REQUIRED) 8 | add_definitions(-DBOOST_LOG_DYN_LINK) 9 | 10 | set(CMAKE_CXX_STANDARD 17) 11 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 12 | set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) 13 | 14 | # library 15 | file(GLOB SOURCES "src/*.cpp") 16 | 17 | add_library(udmaio SHARED ${SOURCES}) 18 | target_include_directories(udmaio PUBLIC inc) 19 | target_link_libraries(udmaio Boost::log Boost::dynamic_linking) 20 | target_compile_options(udmaio PRIVATE -Wall -Wextra -Wno-packed-bitfield-compat -Wno-missing-field-initializers -Wno-psabi -O2) 21 | set_target_properties(udmaio PROPERTIES VERSION ${PROJECT_VERSION}) 22 | if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") 23 | target_precompile_headers(udmaio PRIVATE inc/udmaio/BoostPCH.hpp) 24 | endif() 25 | 26 | include(GNUInstallDirs) 27 | include(CMakePackageConfigHelpers) 28 | 29 | configure_package_config_file(udmaio-config.cmake.in 30 | ${CMAKE_CURRENT_BINARY_DIR}/udmaio-config.cmake 31 | INSTALL_DESTINATION ${LIB_INSTALL_DIR}/cmake/udmaio 32 | PATH_VARS CMAKE_INSTALL_LIBDIR CMAKE_INSTALL_INCLUDEDIR 33 | ) 34 | 35 | write_basic_package_version_file( 36 | ${CMAKE_CURRENT_BINARY_DIR}/udmaio-config-version.cmake 37 | VERSION ${PROJECT_VERSION} 38 | COMPATIBILITY SameMajorVersion 39 | ) 40 | 41 | install( 42 | TARGETS udmaio 43 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 44 | ) 45 | install( 46 | DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/inc/udmaio" 47 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 48 | COMPONENT dev 49 | ) 50 | install( 51 | FILES 52 | "${CMAKE_CURRENT_BINARY_DIR}/udmaio-config.cmake" 53 | "${CMAKE_CURRENT_BINARY_DIR}/udmaio-config-version.cmake" 54 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/udmaio" 55 | ) 56 | 57 | # Invoke cmake with -DTARGET_HW=ZUP or -DTARGET_HW=Z7IO. ZUP is the default 58 | if("${TARGET_HW}" STREQUAL "") 59 | message(WARNING "Set target hardware, e.g. cmake -DTARGET_HW=ZUP\nSupported targets: ZUP, Z7IO\nUsing ZUP as default target") 60 | set(TARGET_HW "ZUP") 61 | endif() 62 | 63 | # cpp example 64 | add_executable(axi_dma_demo_cpp) 65 | file(GLOB EXAMPLE_SOURCES "example/demo_cpp/src/*.cpp") 66 | target_sources(axi_dma_demo_cpp PRIVATE ${EXAMPLE_SOURCES}) 67 | target_include_directories(axi_dma_demo_cpp PRIVATE example/demo_cpp/inc inc) 68 | target_link_libraries(axi_dma_demo_cpp Boost::log Boost::program_options Boost::dynamic_linking udmaio) 69 | target_compile_options(axi_dma_demo_cpp PRIVATE -Wall -Wextra -Wno-packed-bitfield-compat -Wno-missing-field-initializers -O2) 70 | target_compile_definitions(axi_dma_demo_cpp PRIVATE -DTARGET_HW=TARGET_HW_${TARGET_HW}) 71 | if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") 72 | target_precompile_headers(axi_dma_demo_cpp PRIVATE example/demo_cpp/inc/BoostPCH.hpp) 73 | endif() 74 | 75 | if(BUILD_TESTING) 76 | # library tests 77 | file(GLOB LIB_TESTS "tests/*.cpp") 78 | 79 | foreach(TEST_FILE ${LIB_TESTS}) 80 | get_filename_component(test_basename ${TEST_FILE} NAME_WE) 81 | 82 | add_executable(${test_basename} ${TEST_FILE}) 83 | target_include_directories(${test_basename} PRIVATE inc) 84 | target_link_libraries(${test_basename} LINK_PUBLIC Boost::unit_test_framework Boost::log Boost::program_options Boost::dynamic_linking udmaio) 85 | add_test(NAME ${test_basename} COMMAND ${test_basename}) 86 | endforeach() 87 | 88 | # cpp example tests 89 | file(GLOB TEST_SOURCES "example/demo_cpp/src/*.cpp") 90 | file(GLOB EXAMPLE_MAIN "example/demo_cpp/src/axi_dma_demo.cpp") 91 | list(REMOVE_ITEM TEST_SOURCES ${EXAMPLE_MAIN}) 92 | add_library(test-objs OBJECT ${TEST_SOURCES}) 93 | target_include_directories(test-objs PRIVATE example/demo_cpp/inc inc) 94 | 95 | file(GLOB TESTS "example/demo_cpp/tests/*.cpp") 96 | 97 | foreach(TEST_FILE ${TESTS}) 98 | get_filename_component(test_basename ${TEST_FILE} NAME_WE) 99 | 100 | add_executable(${test_basename} ${TEST_FILE} $) 101 | target_include_directories(${test_basename} PRIVATE example/demo_cpp/inc example/demo_cpp/tests/inc inc) 102 | target_link_libraries(${test_basename} LINK_PUBLIC Boost::unit_test_framework Boost::log Boost::program_options Boost::dynamic_linking udmaio) 103 | add_test(NAME ${test_basename} COMMAND ${test_basename}) 104 | endforeach() 105 | endif() 106 | 107 | # The python binding is usually installed via setuptools; 108 | # we include the C++ part of it here only to populate the compile-command.json for editors/IDEs 109 | if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/pyudmaio/pybind11/CMakeLists.txt") 110 | message("pybind11 submodule found, adding pyudmaio target") 111 | add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/pyudmaio/pybind11") 112 | pybind11_add_module(pyudmaio EXCLUDE_FROM_ALL 113 | "${CMAKE_CURRENT_LIST_DIR}/pyudmaio/src/PythonBinding.cpp" 114 | "${CMAKE_CURRENT_LIST_DIR}/pyudmaio/src/DataHandlerPython.cpp" 115 | ) 116 | else() 117 | message("skipping python binding, clone pybind11 submodule to include it") 118 | endif() 119 | 120 | # Create debian package containing library 121 | set(CPACK_GENERATOR "DEB") 122 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Patrick Huesmann ") 123 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/MicroTCA-Tech-Lab/libudmaio") 124 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-log-dev") # use -dev b/c it's independent of package version 125 | set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Userspace I/O library for Xilinx AXI S2MM DMA ") 126 | set(CPACK_DEBIAN_PACKAGE_SECTION "contrib/devel") 127 | set(CPACK_PACKAGE_NAME "libudmaio") 128 | set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) 129 | set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) 130 | set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) 131 | include(CPack) 132 | -------------------------------------------------------------------------------- /inc/udmaio/RegAccessor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "udmaio/UioIf.hpp" 6 | 7 | #ifndef REG_PACKED_ALIGNED 8 | #define REG_PACKED_ALIGNED __attribute__((packed, aligned(4))) 9 | #endif 10 | 11 | namespace udmaio { 12 | 13 | /** 14 | * @brief Base class implementing register access. Only used by subclasses, not used directly 15 | * @tparam C Register content type 16 | */ 17 | template 18 | class RegAccessorBase { 19 | static_assert((sizeof(C) % sizeof(uint32_t)) == 0, 20 | "Register size must be a aligned to 32 bits"); 21 | 22 | protected: 23 | UioIf* _if; 24 | 25 | public: 26 | using constructor_arg_type = UioIf*; 27 | 28 | RegAccessorBase() = delete; 29 | 30 | /** 31 | * @brief Construct a RegAccessorBase 32 | * @param interface Hardware interface 33 | */ 34 | RegAccessorBase(constructor_arg_type interface) : _if{interface} {} 35 | 36 | /** 37 | * @brief Read register 38 | * @param offs Offset into address space 39 | * @return C Register content 40 | */ 41 | C _rd(uint32_t offs) const { return _if->template _rd_reg(offs); } 42 | 43 | /** 44 | * @brief Write register 45 | * @param offs Offset into address space 46 | * @param value Content to write to register 47 | */ 48 | void _wr(uint32_t offs, const C& value) { _if->template _wr_reg(offs, value); } 49 | }; 50 | 51 | /** 52 | * @brief Accessor for register array element. Returned by RegAccessorArray::operator[] 53 | * @tparam C Register content type 54 | */ 55 | template 56 | class RegAccessorArrayElement : public RegAccessorBase { 57 | static_assert((sizeof(C) % sizeof(uint32_t)) == 0, "Register size must be aligned to 32 bits"); 58 | 59 | const uint32_t _reg_offs; 60 | 61 | public: 62 | /** 63 | * @brief Construct a new register array element accessor 64 | * @param c Hardware interface 65 | * @param reg_offs Offset of array element into address space 66 | */ 67 | RegAccessorArrayElement(typename RegAccessorBase::constructor_arg_type c, uint32_t reg_offs) 68 | : RegAccessorBase{c}, _reg_offs{reg_offs} {} 69 | 70 | /** 71 | * @brief Read register array element 72 | * @return T Register content 73 | */ 74 | C rd() const { return RegAccessorBase::_rd(_reg_offs); } 75 | 76 | /** 77 | * @brief Write register 78 | * @param value Content to write to register 79 | */ 80 | void wr(const C& value) { RegAccessorBase::_wr(_reg_offs, value); } 81 | }; 82 | 83 | /** 84 | * @brief Accessor for register array 85 | * @tparam C Content type of a register array element 86 | * @tparam offset Offset of first register array element into address space 87 | * @tparam arr_size Number of array elements 88 | * @tparam arr_stride Address distance from one element to the next 89 | */ 90 | template 91 | class RegAccessorArray : public RegAccessorBase { 92 | static_assert((sizeof(C) % sizeof(uint32_t)) == 0, "Register size must be aligned to 32 bits"); 93 | static_assert((offset % sizeof(uint32_t)) == 0, 94 | "Register array offset must be aligned to 32 bits"); 95 | static_assert(arr_size > 0, "Register array size must be non-zero"); 96 | static_assert((arr_stride % sizeof(uint32_t)) == 0, 97 | "Register array stride must be aligned to 32 bits"); 98 | 99 | public: 100 | using RegAccessorBase::RegAccessorBase; 101 | 102 | /** 103 | * @brief Return element accessor 104 | * @param idx Index into register array 105 | * @return RegAccessorArrayElement Accessor for requested array element 106 | */ 107 | RegAccessorArrayElement operator[](uint32_t idx) { 108 | assert(idx < arr_size); 109 | const uint32_t reg_offs = offset + idx * arr_stride; 110 | return RegAccessorArrayElement{RegAccessorBase::_if, reg_offs}; 111 | } 112 | 113 | const RegAccessorArrayElement operator[](uint32_t idx) const { 114 | assert(idx < arr_size); 115 | const uint32_t reg_offs = offset + idx * arr_stride; 116 | return RegAccessorArrayElement{RegAccessorBase::_if, reg_offs}; 117 | } 118 | 119 | /** 120 | * @brief Get array size 121 | * @return constexpr uint32_t Number of elements in this array 122 | */ 123 | constexpr uint32_t size() { return arr_size; } 124 | }; 125 | 126 | /** 127 | * @brief Accessor for single register 128 | * @tparam C Register type 129 | * @param offset Register offset into address space 130 | */ 131 | template 132 | class RegAccessorArray : public RegAccessorBase { 133 | static_assert((sizeof(C) % sizeof(uint32_t)) == 0, "Register size must be aligned to 32 bits"); 134 | static_assert((offset % sizeof(uint32_t)) == 0, "Register offset must be aligned to 32 bits"); 135 | 136 | public: 137 | using RegAccessorBase::RegAccessorBase; 138 | 139 | /** 140 | * @brief Read register 141 | * @return T Register content 142 | */ 143 | C rd() const { return RegAccessorBase::_rd(offset); } 144 | 145 | /** 146 | * @brief Write register 147 | * @param value Content to write to register 148 | */ 149 | void wr(const C& value) { RegAccessorBase::_wr(offset, value); } 150 | }; 151 | 152 | /** 153 | * @brief Convenience wrapper for single (i.e. non-array) register 154 | * @tparam C Register content type 155 | * @tparam offset Register offset into address space 156 | */ 157 | template 158 | using RegAccessor = RegAccessorArray; 159 | 160 | /** 161 | * @brief Convert fixed C string to a std::string 162 | * 163 | * @tparam T Register field holding the string (must be of type char[N]) 164 | * @param chararray Register field holding the string (must be of type char[N]) 165 | * @return std::string Converted C++ string, limited to max. N chars 166 | */ 167 | template 168 | std::string field_to_str(const T& chararray) { 169 | return {chararray, strnlen(chararray, sizeof(chararray))}; 170 | } 171 | 172 | } // namespace udmaio 173 | -------------------------------------------------------------------------------- /example/demo_cpp/src/axi_dma_demo.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "DataHandlerPrint.hpp" 18 | #include "UioGpioStatus.hpp" 19 | #include "UioTrafficGen.hpp" 20 | #include "udmaio/FpgaMemBufferOverAxi.hpp" 21 | #include "udmaio/FpgaMemBufferOverXdma.hpp" 22 | #include "udmaio/Logging.hpp" 23 | #include "udmaio/UDmaBuf.hpp" 24 | #include "udmaio/UioAxiDmaIf.hpp" 25 | #include "udmaio/UioIf.hpp" 26 | #include "udmaio/UioMemSgdma.hpp" 27 | #include 28 | #include 29 | 30 | #define TARGET_HW_ZUP 1 31 | #define TARGET_HW_Z7IO 2 32 | 33 | #if TARGET_HW == TARGET_HW_ZUP 34 | #pragma message "Compiling for DAMC-FMC2ZUP" 35 | #include "ZupExampleProjectConsts.hpp" 36 | #elif TARGET_HW == TARGET_HW_Z7IO 37 | #pragma message "Compiling for DAMC-FMC1Z7IO" 38 | #include "Z7ioExampleProjectConsts.hpp" 39 | #endif 40 | 41 | namespace bpo = boost::program_options; 42 | 43 | using namespace udmaio; 44 | using namespace std::chrono_literals; 45 | 46 | volatile bool g_stop_loop = false; 47 | 48 | void signal_handler([[maybe_unused]] int signal) { 49 | g_stop_loop = true; 50 | } 51 | 52 | int main(int argc, char* argv[]) { 53 | bpo::options_description desc("AXI DMA demo"); 54 | bool debug, trace, rt_prio; 55 | uint16_t pkt_pause; 56 | uint16_t nr_pkts; 57 | uint32_t pkt_len; 58 | DmaMode mode; 59 | std::string dev_path; 60 | 61 | // clang-format off 62 | desc.add_options() 63 | ("help,h", "this help") 64 | ("mode", bpo::value(&mode)->multitoken()->required(), "select operation mode (xdma or uio) - see docs for details") 65 | ("debug", bpo::bool_switch(&debug), "enable verbose output (debug level)") 66 | ("trace", bpo::bool_switch(&trace), "enable even more verbose output (trace level)") 67 | ("rt_prio", bpo::bool_switch(&rt_prio), "enable RT priority for I/O thread") 68 | ("pkt_pause", bpo::value(&pkt_pause)->default_value(10), "pause between pkts - see AXI TG user's manual") 69 | ("nr_pkts", bpo::value(&nr_pkts)->default_value(1), "number of packets to generate - see AXI TG user's manual") 70 | ("pkt_len", bpo::value(&pkt_len)->default_value(1024), "packet length - see AXI TG user's manual") 71 | ("dev_path", bpo::value(&dev_path), "Path to xdma device nodes (e.g. /dev/xdma/card0)") 72 | ; 73 | // clang-format on 74 | 75 | bpo::variables_map vm; 76 | bpo::store(bpo::parse_command_line(argc, argv, desc), vm); 77 | 78 | if (vm.count("help")) { 79 | std::cout << desc << "\n"; 80 | return 0; 81 | } 82 | 83 | bpo::notify(vm); 84 | 85 | if (mode == DmaMode::XDMA && dev_path.empty()) { 86 | std::cerr << "XDMA mode needs path to device (--dev_path)" << std::endl; 87 | return 0; 88 | } 89 | 90 | Logger::init(17); 91 | if (trace) { 92 | Logger::set_level(bls::trace); 93 | } else if (debug) { 94 | Logger::set_level(bls::debug); 95 | } else { 96 | Logger::set_level(bls::info); 97 | } 98 | 99 | Logger log{"main"}; 100 | 101 | std::signal(SIGINT, signal_handler); 102 | 103 | if (mode == DmaMode::UIO) { 104 | UioDeviceLocation::set_link_axi(); 105 | } else { 106 | UioDeviceLocation::set_link_xdma(dev_path, 107 | target_hw_consts::pcie_offset, 108 | TARGET_HW == TARGET_HW_Z7IO); 109 | } 110 | 111 | auto gpio_status = std::make_unique(target_hw_consts::axi_gpio_status); 112 | 113 | bool is_ddr4_init = gpio_status->is_ddr4_init_calib_complete(); 114 | BOOST_LOG_SEV(log._lg, bls::debug) << "DDR4 init = " << is_ddr4_init; 115 | if (!is_ddr4_init) { 116 | throw std::runtime_error("DDR4 init calib is not complete"); 117 | } 118 | 119 | auto axi_dma = std::make_unique(target_hw_consts::axi_dma); 120 | auto mem_sgdma = std::make_unique(target_hw_consts::axi_bram_ctrl); 121 | auto traffic_gen = std::make_unique(target_hw_consts::axi_traffic_gen); 122 | 123 | std::shared_ptr udmabuf; 124 | if (mode == DmaMode::UIO) { 125 | udmabuf = std::make_shared(); 126 | } else { 127 | udmabuf = 128 | std::make_shared(dev_path, target_hw_consts::fpga_mem_phys_addr); 129 | } 130 | 131 | const size_t pkt_size = pkt_len * target_hw_consts::lfsr_bytes_per_beat; 132 | 133 | DataHandlerPrint data_handler{*axi_dma, 134 | *mem_sgdma, 135 | *udmabuf, 136 | target_hw_consts::lfsr_bytes_per_beat, 137 | nr_pkts * pkt_size, 138 | rt_prio}; 139 | auto fut = std::async(std::launch::async, std::ref(data_handler)); 140 | 141 | constexpr int nr_buffers = 32; 142 | mem_sgdma->init_buffers(udmabuf, nr_buffers, pkt_size); 143 | 144 | uintptr_t first_desc = mem_sgdma->get_first_desc_addr(); 145 | axi_dma->start(first_desc); 146 | traffic_gen->start(nr_pkts, pkt_len, pkt_pause); 147 | 148 | // Wait until data_handler has finished or user hit Ctrl-C 149 | while (fut.wait_for(10ms) != std::future_status::ready) { 150 | if (g_stop_loop) { 151 | data_handler.stop(); 152 | fut.wait(); 153 | axi_dma->dump_status(); 154 | break; 155 | } 156 | } 157 | axi_dma->check_for_errors(); 158 | traffic_gen->stop(); 159 | 160 | auto [counter_ok, counter_total] = fut.get(); 161 | std::cout << "Counters: OK = " << counter_ok << ", total = " << counter_total << "\n"; 162 | 163 | return !(counter_ok == counter_total); 164 | } 165 | -------------------------------------------------------------------------------- /example/demo_python/axi_dma_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 4 | 5 | import sys 6 | import os 7 | import numpy as np 8 | import argparse 9 | 10 | from pyudmaio import UioDeviceLocation, ConfigUio, ConfigXdma, FpgaMemBufferOverXdma, FpgaMemBufferOverAxi 11 | from pyudmaio import UDmaBuf, UioAxiDmaIf, UioMemSgdma, DataHandler 12 | from pyudmaio import LogLevel, set_logging_level 13 | from GpioStatus import GpioStatus 14 | from TrafficGen import TrafficGen 15 | 16 | import ProjectConsts 17 | 18 | # Implements LFSR as described in "AXI Traffic Generator v3.0" 19 | 20 | 21 | class Lfsr(object): 22 | def __init__(self, seed): 23 | self.state = seed 24 | 25 | def advance(self): 26 | old_val = self.state 27 | new_bit = 1 ^ (self.state) ^ (self.state >> 1) ^ ( 28 | self.state >> 3) ^ (self.state >> 12) 29 | self.state = (new_bit << 15) | (self.state >> 1) 30 | self.state &= 0xffff 31 | return old_val 32 | 33 | 34 | # Checks array against expected LFSR values 35 | class LfsrChecker(object): 36 | 37 | # blk_len: Each array is built up by blocks of N identical values (determined by bus width) 38 | # e.g. 8 for ZUP, 4 for Z7IO 39 | def __init__(self, blk_len): 40 | self.lfsr = None 41 | self.blk_len = blk_len 42 | 43 | def check(self, arr): 44 | if self.lfsr is None: 45 | self.lfsr = Lfsr(arr[0]) 46 | 47 | # Create vector of expected values 48 | vfy = np.asarray([self.lfsr.advance() for n in range( 49 | len(arr) // self.blk_len)], dtype=np.uint16) 50 | 51 | # Create matrix where rows are blocks of identical values 52 | arr = arr.reshape(-1, self.blk_len) 53 | 54 | for n, v in enumerate(vfy): 55 | if not np.all(np.equal(arr[n], v)): 56 | print(f'mismatch at row {n}: rcv {arr[n]}, exp {v}') 57 | return False 58 | return True 59 | 60 | 61 | def main(): 62 | parser = argparse.ArgumentParser( 63 | description='AXI DMA demo for LFSR traffic generator' 64 | ) 65 | parser.add_argument('-H', '--hardware', 66 | type=str, 67 | default='zup', 68 | help='Hardware (zup or z7io)' 69 | ) 70 | parser.add_argument('-l', '--pkt_len', 71 | type=int, 72 | default=1024, 73 | help='Packet length, default 1024' 74 | ) 75 | parser.add_argument('-n', '--nr_pkts', 76 | type=int, 77 | default=1, 78 | help='Number of packets, default 1' 79 | ) 80 | parser.add_argument('-p', '--pkt_pause', 81 | type=int, 82 | default=10, 83 | help='Pause between packets, default 10' 84 | ) 85 | parser.add_argument('-d', '--dev_path', 86 | type=str, 87 | help='Path to xdma device nodes' 88 | ) 89 | parser.add_argument('-r', '--rt-prio', 90 | action='store_true', 91 | help='Enable RT scheduling priority' 92 | ) 93 | dma_mode = parser.add_mutually_exclusive_group(required=True) 94 | dma_mode.add_argument('-x', '--xdma', 95 | action='store_true', 96 | help='Use XDMA mode' 97 | ) 98 | dma_mode.add_argument('-u', '--uio', 99 | action='store_true', 100 | help='Use UIO mode' 101 | ) 102 | log_lvl = parser.add_mutually_exclusive_group(required=False) 103 | log_lvl.add_argument('--debug', 104 | action='store_true', 105 | help='Enable verbose output (debug level)' 106 | ) 107 | log_lvl.add_argument('--trace', 108 | action='store_true', 109 | help='Enable even more verbose output (trace level)' 110 | ) 111 | args = parser.parse_args() 112 | 113 | if args.xdma and not args.dev_path: 114 | print('Need device path in XDMA mode', file=sys.stderr) 115 | sys.exit(-1) 116 | 117 | if args.trace: 118 | print("Set log level to TRACE") 119 | set_logging_level(LogLevel.TRACE) 120 | elif args.debug: 121 | print("Set log level to DEBUG") 122 | set_logging_level(LogLevel.DEBUG) 123 | 124 | consts = { 125 | 'zup': ProjectConsts.ZupExampleConsts, 126 | 'z7io': ProjectConsts.Z7ioExampleConsts 127 | }[args.hardware.lower()] 128 | 129 | if args.xdma: 130 | UioDeviceLocation.set_link_xdma( 131 | args.dev_path, consts.PCIE_AXI4L_OFFSET, args.hardware.lower() == 'z7io') 132 | else: 133 | UioDeviceLocation.set_link_axi() 134 | 135 | g = GpioStatus(consts.AXI_GPIO_STATUS) 136 | if not g.is_ddr4_init_calib_complete(): 137 | raise RuntimeError('DDR4 init calib is not complete') 138 | 139 | print('Creating DMA handler') 140 | 141 | axi_dma = UioAxiDmaIf(consts.AXI_DMA_0) 142 | mem_sgdma = UioMemSgdma(consts.BRAM_CTRL_0) 143 | if args.xdma: 144 | udmabuf = FpgaMemBufferOverXdma( 145 | args.dev_path, consts.FPGA_MEM_PHYS_ADDR) 146 | else: 147 | udmabuf = UDmaBuf() 148 | # when using predefined UIO region such as PL-DDR 149 | # udmabuf = FpgaMemBufferOverAxi(UioDeviceLocation('plddr-axi-test')) 150 | 151 | data_handler = DataHandler(axi_dma, mem_sgdma, udmabuf, True, 1024, args.rt_prio) 152 | traffic_gen = TrafficGen(consts.AXI_TRAFFIC_GEN_0) 153 | 154 | print('Starting DMA') 155 | NR_BUFFERS = 32 156 | data_handler.start(NR_BUFFERS, args.pkt_len * consts.LFSR_BYTES_PER_BEAT) 157 | 158 | print('Starting TrafficGen') 159 | traffic_gen.start(args.nr_pkts, args.pkt_len, args.pkt_pause) 160 | 161 | checker = LfsrChecker(consts.LFSR_BYTES_PER_BEAT // 2) # 2 bytes per word 162 | words_total = 0 163 | 164 | while True: 165 | data_bytes = data_handler.read(10) 166 | if not data_bytes.size: 167 | break 168 | result = np.frombuffer(data_bytes.tobytes(), dtype=np.uint16) 169 | if not checker.check(result): 170 | break 171 | words_total += result.size 172 | 173 | print(f'{words_total} words OK') 174 | traffic_gen.stop() 175 | data_handler.stop() 176 | axi_dma.dump_status() 177 | 178 | 179 | if __name__ == '__main__': 180 | main() 181 | -------------------------------------------------------------------------------- /src/FrameFormat.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2023 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/FrameFormat.hpp" 13 | 14 | #include 15 | #include 16 | 17 | namespace udmaio { 18 | 19 | const std::unordered_map FrameFormat::_pxfmt_enum_to_str_tab{ 20 | {PixelFormat::Mono8, "Mono8"}, 21 | {PixelFormat::Mono10, "Mono10"}, 22 | {PixelFormat::Mono12, "Mono12"}, 23 | {PixelFormat::Mono14, "Mono14"}, 24 | {PixelFormat::Mono16, "Mono16"}, 25 | {PixelFormat::BayerGR8, "BayerGR8"}, 26 | {PixelFormat::BayerRG8, "BayerRG8"}, 27 | {PixelFormat::BayerGB8, "BayerGB8"}, 28 | {PixelFormat::BayerBG8, "BayerBG8"}, 29 | {PixelFormat::BayerGR10, "BayerGR10"}, 30 | {PixelFormat::BayerRG10, "BayerRG10"}, 31 | {PixelFormat::BayerGB10, "BayerGB10"}, 32 | {PixelFormat::BayerBG10, "BayerBG10"}, 33 | {PixelFormat::BayerGR12, "BayerGR12"}, 34 | {PixelFormat::BayerRG12, "BayerRG12"}, 35 | {PixelFormat::BayerGB12, "BayerGB12"}, 36 | {PixelFormat::BayerBG12, "BayerBG12"}, 37 | {PixelFormat::BayerGR16, "BayerGR16"}, 38 | {PixelFormat::BayerRG16, "BayerRG16"}, 39 | {PixelFormat::BayerGB16, "BayerGB16"}, 40 | {PixelFormat::BayerBG16, "BayerBG16"}, 41 | {PixelFormat::RGB8, "RGB8"}, 42 | {PixelFormat::BGR8, "BGR8"}, 43 | {PixelFormat::RGBa8, "RGBa8"}, 44 | {PixelFormat::BGRa8, "BGRa8"}, 45 | {PixelFormat::RGB10V1Packed1, "RGB10V1Packed1"}, 46 | {PixelFormat::RGB10p32, "RGB10p32"}, 47 | {PixelFormat::RGB565p, "RGB565p"}, 48 | {PixelFormat::BGR565p, "BGR565p"}, 49 | {PixelFormat::YUV422_8_UYVY, "YUV422_8_UYVY"}, 50 | {PixelFormat::YUV422_8, "YUV422_8"}, 51 | {PixelFormat::YUV8_UYV, "YUV8_UYV"}, 52 | {PixelFormat::YCbCr8_CbYCr, "YCbCr8_CbYCr"}, 53 | {PixelFormat::YCbCr422_8, "YCbCr422_8"}, 54 | {PixelFormat::YCbCr422_8_CbYCrY, "YCbCr422_8_CbYCrY"}, 55 | {PixelFormat::YCbCr601_8_CbYCr, "YCbCr601_8_CbYCr"}, 56 | {PixelFormat::YCbCr601_422_8, "YCbCr601_422_8"}, 57 | {PixelFormat::YCbCr601_422_8_CbYCrY, "YCbCr601_422_8_CbYCrY"}, 58 | {PixelFormat::YCbCr709_8_CbYCr, "YCbCr709_8_CbYCr"}, 59 | {PixelFormat::YCbCr709_422_8, "YCbCr709_422_8"}, 60 | {PixelFormat::YCbCr601_422_8_CbYCrY, "YCbCr601_422_8_CbYCrY"}, 61 | {PixelFormat::YCbCr709_8_CbYCr, "YCbCr709_8_CbYCr"}, 62 | {PixelFormat::YCbCr709_422_8, "YCbCr709_422_8"}, 63 | {PixelFormat::YCbCr709_422_8_CbYCrY, "YCbCr709_422_8_CbYCrY"}, 64 | {PixelFormat::RGB8_Planar, "RGB8_Planar"}, 65 | }; 66 | 67 | FrameFormat::PixelFormat FrameFormat::pix_fmt_from_str(std::string pix_fmt_str) { 68 | for (const auto& f : _pxfmt_enum_to_str_tab) { 69 | if (f.second == pix_fmt_str) { 70 | return f.first; 71 | } 72 | } 73 | return PixelFormat::unknown; 74 | } 75 | 76 | std::string FrameFormat::pix_fmt_to_str(PixelFormat pix_fmt) { 77 | auto it = _pxfmt_enum_to_str_tab.find(pix_fmt); 78 | if (it != _pxfmt_enum_to_str_tab.end()) { 79 | return it->second; 80 | } 81 | return ""; 82 | } 83 | 84 | std::ostream& operator<<(std::ostream& os, const FrameFormat::PixelFormat px_fmt) { 85 | os << FrameFormat::pix_fmt_to_str(px_fmt); 86 | return os; 87 | } 88 | 89 | FrameFormat::FrameFormat() 90 | : _dim{0, 0} 91 | , _pix_fmt{PixelFormat::unknown} 92 | , _bpp{1} 93 | , _word_width{4} 94 | , _pix_per_word{4} 95 | , _hsize{0} {} 96 | 97 | void FrameFormat::set_format(dim_t dim, uint16_t bytes_per_pixel, uint8_t word_width) { 98 | update_frm_dim(dim); 99 | update_bpp(bytes_per_pixel); 100 | _word_width = word_width; 101 | update_hsize(); 102 | } 103 | 104 | void FrameFormat::set_format(dim_t dim, std::string pix_fmt_str, uint8_t word_width) { 105 | set_format(dim, pix_fmt_from_str(pix_fmt_str), word_width); 106 | } 107 | 108 | void FrameFormat::set_format(dim_t dim, PixelFormat pixFmt, uint8_t word_width) { 109 | update_frm_dim(dim); 110 | update_px_fmt(pixFmt); 111 | _word_width = word_width; 112 | update_hsize(); 113 | } 114 | 115 | void FrameFormat::set_dim(dim_t dim) { 116 | update_frm_dim(dim); 117 | update_hsize(); 118 | } 119 | 120 | void FrameFormat::set_bpp(uint16_t bytes_per_pixel) { 121 | update_bpp(bytes_per_pixel); 122 | update_hsize(); 123 | } 124 | 125 | void FrameFormat::set_pix_fmt(PixelFormat pixFmt) { 126 | update_px_fmt(pixFmt); 127 | update_hsize(); 128 | } 129 | 130 | void FrameFormat::set_word_width(uint8_t word_width) { 131 | _word_width = word_width; 132 | update_hsize(); 133 | } 134 | 135 | FrameFormat::dim_t FrameFormat::get_dim() const { 136 | return _dim; 137 | } 138 | 139 | FrameFormat::PixelFormat FrameFormat::get_pixel_format() const { 140 | return _pix_fmt; 141 | } 142 | 143 | std::string FrameFormat::get_pixel_format_str() const { 144 | return pix_fmt_to_str(_pix_fmt); 145 | } 146 | 147 | uint16_t FrameFormat::get_bytes_per_pixel() const { 148 | return _bpp; 149 | } 150 | 151 | uint8_t FrameFormat::get_word_width() const { 152 | return _word_width; 153 | } 154 | 155 | uint16_t FrameFormat::get_pixel_per_word() const { 156 | return _pix_per_word; 157 | } 158 | 159 | uint16_t FrameFormat::get_hsize() const { 160 | return _hsize; 161 | } 162 | 163 | size_t FrameFormat::get_frm_size() const { 164 | return (size_t)(_hsize * _dim.height); 165 | } 166 | 167 | void FrameFormat::update_frm_dim(dim_t dim) { 168 | _dim = dim; 169 | } 170 | 171 | void FrameFormat::update_bpp(uint16_t bytes_per_pixel) { 172 | _pix_fmt = PixelFormat::unknown; 173 | _bpp = bytes_per_pixel; 174 | } 175 | 176 | void FrameFormat::update_px_fmt(PixelFormat pixFmt) { 177 | if (pixFmt == PixelFormat::unknown) { 178 | throw std::runtime_error("Attempt to set pixel format to 'unknown'"); 179 | } else { 180 | _pix_fmt = pixFmt; 181 | _bpp = (0x00ff & (static_cast(pixFmt) >> 16)) / 8; 182 | } 183 | } 184 | 185 | void FrameFormat::update_hsize() { 186 | _pix_per_word = _word_width / _bpp; 187 | _hsize = std::ceil((float)_dim.width / _pix_per_word) * _word_width; 188 | } 189 | 190 | std::ostream& operator<<(std::ostream& os, const FrameFormat& frm_fmt) { 191 | const auto dim = frm_fmt.get_dim(); 192 | os << "width = 0x" << std::hex << std::setfill('0') << std::setw(4) << dim.width 193 | << std::dec << "\n"; 194 | os << "height = 0x" << std::hex << std::setfill('0') << std::setw(4) << dim.height 195 | << std::dec << "\n"; 196 | os << "px_fmt = " << frm_fmt.get_pixel_format_str() << " (0x" << std::hex 197 | << std::setfill('0') << std::setw(8) << static_cast(frm_fmt.get_pixel_format()) 198 | << std::dec << ")" 199 | << "\n"; 200 | os << "bpp = 0x" << std::hex << std::setfill('0') << std::setw(4) 201 | << frm_fmt.get_bytes_per_pixel() << std::dec << "\n"; 202 | os << "word_width = 0x" << std::hex << std::setfill('0') << std::setw(2) 203 | << (int)frm_fmt.get_word_width() << std::dec << "\n"; 204 | os << "pix_per_word = 0x" << std::hex << std::setfill('0') << std::setw(4) 205 | << frm_fmt.get_pixel_per_word() << std::dec << "\n"; 206 | os << "hsize = 0x" << std::hex << std::setfill('0') << std::setw(4) 207 | << frm_fmt.get_hsize() << std::dec << "\n"; 208 | return os; 209 | } 210 | 211 | } // namespace udmaio 212 | -------------------------------------------------------------------------------- /src/UioConfig.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | // Copyright (c) 2022 Atom Computing, Inc. 12 | 13 | #include "udmaio/UioConfig.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include "udmaio/HwAccessor.hpp" 22 | 23 | namespace udmaio { 24 | 25 | std::istream& operator>>(std::istream& in, DmaMode& mode) { 26 | std::string token; 27 | in >> token; 28 | if (token == "xdma") 29 | mode = DmaMode::XDMA; 30 | else if (token == "uio") 31 | mode = DmaMode::UIO; 32 | else 33 | in.setstate(std::ios_base::failbit); 34 | return in; 35 | } 36 | 37 | std::unique_ptr UioDeviceLocation::_link_cfg{}; 38 | 39 | void UioDeviceLocation::set_link_axi() { 40 | _link_cfg = std::make_unique(); 41 | } 42 | 43 | void UioDeviceLocation::set_link_xdma(std::string xdma_path, 44 | uintptr_t pcie_offs, 45 | bool x7_series_mode) { 46 | _link_cfg = std::make_unique(xdma_path, pcie_offs, x7_series_mode); 47 | } 48 | 49 | HwAccessorPtr UioDeviceLocation::hw_acc() const { 50 | if (_hw_acc_override) { 51 | // If a hardware accessor is set (e.g. for testing), return it in place of the regular one 52 | return _hw_acc_override; 53 | } 54 | if (!_link_cfg) { 55 | throw std::runtime_error("UioIf link type not set (use setLinkAxi() or setLinkXdma())"); 56 | } 57 | // Create a hardware accessor for the actual UioConfig. 58 | return _link_cfg->hw_acc(*this); 59 | } 60 | 61 | /** @brief gets a number of UIO device based on the name 62 | * 63 | * i.e. searches for the uio instance where "/sys/class/uio/uioX/name" 64 | * matches `name` 65 | */ 66 | int UioConfigUio::_get_uio_number(std::string_view name) { 67 | std::string path = "/sys/class/uio/"; 68 | for (const auto& entry : std::filesystem::directory_iterator(path)) { 69 | std::ifstream ifs{entry.path() / "name"}; 70 | 71 | if (!ifs) { 72 | continue; 73 | } 74 | 75 | std::string cur_name; 76 | ifs >> cur_name; 77 | if (cur_name == name) { 78 | std::regex r{R"(uio(\d+))"}; 79 | std::cmatch m; 80 | std::regex_match(entry.path().filename().c_str(), m, r); 81 | return std::stoi(m.str(1)); 82 | } 83 | } 84 | return -1; 85 | } 86 | 87 | unsigned long long UioConfigUio::_get_uio_val(const std::string path) { 88 | std::ifstream ifs{path}; 89 | if (!ifs) { 90 | throw std::runtime_error("could not find " + path); 91 | } 92 | std::string size_str; 93 | ifs >> size_str; 94 | return std::stoull(size_str, nullptr, 0); 95 | } 96 | 97 | /** @brief gets a region for an uio map */ 98 | UioRegion UioConfigUio::_get_map_region(int uio_number, int map_index) { 99 | const std::string base_path{"/sys/class/uio/uio" + std::to_string(uio_number) + "/maps/map" + 100 | std::to_string(map_index) + "/"}; 101 | 102 | auto region = UioRegion{ 103 | static_cast(_get_uio_val(base_path + "addr")), 104 | static_cast(_get_uio_val(base_path + "size")), 105 | }; 106 | 107 | return region; 108 | } 109 | 110 | size_t UioConfigUio::_get_map_offset(int uio_number, int map_index) { 111 | const std::string base_path{"/sys/class/uio/uio" + std::to_string(uio_number) + "/maps/map" + 112 | std::to_string(map_index) + "/"}; 113 | 114 | auto offset = static_cast(_get_uio_val(base_path + "offset")); 115 | 116 | return offset; 117 | } 118 | 119 | /** @brief gets device info (mem region, mmap offset, ...) from a uio name 120 | * 121 | * In a case where there are more than one memory mappings, an individual memory 122 | * mapping can be selected by appending a colon (":") to the UIO name, followed 123 | * by the mapping index. The UIO names are (in most cases) derived from the IP 124 | * names in Vivado Block Diagram, and those names cannot include a colon. 125 | * Additionally, a colon cannot be used in the device tree identifiers. It is, 126 | * therefore, safe to reserve a colon as a special character. 127 | * 128 | * Example: 129 | * 130 | * Consider the following output of the `lsuio` command: 131 | * 132 | * ``` 133 | * uio8: name=example_clocking, version=devicetree, events=0 134 | * map[0]: addr=0xA0140000, size=65536 135 | * uio7: name=example_app_top, version=devicetree, events=0 136 | * map[0]: addr=0xA0200000, size=1048576 137 | * map[1]: addr=0xA0300000, size=65536 138 | * ``` 139 | * 140 | * - The "example_clocking" UIO can be selected either with `example_clocking` 141 | * or `example_clocking:0`. 142 | * - Similarly, the first memory mapping of "example_app_top" can be selected 143 | * either with `example_app_top` or with `example_app_top:0`. 144 | * - Second memory mapping of "example_app_top" can only be selected with 145 | * `example_app_top:1` 146 | * 147 | */ 148 | HwAccessorPtr UioConfigUio::hw_acc(const UioDeviceLocation& dev_loc) const { 149 | int map_index = 0; 150 | const std::string& dev_name = dev_loc.uio_name; 151 | std::string base_dev_name; // a name without the colon 152 | 153 | size_t colon_pos = dev_name.find(":"); 154 | if (colon_pos != std::string::npos) { 155 | base_dev_name = dev_name.substr(0, colon_pos); 156 | std::string_view idx_substr = dev_name.substr(colon_pos + 1, std::string::npos); 157 | map_index = std::stoi(std::string{idx_substr}); 158 | } else { 159 | base_dev_name = dev_name; 160 | } 161 | 162 | int uio_number = _get_uio_number(base_dev_name); 163 | if (uio_number < 0) { 164 | throw std::runtime_error("could not find a UIO device " + dev_name); 165 | } 166 | return std::make_shared(std::string{"/dev/uio"} + std::to_string(uio_number), 167 | _get_map_region(uio_number, map_index), 168 | static_cast(map_index * getpagesize()), 169 | _get_map_offset(uio_number, map_index)); 170 | } 171 | 172 | UioConfigXdma::UioConfigXdma(std::string xdma_path, uintptr_t pcie_offs, bool x7_series_mode) 173 | : _xdma_path(xdma_path), _pcie_offs(pcie_offs), _x7_series_mode(x7_series_mode) {} 174 | 175 | HwAccessorPtr UioConfigXdma::hw_acc(const UioDeviceLocation& dev_loc) const { 176 | if (_x7_series_mode) { 177 | // Workaround for limited PCIe memory access to certain devices: 178 | // "For 7 series Gen2 IP, PCIe access from the Host system must be limited to 1DW (4 Bytes) 179 | // transaction only." (see Xilinx pg195, page 10) If using direct access to the mmap()ed area (or a 180 | // regular memcpy), the CPU will issue larger transfers and the system will crash 181 | return std::make_shared>( 182 | _xdma_path, 183 | dev_loc.xdma_evt_dev, 184 | UioRegion{dev_loc.xdma_region.addr, dev_loc.xdma_region.size}, 185 | _pcie_offs); 186 | } else { 187 | return std::make_shared>( 188 | _xdma_path, 189 | dev_loc.xdma_evt_dev, 190 | UioRegion{dev_loc.xdma_region.addr, dev_loc.xdma_region.size}, 191 | _pcie_offs); 192 | } 193 | } 194 | 195 | } // namespace udmaio 196 | -------------------------------------------------------------------------------- /src/UioMemSgdma.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2021 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/UioMemSgdma.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "udmaio/DmaBufferAbstract.hpp" 23 | #include 24 | 25 | namespace std { 26 | // To dump e.g. buffer indices 27 | // To look a operator up, it has to live in the same namespace as the type it is supposed to stream 28 | // We want to stream std::vector so we have to put it into std namespace :-/ 29 | ostream& operator<<(ostream& os, const vector& vec) { 30 | os << '['; 31 | if (!vec.empty()) { 32 | copy(vec.begin(), vec.end() - 1, ostream_iterator(os, ", ")); 33 | os << vec.back(); 34 | } 35 | os << ']'; 36 | return os; 37 | } 38 | }; // namespace std 39 | 40 | namespace udmaio { 41 | 42 | void UioMemSgdma::write_cyc_mode(const std::vector& dst_bufs) { 43 | _nr_cyc_desc = dst_bufs.size(); 44 | _next_readable_buf = 0; 45 | size_t i = 0; 46 | 47 | for (auto dst_buf : dst_bufs) { 48 | BOOST_LOG_SEV(_lg, bls::trace) 49 | << "dest buf addr = 0x" << std::hex << dst_buf.addr << std::dec; 50 | 51 | uintptr_t nxtdesc = get_first_desc_addr() + ((i + 1) % _nr_cyc_desc) * DESC_ADDR_STEP; 52 | 53 | S2mmDesc desc{ 54 | .nxtdesc = nxtdesc, 55 | .buffer_addr = dst_buf.addr, 56 | .control = 57 | S2mmDescControl{ 58 | .buffer_len = static_cast(dst_buf.size), 59 | .rxeof = 0, 60 | .rxsof = 0, 61 | .rsvd = 0, 62 | }, 63 | .status = {0}, 64 | .app = {0}, 65 | }; 66 | descriptors[i++].wr(desc); 67 | } 68 | } 69 | 70 | void UioMemSgdma::init_buffers(std::shared_ptr mem, 71 | size_t num_buffers, 72 | size_t buf_size) { 73 | _mem = mem; 74 | 75 | // FIXME: enforce alignment constraints? 76 | std::vector dst_bufs; 77 | _buf_addrs.clear(); 78 | for (size_t i = 0; i < num_buffers; i++) { 79 | uint64_t addr = mem->get_phys_region().addr + i * buf_size; 80 | dst_bufs.push_back({ 81 | .addr = addr, 82 | .size = buf_size, 83 | }); 84 | _buf_addrs.push_back(addr); 85 | }; 86 | 87 | write_cyc_mode(dst_bufs); 88 | _buf_size = buf_size; 89 | } 90 | 91 | void UioMemSgdma::print_desc(const S2mmDesc& desc) const { 92 | auto fmt_flag = [](std::string name, bool val) { return (val ? "+" : "-") + name; }; 93 | #define BLI BOOST_LOG_SEV(_lg, bls::info) << "" 94 | BLI << "next_desc: 0x" << std::hex << desc.nxtdesc << ", " << "buff_addr: 0x" << std::hex 95 | << desc.buffer_addr; 96 | BLI << "ctrl: buf_len " << std::dec << desc.control.buffer_len << ", " 97 | << fmt_flag("sof", desc.control.rxsof) << " " << fmt_flag("eof", desc.control.rxeof) << " "; 98 | BLI << "status: num_bytes " << std::dec << desc.status.num_stored_bytes << ", " 99 | << fmt_flag("sof", desc.status.rxsof) << " " << fmt_flag("eof", desc.status.rxeof) << " " 100 | << fmt_flag("interr", desc.status.dmainterr) << " " 101 | << fmt_flag("slverr", desc.status.dmaslverr) << " " 102 | << fmt_flag("decerr", desc.status.dmadecerr) << " " << fmt_flag("cmplt", desc.status.cmplt); 103 | } 104 | 105 | void UioMemSgdma::print_descs() const { 106 | for (size_t i = 0; i < _nr_cyc_desc; i++) { 107 | BLI << "Reading desc " << i << "/" << _nr_cyc_desc - 1 << " from offset 0x" << std::hex 108 | << i * DESC_ADDR_STEP << std::dec; 109 | print_desc(descriptors[i].rd()); 110 | } 111 | } 112 | 113 | uintptr_t UioMemSgdma::get_first_desc_addr() const { 114 | return _hw->get_phys_region().addr; 115 | } 116 | 117 | std::ostream& operator<<(std::ostream& os, const UioRegion& buf_info) { 118 | os << "UioRegion{0x" << std::hex << buf_info.addr << ", 0x" << buf_info.size << std::dec << "}"; 119 | return os; 120 | } 121 | 122 | std::vector UioMemSgdma::get_full_buffers() { 123 | std::vector result; 124 | 125 | for (size_t i = 0; i < _nr_cyc_desc; i++) { 126 | auto stat = desc_statuses[_next_readable_buf].rd(); 127 | if (!stat.cmplt) { 128 | break; 129 | } 130 | 131 | if (stat.num_stored_bytes == 0) { 132 | throw std::runtime_error("Descriptor #" + std::to_string(_next_readable_buf) + 133 | " yields zero buffer length"); 134 | } 135 | 136 | result.push_back(_next_readable_buf); 137 | 138 | _next_readable_buf++; 139 | _next_readable_buf %= _nr_cyc_desc; 140 | } 141 | 142 | if (!result.empty()) { 143 | BOOST_LOG_SEV(_lg, bls::trace) << "get_full_buffers() result: " << result; 144 | } 145 | return result; 146 | } 147 | 148 | std::vector UioMemSgdma::get_next_packet() { 149 | std::vector result; 150 | 151 | bool has_sof = false; 152 | size_t n = _next_readable_buf; 153 | 154 | for (size_t i = 0; i < _nr_cyc_desc; i++) { 155 | auto stat = desc_statuses[n].rd(); 156 | if (!stat.cmplt) { 157 | break; 158 | } 159 | 160 | if (stat.num_stored_bytes == 0) { 161 | throw std::runtime_error("Descriptor #" + std::to_string(n) + 162 | " yields zero buffer length"); 163 | } 164 | if (!has_sof && !stat.rxsof) { 165 | BOOST_LOG_SEV(_lg, bls::warning) << "buffer #" << n << " has no SOF, skipping"; 166 | n++; 167 | n %= _nr_cyc_desc; 168 | _next_readable_buf = n; 169 | continue; 170 | } 171 | if (stat.rxsof) { 172 | if (!has_sof) { 173 | has_sof = true; 174 | } else { 175 | BOOST_LOG_SEV(_lg, bls::warning) << "buffer #" << n << ": duplicate SOF"; 176 | } 177 | } 178 | result.push_back(n); 179 | n++; 180 | n %= _nr_cyc_desc; 181 | if (stat.rxeof) { 182 | _next_readable_buf = n; 183 | BOOST_LOG_SEV(_lg, bls::trace) << "get_next_packet() result: " << result; 184 | return result; 185 | } 186 | } 187 | 188 | return {}; 189 | } 190 | 191 | std::vector UioMemSgdma::read_buffers(const std::vector indices) { 192 | // Read statuses for all buffers 193 | std::vector stats{indices.size()}; 194 | size_t result_size = 0; 195 | for (size_t i = 0; i < indices.size(); i++) { 196 | stats[i] = desc_statuses[indices[i]].rd(); 197 | result_size += stats[i].num_stored_bytes; 198 | } 199 | 200 | std::vector result; 201 | // Reserve enough space in advance to avoid re-allocation / copying 202 | result.reserve(result_size); 203 | for (size_t i = 0; i < indices.size(); i++) { 204 | auto region = UioRegion{_buf_addrs[indices[i]], stats[i].num_stored_bytes}; 205 | BOOST_LOG_SEV(_lg, bls::trace) << "save buf #" << indices[i] << " (" << region.size 206 | << " bytes) @ 0x" << std::hex << region.addr; 207 | _mem->append_from_buf(region, result); 208 | // Clear complete flag after having read the data 209 | stats[i].cmplt = 0; 210 | desc_statuses[indices[i]].wr(stats[i]); 211 | } 212 | return result; 213 | } 214 | 215 | } // namespace udmaio 216 | -------------------------------------------------------------------------------- /src/UioAxiVdmaIf.cpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2023 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #include "udmaio/UioAxiVdmaIf.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "udmaio/Logging.hpp" 19 | 20 | namespace udmaio { 21 | 22 | //////////////////////////////////////// 23 | // Helper functions 24 | //////////////////////////////////////// 25 | 26 | void UioAxiVdmaIf::init_buffers(const std::vector& frm_bufs) { 27 | for (size_t i = 0; i < frm_bufs.size(); i++) { 28 | BOOST_LOG_SEV(_lg, bls::trace) 29 | << "frame buf (" << i << ") addr = 0x" << std::hex << frm_bufs[i].addr << std::dec; 30 | 31 | if (_long_addrs) { 32 | s2mm_sa[i * 2].wr(frm_bufs[i].addr >> 32); 33 | s2mm_sa[i * 2 + 1].wr(frm_bufs[i].addr & 0xffffffff); 34 | } else { 35 | s2mm_sa[i].wr(frm_bufs[i].addr); 36 | } 37 | } 38 | } 39 | 40 | void UioAxiVdmaIf::set_frame_format(FrameFormat frm_format) { 41 | // Write stride (needed?) 42 | s2mm_frmdly_stride.wr({.stride = frm_format.get_hsize()}); 43 | 44 | // Write horizontal size 45 | s2mm_hsize.wr({.horizontal_size = frm_format.get_hsize()}); 46 | 47 | // Write vertical size 48 | s2mm_vsize.wr({.vertical_size = frm_format.get_dim().height}); 49 | } 50 | 51 | size_t UioAxiVdmaIf::autodetect_num_frmbufs() 52 | { 53 | // There is some weird banking logic going on when using more than 16 frame buffers. 54 | // We're just considering the first 16 ones for now 55 | constexpr size_t num_addrs = 16; 56 | // Just write and readback a pattern to check if the frame buffer can be used 57 | constexpr uint32_t patterns[] = { 0x55555555, 0xaaaaaaaa }; 58 | 59 | size_t i; 60 | for (i = 0; i < num_addrs; i++) { 61 | for (size_t k = 0; k < sizeof(patterns)/sizeof(patterns[0]); k++) { 62 | bool vfy = false; 63 | if (_long_addrs) { 64 | s2mm_sa[i * 2].wr(patterns[k]); 65 | s2mm_sa[i * 2 + 1].wr(patterns[k]); 66 | vfy = s2mm_sa[i * 2].rd() == patterns[k] && s2mm_sa[i * 2 + 1].rd() == patterns[k]; 67 | } else { 68 | s2mm_sa[i].wr(patterns[k]); 69 | vfy = s2mm_sa[i].rd() == patterns[k]; 70 | } 71 | if (!vfy) { 72 | return i; 73 | } 74 | } 75 | } 76 | return i; 77 | } 78 | 79 | //////////////////////////////////////// 80 | // Base config 81 | //////////////////////////////////////// 82 | 83 | void UioAxiVdmaIf::config(const std::vector& frm_bufs) { 84 | // 1. Write to VDMA control register, except run bit 85 | auto cr = s2mm_vdmacr.rd(); 86 | cr.circular_park = 1; 87 | cr.repeat_en = 1; 88 | s2mm_vdmacr.wr(cr); 89 | 90 | // 2. Write to VDMA frame buffer start address register 91 | init_buffers(frm_bufs); 92 | } 93 | 94 | void UioAxiVdmaIf::config(const std::vector& frm_bufs, FrameFormat frm_format) { 95 | // check frame buffer sizes 96 | for (auto frm_buf : frm_bufs) { 97 | if (frm_buf.size < frm_format.get_frm_size()) { 98 | BOOST_LOG_SEV(_lg, bls::error) 99 | << "frame buf size insufficient (got " << frm_buf.size << ", need " 100 | << frm_format.get_frm_size() << ")" << std::dec; 101 | } 102 | } 103 | 104 | config(frm_bufs); 105 | 106 | set_frame_format(frm_format); 107 | } 108 | 109 | void UioAxiVdmaIf::reset() { 110 | // Reset all errors in s2mm channel 111 | s2mm_vdmasr.wr({.vdma_int_err = 1, 112 | .vdma_slv_err = 1, 113 | .vdma_dec_err = 1, 114 | .sof_early_err = 1, 115 | .eol_early_err = 1, 116 | .sof_late_err = 1, 117 | .eol_late_err = 1}); 118 | 119 | // Soft reset s2mm channel 120 | s2mm_vdmacr.wr({.reset = 1}); 121 | } 122 | 123 | void UioAxiVdmaIf::print_regs() { 124 | BOOST_LOG_SEV(_lg, bls::debug) 125 | << "Park Pointer Register = 0x" << std::hex << reg_to_raw(park_ptr_reg.rd()) << std::dec; 126 | BOOST_LOG_SEV(_lg, bls::debug) << "AXI VDMA Version Register = 0x" << std::hex 127 | << reg_to_raw(vdma_version.rd()) << std::dec; 128 | BOOST_LOG_SEV(_lg, bls::debug) << "S2MM VDMA Control Register = 0x" << std::hex 129 | << reg_to_raw(s2mm_vdmacr.rd()) << std::dec; 130 | BOOST_LOG_SEV(_lg, bls::debug) 131 | << "S2MM VDMA Status Register = 0x" << std::hex << reg_to_raw(s2mm_vdmasr.rd()) << std::dec; 132 | BOOST_LOG_SEV(_lg, bls::debug) << "S2MM Error Interrupt Mask Register = 0x" << std::hex 133 | << reg_to_raw(s2mm_vdma_irq_mask.rd()) << std::dec; 134 | BOOST_LOG_SEV(_lg, bls::debug) 135 | << "S2MM Register Index = 0x" << std::hex << reg_to_raw(s2mm_reg_index.rd()) << std::dec; 136 | BOOST_LOG_SEV(_lg, bls::debug) 137 | << "S2MM Vertical Size = 0x" << std::hex << reg_to_raw(s2mm_vsize.rd()) << std::dec; 138 | BOOST_LOG_SEV(_lg, bls::debug) 139 | << "S2MM Horizontal Size = 0x" << std::hex << reg_to_raw(s2mm_hsize.rd()) << std::dec; 140 | BOOST_LOG_SEV(_lg, bls::debug) << "S2MM Frame Delay and Stride = 0x" << std::hex 141 | << reg_to_raw(s2mm_frmdly_stride.rd()) << std::dec; 142 | 143 | for (size_t i = 0; i < s2mm_sa.size(); i++) 144 | BOOST_LOG_SEV(_lg, bls::debug) << "S2MM Start Address Register " << i << " = 0x" << std::hex 145 | << reg_to_raw(s2mm_sa[i].rd()) << std::dec; 146 | } 147 | 148 | uint32_t UioAxiVdmaIf::get_cur_frmbuf() { 149 | return park_ptr_reg.rd().wr_frm_store; 150 | } 151 | 152 | void UioAxiVdmaIf::print_cur_frmbuf() { 153 | BOOST_LOG_SEV(_lg, bls::debug) << "Current frame buffer = " << get_cur_frmbuf(); 154 | } 155 | 156 | //////////////////////////////////////// 157 | // Operational functions 158 | //////////////////////////////////////// 159 | 160 | void UioAxiVdmaIf::start() { 161 | // Set run bit of s2mm channel to start operation 162 | auto cr = s2mm_vdmacr.rd(); 163 | cr.rs = 1; 164 | s2mm_vdmacr.wr(cr); 165 | } 166 | 167 | void UioAxiVdmaIf::start(FrameFormat frm_format) { 168 | set_frame_format(frm_format); 169 | 170 | start(); 171 | } 172 | 173 | void UioAxiVdmaIf::stop(bool wait_for_end = false) { 174 | // Reset run bit of s2mm channel to start operation 175 | auto cr = s2mm_vdmacr.rd(); 176 | cr.rs = 0; 177 | s2mm_vdmacr.wr(cr); 178 | 179 | if (wait_for_end) { 180 | while (!s2mm_vdmasr.rd().halted) 181 | ; 182 | } 183 | } 184 | 185 | bool UioAxiVdmaIf::isrunning() { 186 | return !s2mm_vdmasr.rd().halted; 187 | } 188 | 189 | bool UioAxiVdmaIf::check_for_errors() { 190 | bool has_errors = false; 191 | 192 | auto sr = s2mm_vdmasr.rd(); 193 | 194 | if (sr.vdma_int_err) { 195 | has_errors = true; 196 | BOOST_LOG_SEV(_lg, bls::fatal) << "VDMA Internal Error, reset required!"; 197 | } 198 | 199 | if (sr.vdma_slv_err) { 200 | has_errors = true; 201 | BOOST_LOG_SEV(_lg, bls::fatal) << "VDMA Slave Error"; 202 | } 203 | 204 | if (sr.vdma_dec_err) { 205 | has_errors = true; 206 | BOOST_LOG_SEV(_lg, bls::fatal) << "VDMA Decode Error"; 207 | } 208 | 209 | if (sr.sof_early_err) { 210 | has_errors = true; 211 | BOOST_LOG_SEV(_lg, bls::fatal) << "Start of Frame Early Error"; 212 | } 213 | 214 | if (sr.eol_early_err) { 215 | has_errors = true; 216 | BOOST_LOG_SEV(_lg, bls::fatal) << "End of Line Early Error"; 217 | } 218 | 219 | if (sr.sof_late_err) { 220 | has_errors = true; 221 | BOOST_LOG_SEV(_lg, bls::fatal) << "Start of Frame Late Error"; 222 | } 223 | 224 | if (sr.eol_late_err) { 225 | has_errors = true; 226 | BOOST_LOG_SEV(_lg, bls::fatal) << "End of Line Late Error"; 227 | } 228 | 229 | return has_errors; 230 | } 231 | 232 | } // namespace udmaio 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Userspace DMA I/O 2 | 3 | This library should simplify the use of the [Xilinx AXI DMA 4 | controller](https://www.xilinx.com/products/intellectual-property/axi_dma.html) 5 | when used in S2MM (Stream to Memory-Mapped) mode. 6 | 7 | A typical use case would be a DAQ (Data Acquisition) system, where ADC 8 | is generating data and the DMA is responsible to store the data in the memory. 9 | 10 | A configuration required by this library is presented in the block diagram below. 11 | 12 | ![DMA components](./docs/dma_mgmt_components.png) 13 | 14 | ## Different operation modes 15 | 16 | ### Access interface 17 | 18 | This library supports two different access modes: 19 | 20 | * over PCIe with [xdma](https://github.com/Xilinx/dma_ip_drivers/tree/master/XDMA) driver 21 | * from ARM over the [Userspace I/O](https://www.kernel.org/doc/html/v5.4/driver-api/uio-howto.html) 22 | 23 | Both methods are somehow similar, but are also different in a couple of details. 24 | 25 | #### Memory mapping 26 | 27 | `xdma` provides only one memory mapping for the entire register space, while 28 | `uio` provides a separate file for each IP. 29 | 30 | Consequently, an address of the IP needs to be provided to the `xdma` handler, 31 | as this information is not made available. 32 | 33 | #### Interrupts 34 | 35 | Interrupts can be associated with an individual `uio` files, but not with the 36 | `xdma` memory mapped file; `xdma` instead provides separate files (with the name 37 | `eventN`). 38 | 39 | ### Data memory access 40 | 41 | There are also two way to provide a memory for the DMA data, i.e. the 42 | memory where the DMA is writing to: 43 | 44 | * [u-dma-buf](https://github.com/ikwzm/udmabuf), which provides a method to 45 | allocate a chunk of memory from the kernel 46 | * FPGA memory, where the memory is connected directly to the FPGA (e.g. DDR4 47 | memory connected directly to Programmable Logic (PL) on DAMC-FMC2ZUP) 48 | 49 | ## Python binding 50 | 51 | Please keep in mind that on some systems (e.g. DAMC-FMC2ZUP and DAMC-FMC1Z7IO) 52 | the `libudmaio` and the `pyudmaio` Python binding can be already installed. The 53 | steps descibed in the chapter below are only needed for the development of the 54 | library itself. 55 | 56 | ### Installation 57 | 58 | The Python binding `pyudmaio` uses `setuptools` to build/install and requires 59 | [pip 21 or 60 | newer](https://pybind11.readthedocs.io/en/stable/compiling.html?highlight=setuptools#setup-helpers-pep518). 61 | 62 | It also assumes the `libudmaio` header files and shared library to be installed 63 | system-wide. Make sure to `make install` the C++ library before trying to 64 | install the Python binding. 65 | 66 | To install the Python library, in-tree build must be used: 67 | 68 | ``` 69 | pip install --use-feature=in-tree-build . 70 | ``` 71 | 72 | or (to install the library in [editable mode](https://pip.pypa.io/en/latest/cli/pip_install/#editable-installs): 73 | 74 | ``` 75 | pip3 install --use-feature=in-tree-build -e . 76 | ``` 77 | 78 | ### `.pyi` update 79 | 80 | To update the `.pyi` stubs after changing the binding: 81 | 82 | ```bash 83 | # (re-)generate docstrings 84 | python3 -m pybind11_mkdoc -I ./inc -o pyudmaio/src/docstrings.hpp \ 85 | inc/udmaio/UioConfig.hpp \ 86 | inc/udmaio/UioIf.hpp \ 87 | inc/udmaio/DmaBufferAbstract.hpp \ 88 | inc/udmaio/FpgaMemBufferOverAxi.hpp \ 89 | inc/udmaio/FpgaMemBufferOverXdma.hpp \ 90 | inc/udmaio/UDmaBuf.hpp \ 91 | inc/udmaio/UioAxiDmaIf.hpp \ 92 | inc/udmaio/UioMemSgdma.hpp \ 93 | pyudmaio/src/DataHandlerPython.hpp \ 94 | inc/udmaio/FrameFormat.hpp 95 | 96 | # build & install binding 97 | pip3 install ./pyudmaio 98 | 99 | # update stubs 100 | python3 -m pybind11_stubgen pyudmaio -o pyudmaio 101 | ``` 102 | 103 | ## Usage example 104 | 105 | Available in the folder `example` is a small example, which demonstrates how 106 | to use the components of this library. This example assumes that the 107 | [AXI Traffic Generator](https://www.xilinx.com/products/intellectual-property/axi_tg.html) 108 | is connected to the AXI4-Stream input of the DMA; this Traffic Generator IP 109 | produces a LFSR pattern which is then compared in the interrupt handler. 110 | 111 | ### udev rules 112 | 113 | Install TechLab's xdma metapackage (either as Debian package `xdma-dkms` from [doocs.desy.de](https://doocs.desy.de/pub/doocs/dists/) or [from source](https://github.com/MicroTCA-Tech-Lab/xdma-metapackage)) to make sure udev rules are installed to match the device file paths expected by the library. 114 | 115 | ### UIO - with default parameters 116 | 117 | ``` 118 | # ./axi_dma_demo --mode uio --debug 119 | [2069-12-16 19:52:38.443477] [0x0000007fbce25010] [debug] : uio name = /dev/uio5 120 | [2069-12-16 19:52:38.444203] [0x0000007fbce25010] [debug] : uio name = /dev/uio4 121 | [2069-12-16 19:52:38.444769] [0x0000007fbce25010] [debug] : uio name = /dev/uio6 122 | [2069-12-16 19:52:38.444988] [0x0000007fbce25010] [debug] UDmaBuf: size = 134217728 123 | [2069-12-16 19:52:38.445022] [0x0000007fbce25010] [debug] UDmaBuf: phys addr = 6fd00000 124 | [2069-12-16 19:52:38.445264] [0x0000007fbce25010] [debug] UioAxiDmaIf: start, start_desc = 88840000 125 | [2069-12-16 19:52:38.445651] [0x0000007fb46251c0] [debug] DataHandlerPrint: process data, size = 16384 126 | [2069-12-16 19:52:38.445853] [0x0000007fb46251c0] [debug] DataHandler: stopping thread 127 | Counters: OK = 8192, total = 8192 128 | ``` 129 | 130 | ### UIO - long data acquisition 131 | 132 | When the pause between packets is high enough (and when the packets are small 133 | enough), the library can operate continuously - here an example where 50000 134 | samples were captured. 135 | 136 | ``` 137 | # ./axi_dma_demo --debug --mode uio --nr_pkts 50000 --pkt_pause 60000 138 | [2069-12-16 20:21:36.560101] [0x0000007fae16d010] [debug] : uio name = /dev/uio5 139 | [2069-12-16 20:21:36.560779] [0x0000007fae16d010] [debug] : uio name = /dev/uio4 140 | [2069-12-16 20:21:36.561316] [0x0000007fae16d010] [debug] : uio name = /dev/uio6 141 | [2069-12-16 20:21:36.561505] [0x0000007fae16d010] [debug] UDmaBuf: size = 134217728 142 | [2069-12-16 20:21:36.561529] [0x0000007fae16d010] [debug] UDmaBuf: phys addr = 6fd00000 143 | [2069-12-16 20:21:36.561788] [0x0000007fae16d010] [debug] UioAxiDmaIf: start, start_desc = 88840000 144 | [2069-12-16 20:21:36.562072] [0x0000007fa596d1c0] [debug] DataHandlerPrint: process data, size = 16384 145 | [2069-12-16 20:21:36.562366] [0x0000007fa596d1c0] [debug] DataHandlerPrint: process data, size = 16384 146 | [2069-12-16 20:21:36.562650] [0x0000007fa596d1c0] [debug] DataHandlerPrint: process data, size = 16384 147 | [2069-12-16 20:21:36.562931] [0x0000007fa596d1c0] [debug] DataHandlerPrint: process data, size = 16384 148 | <...> 149 | [2069-12-16 20:21:48.766523] [0x0000007fa596d1c0] [debug] DataHandlerPrint: process data, size = 16384 150 | [2069-12-16 20:21:48.766794] [0x0000007fa596d1c0] [debug] DataHandlerPrint: process data, size = 16384 151 | [2069-12-16 20:22:37.585985] [0x0000007fa596d1c0] [debug] DataHandler: stopping thread 152 | Counters: OK = 409600000, total = 40960000 153 | ``` 154 | 155 | ### XDMA 156 | 157 | ``` 158 | $ ./axi_dma_demo --mode xdma --debug 159 | [2021-03-01 13:49:31.941347] [0x00007f6b7e56c740] [debug] : uio name = /dev/xdma/card0/user 160 | [2021-03-01 13:49:31.941421] [0x00007f6b7e56c740] [debug] : uio name = /dev/xdma/card0/user 161 | [2021-03-01 13:49:31.941451] [0x00007f6b7e56c740] [debug] : uio name = /dev/xdma/card0/user 162 | [2021-03-01 13:49:31.941601] [0x00007f6b7e56c740] [debug] UioAxiDmaIf: start, start_desc = 88920000 163 | [2021-03-01 13:49:31.942309] [0x00007f6b7e568700] [debug] DataHandlerPrint: process data, size = 16384 164 | [2021-03-01 13:49:31.942382] [0x00007f6b7e568700] [debug] DataHandler: stopping thread 165 | Counters: OK = 8192, total = 8192 166 | ``` 167 | 168 | ## Selecting the target hardware 169 | 170 | The demo application includes hardware-related constants such as bus width and IP core addresses. To make it work, the right target hardware must be selected. The demo application currently supports DAMC-FMC2ZUP and DAMC-FMC1Z7IO. 171 | 172 | ### Hardware selection for the C++ demo 173 | 174 | The hardware is selected at build time using a CMake parameter. 175 | ``` 176 | cmake -DTARGET_HW=ZUP 177 | cmake -DTARGET_HW=Z7IO 178 | ``` 179 | 180 | ### Hardware selection for the Python demo 181 | 182 | The hardware is selected at runtime using a command line parameter. 183 | ``` 184 | ./axi_dma_demo.py -H zup 185 | ./axi_dma_demo.py -H z7io 186 | ``` 187 | 188 | # Source code documentation 189 | 190 | ## Local build 191 | 192 | * To create the Doxygen documentation for the library, run `doxygen` in the source tree root. 193 | * To create the Doxygen documentation for the C++ example, run `doxygen` in the `example` directory. 194 | * The Doxygen documentation is created in `docs/libudmaio` and `docs/example`, respectively. 195 | 196 | ## GitHub pages 197 | 198 | The Doxygen source code documentation of the master branch is also available on [GitHub pages](https://microtca-tech-lab.github.io/libudmaio/). 199 | -------------------------------------------------------------------------------- /tests/test_UioMemSgdma.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "udmaio/Logging.hpp" 5 | #include "udmaio/UioConfig.hpp" 6 | #define BOOST_TEST_MODULE UioMemSgdma 7 | 8 | #define BOOST_TEST_DYN_LINK 9 | 10 | #include "udmaio/FpgaMemBufferOverAxi.hpp" 11 | #include "udmaio/HwAccessor.hpp" 12 | #include "udmaio/UioMemSgdma.hpp" 13 | #include 14 | 15 | namespace udmaio { 16 | 17 | struct UioMemSgdmaTest : public Logger { 18 | HwAccessorPtr hw_sgdma; 19 | UioMemSgdma sgdma; 20 | 21 | HwAccessorPtr hw_fpga_mem; 22 | std::shared_ptr fpga_mem; 23 | 24 | UioMemSgdmaTest() 25 | : hw_sgdma{std::make_shared(16 * 1024)} 26 | , sgdma{UioDeviceLocation{hw_sgdma}} 27 | , hw_fpga_mem{std::make_shared(16 * 1024)} 28 | , fpga_mem{std::make_shared(UioDeviceLocation{hw_fpga_mem})} 29 | , descriptors{sgdma.descriptors} 30 | , Logger("UioMemSgdmaTest") { 31 | BOOST_LOG_SEV(_lg, bls::debug) << "UioMemSgdmaTest ctor"; 32 | sgdma.init_buffers(fpga_mem, num_bufs, buf_size); 33 | } 34 | ~UioMemSgdmaTest() {} 35 | 36 | RegAccessorArray& descriptors; 37 | 38 | static constexpr size_t num_bufs = 8, buf_size = 1024; 39 | }; 40 | 41 | BOOST_FIXTURE_TEST_CASE(desc_addr, UioMemSgdmaTest) { 42 | BOOST_CHECK_MESSAGE(sgdma.get_first_desc_addr() == hw_sgdma->get_phys_region().addr, 43 | "Descriptor address mismatch"); 44 | } 45 | 46 | void fill_buffer(HwAccessorPtr& mem, uintptr_t addr, uint32_t start_count, size_t size) { 47 | assert(addr % sizeof(uint32_t) == 0); 48 | assert(size % sizeof(uint32_t) == 0); 49 | 50 | std::cerr << "Filling memory from 0x" << std::hex << addr << " to 0x" << std::hex << addr + size 51 | << std::endl; 52 | 53 | uintptr_t end_addr = addr + size; 54 | uint32_t count = start_count; 55 | while (addr < end_addr) { 56 | mem->_wr32(addr, count++); 57 | addr += sizeof(uint32_t); 58 | } 59 | } 60 | 61 | void check_buffer(std::vector buf, uint32_t start_count = 0) { 62 | uint32_t count = start_count; 63 | BOOST_CHECK_MESSAGE(buf.size() % sizeof(uint32_t) == 0, "Buffer size not 32bit aligned"); 64 | for (size_t i = 0; i < buf.size(); i += sizeof(uint32_t)) { 65 | uint32_t val = *(reinterpret_cast(&buf[i])); 66 | BOOST_CHECK_MESSAGE(val == count, 67 | "Buffer contents mismatch at " << i << ", val = " << val 68 | << " (expected " << count << ")"); 69 | count++; 70 | } 71 | } 72 | 73 | BOOST_FIXTURE_TEST_CASE(empty_buffers, UioMemSgdmaTest) { 74 | auto empty_bufs = sgdma.get_full_buffers(); 75 | BOOST_CHECK_MESSAGE(empty_bufs.size() == 0, "Empty buffers result not empty"); 76 | } 77 | 78 | BOOST_FIXTURE_TEST_CASE(buffer_read, UioMemSgdmaTest) { 79 | // Make a packet filling up the first buffer 80 | fill_buffer(hw_fpga_mem, buf_size * 0, 0, buf_size); 81 | 82 | auto desc = descriptors[0].rd(); 83 | desc.status.cmplt = true; 84 | desc.status.rxsof = true; 85 | desc.status.rxeof = true; 86 | desc.status.num_stored_bytes = buf_size; 87 | descriptors[0].wr(desc); 88 | 89 | auto bufs = sgdma.get_full_buffers(); 90 | BOOST_CHECK_MESSAGE(bufs.size() == 1, "Expected one full buffer, received " << bufs.size()); 91 | BOOST_CHECK_MESSAGE(bufs[0] == 0, "Expected first buffer to be full, received " << bufs[0]); 92 | 93 | std::vector data = sgdma.read_buffers(bufs); 94 | BOOST_CHECK_MESSAGE(data.size() == buf_size, "Buffer size mismatch"); 95 | check_buffer(data); 96 | 97 | bool cmplt_reset = !descriptors[0].rd().status.cmplt; 98 | BOOST_CHECK_MESSAGE(cmplt_reset, "Complete flag is not reset"); 99 | 100 | // Make a packet spanning 1.5 buffers 101 | fill_buffer(hw_fpga_mem, buf_size * 1, 0, buf_size * 3 / 2); 102 | desc = descriptors[1].rd(); 103 | desc.status.cmplt = true; 104 | desc.status.rxsof = true; 105 | desc.status.rxeof = false; 106 | desc.status.num_stored_bytes = buf_size; 107 | descriptors[1].wr(desc); 108 | desc = descriptors[2].rd(); 109 | desc.status.cmplt = true; 110 | desc.status.rxsof = false; 111 | desc.status.rxeof = true; 112 | desc.status.num_stored_bytes = buf_size / 2; 113 | descriptors[2].wr(desc); 114 | 115 | bufs = sgdma.get_full_buffers(); 116 | BOOST_CHECK_MESSAGE((bufs == std::vector{1, 2}), "Buffer indices mismatch"); 117 | 118 | data = sgdma.read_buffers(bufs); 119 | BOOST_CHECK_MESSAGE(data.size() == buf_size * 3 / 2, "Data size mismatch"); 120 | check_buffer(data); 121 | 122 | cmplt_reset = !descriptors[1].rd().status.cmplt && !descriptors[2].rd().status.cmplt; 123 | BOOST_CHECK_MESSAGE(cmplt_reset, "Complete flags are not reset"); 124 | 125 | // Make two packets spanning 4 buffers 126 | fill_buffer(hw_fpga_mem, buf_size * 3, 0, buf_size * 3 / 2); 127 | desc = descriptors[3].rd(); 128 | desc.status.cmplt = true; 129 | desc.status.rxsof = true; 130 | desc.status.rxeof = false; 131 | desc.status.num_stored_bytes = buf_size; 132 | descriptors[3].wr(desc); 133 | desc = descriptors[4].rd(); 134 | desc.status.cmplt = true; 135 | desc.status.rxsof = false; 136 | desc.status.rxeof = true; 137 | desc.status.num_stored_bytes = buf_size / 2; 138 | descriptors[4].wr(desc); 139 | 140 | fill_buffer(hw_fpga_mem, buf_size * 5, 384, buf_size * 2); 141 | desc = descriptors[5].rd(); 142 | desc.status.cmplt = true; 143 | desc.status.rxsof = true; 144 | desc.status.rxeof = false; 145 | desc.status.num_stored_bytes = buf_size; 146 | descriptors[5].wr(desc); 147 | desc = descriptors[6].rd(); 148 | desc.status.cmplt = true; 149 | desc.status.rxsof = false; 150 | desc.status.rxeof = true; 151 | desc.status.num_stored_bytes = buf_size; 152 | descriptors[6].wr(desc); 153 | 154 | bufs = sgdma.get_full_buffers(); 155 | BOOST_CHECK_MESSAGE((bufs == std::vector{3, 4, 5, 6}), "Buffer indices mismatch"); 156 | 157 | data = sgdma.read_buffers(bufs); 158 | BOOST_CHECK_MESSAGE(data.size() == buf_size * 7 / 2, "Data size mismatch"); 159 | check_buffer(data); 160 | cmplt_reset = !descriptors[3].rd().status.cmplt && !descriptors[4].rd().status.cmplt && 161 | !descriptors[5].rd().status.cmplt && !descriptors[6].rd().status.cmplt; 162 | BOOST_CHECK_MESSAGE(cmplt_reset, "Complete flags are not reset"); 163 | } 164 | 165 | BOOST_FIXTURE_TEST_CASE(packets_read, UioMemSgdmaTest) { 166 | // Make a packet filling up the first buffer 167 | fill_buffer(hw_fpga_mem, buf_size * 0, 0, buf_size); 168 | 169 | auto desc = descriptors[0].rd(); 170 | desc.status.cmplt = true; 171 | desc.status.rxsof = true; 172 | desc.status.rxeof = true; 173 | desc.status.num_stored_bytes = buf_size; 174 | descriptors[0].wr(desc); 175 | 176 | auto bufs = sgdma.get_next_packet(); 177 | BOOST_CHECK_MESSAGE(bufs.size() == 1, "Expected one full buffer, received " << bufs.size()); 178 | BOOST_CHECK_MESSAGE(bufs[0] == 0, "Expected first buffer to be full, received " << bufs[0]); 179 | 180 | std::vector data = sgdma.read_buffers(bufs); 181 | BOOST_CHECK_MESSAGE(data.size() == buf_size, "Buffer size mismatch"); 182 | check_buffer(data); 183 | 184 | bool cmplt_reset = !descriptors[0].rd().status.cmplt; 185 | BOOST_CHECK_MESSAGE(cmplt_reset, "Complete flag is not reset"); 186 | 187 | // Make a packet spanning 1.5 buffers 188 | fill_buffer(hw_fpga_mem, buf_size * 1, 0, buf_size * 3 / 2); 189 | desc = descriptors[1].rd(); 190 | desc.status.cmplt = true; 191 | desc.status.rxsof = true; 192 | desc.status.rxeof = false; 193 | desc.status.num_stored_bytes = buf_size; 194 | descriptors[1].wr(desc); 195 | desc = descriptors[2].rd(); 196 | desc.status.cmplt = true; 197 | desc.status.rxsof = false; 198 | desc.status.rxeof = true; 199 | desc.status.num_stored_bytes = buf_size / 2; 200 | descriptors[2].wr(desc); 201 | 202 | bufs = sgdma.get_next_packet(); 203 | BOOST_CHECK_MESSAGE((bufs == std::vector{1, 2}), "Buffer indices mismatch"); 204 | 205 | data = sgdma.read_buffers(bufs); 206 | BOOST_CHECK_MESSAGE(data.size() == buf_size * 3 / 2, "Data size mismatch"); 207 | check_buffer(data); 208 | 209 | cmplt_reset = !descriptors[1].rd().status.cmplt && !descriptors[2].rd().status.cmplt; 210 | BOOST_CHECK_MESSAGE(cmplt_reset, "Complete flags are not reset"); 211 | 212 | // Make two packets spanning 4 buffers 213 | fill_buffer(hw_fpga_mem, buf_size * 3, 1234, buf_size * 3 / 2); 214 | desc = descriptors[3].rd(); 215 | desc.status.cmplt = true; 216 | desc.status.rxsof = true; 217 | desc.status.rxeof = false; 218 | desc.status.num_stored_bytes = buf_size; 219 | descriptors[3].wr(desc); 220 | 221 | // Try to read a packet; ensure it doesn't return anything yet w/o EOF 222 | bufs = sgdma.get_next_packet(); 223 | BOOST_CHECK_MESSAGE(bufs.empty(), "Packet is not full yet"); 224 | 225 | desc = descriptors[4].rd(); 226 | desc.status.cmplt = true; 227 | desc.status.rxsof = false; 228 | desc.status.rxeof = true; 229 | desc.status.num_stored_bytes = buf_size / 2; 230 | descriptors[4].wr(desc); 231 | 232 | fill_buffer(hw_fpga_mem, buf_size * 5, 5678, buf_size * 2); 233 | desc = descriptors[5].rd(); 234 | desc.status.cmplt = true; 235 | desc.status.rxsof = true; 236 | desc.status.rxeof = false; 237 | desc.status.num_stored_bytes = buf_size; 238 | descriptors[5].wr(desc); 239 | desc = descriptors[6].rd(); 240 | desc.status.cmplt = true; 241 | desc.status.rxsof = false; 242 | desc.status.rxeof = true; 243 | desc.status.num_stored_bytes = buf_size; 244 | descriptors[6].wr(desc); 245 | 246 | bufs = sgdma.get_next_packet(); 247 | BOOST_CHECK_MESSAGE((bufs == std::vector{3, 4}), "Buffer indices mismatch"); 248 | data = sgdma.read_buffers(bufs); 249 | BOOST_CHECK_MESSAGE(data.size() == buf_size * 3 / 2, "Data size mismatch"); 250 | check_buffer(data, 1234); 251 | cmplt_reset = !descriptors[3].rd().status.cmplt && !descriptors[4].rd().status.cmplt; 252 | BOOST_CHECK_MESSAGE(cmplt_reset, "Complete flags are not reset"); 253 | 254 | bufs = sgdma.get_next_packet(); 255 | BOOST_CHECK_MESSAGE((bufs == std::vector{5, 6}), "Buffer indices mismatch"); 256 | data = sgdma.read_buffers(bufs); 257 | BOOST_CHECK_MESSAGE(data.size() == buf_size * 2, "Data size mismatch"); 258 | check_buffer(data, 5678); 259 | cmplt_reset = !descriptors[3].rd().status.cmplt && !descriptors[4].rd().status.cmplt; 260 | BOOST_CHECK_MESSAGE(cmplt_reset, "Complete flags are not reset"); 261 | } 262 | } // namespace udmaio 263 | -------------------------------------------------------------------------------- /inc/udmaio/FrameFormat.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2023 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace udmaio { 20 | 21 | class FrameFormat { 22 | public: 23 | /// Frame dimensions 24 | struct dim_t { 25 | uint16_t width; 26 | uint16_t height; 27 | dim_t(uint16_t w, uint16_t h) : width(w), height(h) {} 28 | }; 29 | 30 | // clang-format off 31 | enum class PixelFormat : uint32_t { 32 | unknown = 0, 33 | // Mono1p = 0x01010037, // 1-bit monochrome, unsigned, 8 pixels packed in one byte. 34 | // Mono2p = 0x01020038, // 2-bit monochrome, unsigned, 4 pixels packed in one byte. 35 | // Mono4p = 0x01040039, // 4-bit monochrome, unsigned, 2 pixels packed in one byte. 36 | Mono8 = 0x01080001, // 8-bit monochrome, unsigned, unpacked format. 37 | // Mono8s = 0x01080002, // 8-bit monochrome, signed, unpacked format. 38 | Mono10 = 0x01100003, // 10-bit monochrome, unsigned, unpacked format. 39 | // Mono10Packed1 = 0x010C0004, // 10-bit monochrome, unsigned, GigE Vision-specific packed format. 40 | Mono12 = 0x01100005, // 12-bit monochrome, unsigned, unpacked format. 41 | // Mono12Packed1 = 0x010C0006, // 12-bit monochrome, unsigned, GigE Vision-specific packed format. 42 | Mono14 = 0x01100025, // 14-bit monochrome, unsigned, unpacked format. 43 | Mono16 = 0x01100007, // 16-bit monochrome, unsigned, unpacked format. 44 | BayerGR8 = 0x01080008, // 8-bit GRBG (green-red-blue-green) Bayer pattern, unsigned, unpacked format. 45 | BayerRG8 = 0x01080009, // 8-bit RGGB (red-green-green-blue) Bayer pattern, unsigned, unpacked format. 46 | BayerGB8 = 0x0108000A, // 8-bit GBRG (green-blue-red-green) Bayer pattern, unsigned, unpacked format. 47 | BayerBG8 = 0x0108000B, // 8-bit BGGR (blue-green-green-red) Bayer pattern, unsigned, unpacked format. 48 | BayerGR10 = 0x0110000C, // 10-bit GRBG (green-red-blue-green) Bayer pattern, unsigned, unpacked format. 49 | BayerRG10 = 0x0110000D, // 10-bit RGGB (red-green-green-blue) Bayer pattern, unsigned, unpacked format. 50 | BayerGB10 = 0x0110000E, // 10-bit GBRG (green-blue-red-green) Bayer pattern, unsigned, unpacked format. 51 | BayerBG10 = 0x0110000F, // 10-bit BGGR (blue-green-green-red) Bayer pattern, unsigned, unpacked format. 52 | BayerGR12 = 0x01100010, // 12-bit GRBG (green-red-blue-green) Bayer pattern, unsigned, unpacked format. 53 | BayerRG12 = 0x01100011, // 12-bit RGGB (red-green-green-blue) Bayer pattern, unsigned, unpacked format. 54 | BayerGB12 = 0x01100012, // 12-bit GBRG (green-blue-red-green) Bayer pattern, unsigned, unpacked format. 55 | BayerBG12 = 0x01100013, // 12-bit BGGR (blue-green-green-red) Bayer pattern, unsigned, unpacked format. 56 | // BayerGR10Packed1 = 0x010C0026, // 10-bit GRBG (green-red-blue-green) Bayer pattern, unsigned, GigE Vision-specific packed format. 57 | // BayerRG10Packed1 = 0x010C0027, // 10-bit RGGB (red-green-green-blue) Bayer pattern, unsigned, GigE Vision-specific packed format. 58 | // BayerGB10Packed1 = 0x010C0028, // 10-bit GBRG (green-blue-red-green) Bayer pattern, unsigned, GigE Vision-specific packed format. 59 | // BayerBG10Packed1 = 0x010C0029, // 10-bit BGGR (blue-green-green-red) Bayer pattern, unsigned, GigE Vision-specific packed format. 60 | // BayerGR12Packed1 = 0x010C002A, // 12-bit GRBG (green-red-blue-green) Bayer pattern, unsigned, GigE Vision-specific packed format. 61 | // BayerRG12Packed = 0x010C002B, // 12-bit RGGB (red-green-green-blue) Bayer pattern, unsigned, GigE Vision-specific packed format. 62 | // BayerGB12Packed1 = 0x010C002C, // 12-bit GBRG (green-blue-red-green) Bayer pattern, unsigned, GigE Vision-specific packed format. 63 | // BayerBG12Packed1 = 0x010C002D, // 12-bit BGGR (blue-green-green-red) Bayer pattern, unsigned, GigE Vision-specific packed format. 64 | BayerGR16 = 0x0110002E, // 16-bit GRBG (green-red-blue-green) Bayer pattern, unsigned, unpacked format. 65 | BayerRG16 = 0x0110002F, // 16-bit RGGB (red-green-green-blue) Bayer pattern, unsigned, unpacked format. 66 | BayerGB16 = 0x01100030, // 16-bit GBRG (green-blue-red-green) Bayer pattern, unsigned, unpacked format. 67 | BayerBG16 = 0x01100031, // 16-bit BGGR (blue-green-green-red) Bayer pattern, unsigned, unpacked format. 68 | RGB8 = 0x02180014, // 8-bit RGB (red-green-blue), unsigned, unpacked format. 69 | BGR8 = 0x02180015, // 8-bit BGR (blue-green-red), unsigned, unpacked format. 70 | RGBa8 = 0x02200016, // 8-bit RGBa (red-green-blue-alpha), unsigned, unpacked format. 71 | BGRa8 = 0x02200017, // 8-bit BGRa (blue-green-red-alpha), unsigned, unpacked format. 72 | // RGB10 = 0x02300018, // 10-bit RGB (red-green-blue), unsigned, unpacked format. 73 | // BGR10 = 0x02300019, // 10-bit BGR (blue-green-red), unsigned, unpacked format. 74 | // RGB12 = 0x0230001A, // 12-bit RGB (red-green-blue), unsigned, unpacked format. 75 | // BGR12 = 0x0230001B, // 12-bit BGR (blue-green-red), unsigned, unpacked format. 76 | // RGB16 = 0x02300033, // 16-bit RGB (red-green-blue), unsigned, unpacked format. 77 | RGB10V1Packed1 = 0x0220001C, // 10-bit RGB (red-green-blue), unsigned, GigE Vision-specific packed format. 78 | RGB10p32 = 0x0220001D, // 10-bit RGB (red-green-blue), unsigned, packed format. 79 | // RGB12V1Packed1 = 0x02240034, // 12-bit RGB (red-green-blue), unsigned, GigE Vision-specific packed format. 80 | RGB565p = 0x02100035, // RGB (red 5 bits - green 6 bits - blue 5 bits), unsigned, packed format. 81 | BGR565p = 0x02100036, // BGR (blue 5 bits – green 6 bits – red 5 bits), unsigned, packed format. 82 | // YUV411_8_UYYVYY = 0x020C001E, // 8-bit YUV 4:1:1, unsigned, unpacked format. 83 | YUV422_8_UYVY = 0x0210001F, // 8-bit YUV 4:2:2, unsigned, unpacked format. 84 | YUV422_8 = 0x02100032, // 8-bit YUV 4:2:2, unsigned, unpacked format. 85 | YUV8_UYV = 0x02180020, // 8-bit YUV 4:4:4, unsigned, unpacked format. 86 | YCbCr8_CbYCr = 0x0218003A, // 8-bit YCbCr 4:4:4, unsigned, unpacked format. 87 | YCbCr422_8 = 0x0210003B, // 8-bit YCbCr 4:2:2, unsigned, unpacked format. 88 | YCbCr422_8_CbYCrY = 0x02100043, // 8-bit YCbCr 4:2:2, unsigned, unpacked format. 89 | // YCbCr411_8_CbYYCrYY = 0x020C003C, // 8-bit YCbCr 4:1:1, unsigned, unpacked format. 90 | YCbCr601_8_CbYCr = 0x0218003D, // 8-bit YCbCr 4:4:4, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.601. 91 | YCbCr601_422_8 = 0x0210003E, // 8-bit YCbCr 4:2:2, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.601. 92 | YCbCr601_422_8_CbYCrY = 0x02100044, // 8-bit YCbCr 4:2:2, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.601. 93 | // YCbCr601_411_8_CbYYCrYY = 0x020C003F, // 8-bit YCbCr 4:1:1, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.601. 94 | YCbCr709_8_CbYCr = 0x02180040, // 8-bit YCbCr 4:4:4, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.709. 95 | YCbCr709_422_8 = 0x02100041, // 8-bit YCbCr 4:2:2, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.709. 96 | YCbCr709_422_8_CbYCrY = 0x02100045, // 8-bit YCbCr 4:2:2, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.709. 97 | // YCbCr709_411_8_CbYYCrYY = 0x020C0042, // 8-bit YCbCr 4:1:1, unsigned, unpacked format. This pixel format uses the color space specified in ITU-R BT.709. 98 | RGB8_Planar = 0x02180021, // 8-bit RGB (red-green-blue), unsigned, unpacked, planar format where all color planes are transmitted in a multi-part payload block (recommended) or each color plane is transmitted on a different stream channel. 99 | // RGB10_Planar = 0x02300022, // 10-bit RGB (red-green-blue), unsigned, unpacked, planar format where all color planes are transmitted in a multi-part payload block (recommended) or each color plane is transmitted on a different stream channel. 100 | // RGB12_Planar = 0x02300023, // 12-bit RGB (red-green-blue), unsigned, unpacked, planar format where all color planes are transmitted in a multi-part payload block (recommended) or each color plane is transmitted on a different stream channel. 101 | // RGB16_Planar = 0x02300024, // 16-bit RGB (red-green-blue), unsigned, unpacked, planar format where all color planes are transmitted in a multi-part payload block (recommended) or each color plane is transmitted on a different stream channel. 102 | }; 103 | // clang-format on 104 | 105 | /// @brief Gets pixel format enum from string 106 | /// @param pix_fmt_str String containing the name of the pixel format 107 | static PixelFormat pix_fmt_from_str(std::string pix_fmt_str); 108 | 109 | /// @brief Gets pixel format string from enum 110 | /// @param pix_fmt Enum containing the pixel format 111 | static std::string pix_fmt_to_str(PixelFormat pix_fmt); 112 | 113 | FrameFormat(); 114 | 115 | /// @brief Set format of the frames used for the video stream 116 | /// @param dim Width and height of the video frame (in pixels) 117 | /// @param bytes_per_pix Number of bytes per pixel, pixel format will be set to unknown 118 | /// @param word_width Number of bytes per data word used by the dma 119 | void set_format(dim_t dim, uint16_t bytes_per_pixel, uint8_t word_width = 4); 120 | 121 | /// @brief Set format of the frames used for the video stream 122 | /// @param dim Width and height of the video frame (in pixels) 123 | /// @param pixFmt Pixel format, will be used to set bytes per pixel value 124 | /// @param word_width Number of bytes per data word used by the dma 125 | void set_format(dim_t dim, std::string pix_fmt_str, uint8_t word_width = 4); 126 | 127 | /// @brief Set format of the frames used for the video stream 128 | /// @param dim Width and height of the video frame (in pixels) 129 | /// @param pixFmt Pixel format, will be used to set bytes per pixel value 130 | /// @param word_width Number of bytes per data word used by the dma 131 | void set_format(dim_t dim, PixelFormat pix_fmt, uint8_t word_width = 4); 132 | 133 | /// @brief Set format of the frames used for the video stream, pixel format is untouched 134 | /// @param dim Width and height of the video frame (in pixels) 135 | void set_dim(dim_t dim); 136 | 137 | /// @brief Set format of the frames used for the video stream, frame dimensions are not touched 138 | /// @param bytes_per_pix Number of bytes per pixel, pixel format will be set to unknown 139 | void set_bpp(uint16_t bytes_per_pix); 140 | 141 | /// @brief Set format of the frames used for the video stream, frame dimensions are not touched 142 | /// @param pixFmt Pixel format, will be used to set bytes per pixel value 143 | void set_pix_fmt(PixelFormat pix_fmt); 144 | 145 | /// @brief Set format of the frames used for the video stream, frame dimensions are not touched 146 | /// @param word_width Number of bytes per data word used by the dma 147 | void set_word_width(uint8_t word_width); 148 | 149 | dim_t get_dim() const; 150 | PixelFormat get_pixel_format() const; 151 | std::string get_pixel_format_str() const; 152 | uint16_t get_bytes_per_pixel() const; 153 | uint8_t get_word_width() const; 154 | uint16_t get_pixel_per_word() const; 155 | uint16_t get_hsize() const; 156 | size_t get_frm_size() const; 157 | 158 | private: 159 | static const std::unordered_map _pxfmt_enum_to_str_tab; 160 | 161 | void update_frm_dim(dim_t dim); 162 | void update_bpp(uint16_t bytes_per_pix); 163 | void update_px_fmt(PixelFormat pix_fmt); 164 | void update_hsize(); 165 | 166 | // Frame dimensions 167 | dim_t _dim; 168 | /// Pixel format 169 | PixelFormat _pix_fmt; 170 | /// Bytes per pixel 171 | uint16_t _bpp; 172 | /// Number of bytes per data word used by the dma 173 | uint8_t _word_width; 174 | 175 | uint16_t _pix_per_word; 176 | /// Line length in bytes 177 | uint16_t _hsize; 178 | }; 179 | 180 | std::ostream& operator<<(std::ostream& os, const FrameFormat::PixelFormat px_fmt); 181 | 182 | std::ostream& operator<<(std::ostream& os, const FrameFormat& frm_fmt); 183 | 184 | } // namespace udmaio -------------------------------------------------------------------------------- /inc/udmaio/HwAccessor.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------// 2 | // ____ _____________ __ __ __ _ _____ ___ _ // 3 | // / __ \/ ____/ ___/\ \/ / | \/ (_)__ _ _ __|_ _/ __| /_\ // 4 | // / / / / __/ \__ \ \ / | |\/| | / _| '_/ _ \| || (__ / _ \ // 5 | // / /_/ / /___ ___/ / / / |_| |_|_\__|_| \___/|_| \___/_/ \_\ // 6 | // /_____/_____//____/ /_/ T E C H N O L O G Y L A B // 7 | // // 8 | //---------------------------------------------------------------------------// 9 | 10 | // Copyright (c) 2023 Deutsches Elektronen-Synchrotron DESY 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "udmaio/Logging.hpp" 30 | #include "udmaio/UioConfig.hpp" 31 | 32 | namespace udmaio { 33 | 34 | /// Base class for hardware access 35 | class HwAccessor : public Logger, private boost::noncopyable { 36 | template 37 | friend class RegAccessorBase; 38 | friend class UioIf; 39 | 40 | protected: 41 | bool _debug_enable; 42 | 43 | public: 44 | HwAccessor(); 45 | virtual ~HwAccessor(); 46 | 47 | // Enable raw register access log 48 | void enable_debug(bool enable); 49 | 50 | virtual uint32_t _rd32(uint32_t offs) const = 0; 51 | virtual void _wr32(uint32_t offs, uint32_t data) = 0; 52 | 53 | // Default implementation uses 32 bit accesses 54 | // Can be overridden by subclass if it supports native 64 bit access 55 | virtual uint64_t _rd64(uint32_t offs) const; 56 | 57 | // Default implementation uses 32 bit accesses 58 | // Can be overridden by subclass if it supports native 64 bit access 59 | virtual void _wr64(uint32_t offs, uint64_t data); 60 | 61 | void _rd_mem32(uint32_t offs, void* __restrict__ mem, size_t size) const; 62 | void _rd_mem64(uint32_t offs, void* __restrict__ mem, size_t size) const; 63 | 64 | void _wr_mem32(uint32_t offs, const void* __restrict__ mem, size_t size); 65 | void _wr_mem64(uint32_t offs, const void* __restrict__ mem, size_t size); 66 | 67 | virtual std::vector read_bulk([[maybe_unused]] uint32_t offs, 68 | [[maybe_unused]] uint32_t size) { 69 | return {}; 70 | } 71 | 72 | virtual void write_bulk(uint32_t offs, std::vector data) {} 73 | 74 | /** 75 | * @brief Read register from UIO interface 76 | * 77 | * @tparam C Register data type 78 | * @param offs Register offset into address space 79 | * @return C Value read from register 80 | */ 81 | template 82 | C _rd_reg(uint32_t offs) const { 83 | static_assert(sizeof(C) != 0); 84 | static_assert(sizeof(C) % sizeof(uint32_t) == 0); 85 | 86 | C result; 87 | if constexpr (sizeof(C) > sizeof(uint32_t) && sizeof(C) % sizeof(uint64_t) == 0) { 88 | _rd_mem64(offs, reinterpret_cast(&result), sizeof(C)); 89 | } else { 90 | _rd_mem32(offs, reinterpret_cast(&result), sizeof(C)); 91 | } 92 | 93 | return result; 94 | } 95 | 96 | /** 97 | * @brief Write register to UIO interface 98 | * 99 | * @tparam C Register data type 100 | * @param offs Register offset into address space 101 | * @param value Value to write 102 | */ 103 | template 104 | void _wr_reg(uint32_t offs, const C& value) { 105 | static_assert(sizeof(C) != 0); 106 | static_assert(sizeof(C) % sizeof(uint32_t) == 0); 107 | 108 | if constexpr (sizeof(C) > sizeof(uint32_t) && sizeof(C) % sizeof(uint64_t) == 0) { 109 | _wr_mem64(offs, reinterpret_cast(&value), sizeof(C)); 110 | } else { 111 | _wr_mem32(offs, reinterpret_cast(&value), sizeof(C)); 112 | } 113 | 114 | // Prevent compiler & CPU from re-ordering. Make sure the register is actually written here 115 | __sync_synchronize(); 116 | } 117 | 118 | virtual void arm_interrupt(); 119 | virtual uint32_t wait_for_interrupt(); 120 | virtual int get_fd_int() const { return -1; } 121 | virtual UioRegion get_phys_region() const { return {0, 0}; } 122 | virtual void* get_virt_mem() const { return nullptr; } 123 | }; 124 | 125 | // Base class for mmap'ed hardware access 126 | template 127 | class HwAccessorMmap : public HwAccessor { 128 | public: 129 | HwAccessorMmap(std::string dev_path, UioRegion region, uintptr_t mmap_offs, size_t access_offs) 130 | : HwAccessor(), _region{region} { 131 | BOOST_LOG_SEV(HwAccessor::_lg, bls::debug) << "dev name = " << dev_path; 132 | 133 | _fd = ::open(dev_path.c_str(), O_RDWR); 134 | if (_fd < 0) { 135 | throw std::runtime_error("could not open " + dev_path); 136 | } 137 | BOOST_LOG_SEV(HwAccessor::_lg, bls::trace) << "fd = " << _fd << ", size = " << _region.size; 138 | 139 | // create memory mapping 140 | _mmap_mem = mmap(NULL, _region.size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, mmap_offs); 141 | _access_mem = static_cast(static_cast(_mmap_mem) + access_offs); 142 | 143 | BOOST_LOG_SEV(HwAccessor::_lg, bls::trace) 144 | << "mmap = 0x" << std::hex << mmap_offs << " -> 0x" << _mmap_mem << std::dec; 145 | 146 | if (_mmap_mem == MAP_FAILED) { 147 | throw std::runtime_error("mmap failed for uio " + dev_path + ": " + strerror(errno)); 148 | } 149 | 150 | BOOST_LOG_SEV(HwAccessor::_lg, bls::trace) 151 | << "access offs = 0x" << std::hex << access_offs << " -> 0x" << _access_mem << std::dec; 152 | } 153 | 154 | virtual ~HwAccessorMmap() { 155 | munmap(_mmap_mem, _region.size); 156 | ::close(_fd); 157 | } 158 | 159 | protected: 160 | int _fd; 161 | void* _mmap_mem; 162 | void* _access_mem; 163 | const UioRegion _region; 164 | 165 | template 166 | inline volatile access_width_t* _mem_ptr(uint32_t offs) const { 167 | return reinterpret_cast(static_cast(_access_mem) + 168 | static_cast(offs)); 169 | } 170 | 171 | uint32_t _rd32(uint32_t offs) const final override { 172 | const uint32_t tmp = *_mem_ptr(offs); 173 | if (_debug_enable) { 174 | BOOST_LOG_SEV(_lg, bls::trace) 175 | << "0x" << std::hex << offs << " --> 0x" << std::setw(sizeof(uint32_t) * 2) 176 | << std::setfill('0') << tmp << std::dec; 177 | } 178 | return tmp; 179 | } 180 | 181 | void _wr32(uint32_t offs, const uint32_t data) final override { 182 | if (_debug_enable) { 183 | BOOST_LOG_SEV(_lg, bls::trace) 184 | << "0x" << std::hex << offs << " <-- 0x" << std::setw(sizeof(uint32_t) * 2) 185 | << std::setfill('0') << data << std::dec; 186 | } 187 | *_mem_ptr(offs) = data; 188 | } 189 | 190 | uint64_t _rd64(uint32_t offs) const final override { 191 | if constexpr (sizeof(max_access_width_t) == sizeof(uint64_t)) { 192 | // We support native 64-bit access 193 | const uint64_t tmp = *_mem_ptr(offs); 194 | if (_debug_enable) { 195 | BOOST_LOG_SEV(_lg, bls::trace) 196 | << "0x" << std::hex << offs << " --> 0x" << std::setw(sizeof(uint64_t) * 2) 197 | << std::setfill('0') << tmp << std::dec; 198 | } 199 | return tmp; 200 | } else { 201 | // Use default (32-bit) implementation 202 | return HwAccessor::_rd64(offs); 203 | } 204 | } 205 | 206 | void _wr64(uint32_t offs, const uint64_t data) final override { 207 | if constexpr (sizeof(max_access_width_t) == sizeof(uint64_t)) { 208 | // We support native 64-bit access 209 | if (_debug_enable) { 210 | BOOST_LOG_SEV(_lg, bls::trace) 211 | << "0x" << std::hex << offs << " <-- 0x" << std::setw(sizeof(uint64_t) * 2) 212 | << std::setfill('0') << data << std::dec; 213 | } 214 | *_mem_ptr(offs) = data; 215 | } else { 216 | // Use default (32-bit) implementation 217 | HwAccessor::_wr64(offs, data); 218 | } 219 | } 220 | 221 | uint32_t wait_for_interrupt() final override { 222 | const int fd_int = get_fd_int(); 223 | uint32_t irq_count; 224 | BOOST_LOG_SEV(_lg, bls::trace) << "wait for interrupt ..."; 225 | if (fd_int < 0) { 226 | BOOST_LOG_SEV(_lg, bls::fatal) << "fd_int not set"; 227 | } 228 | int rc = read(fd_int, &irq_count, sizeof(irq_count)); 229 | BOOST_LOG_SEV(_lg, bls::trace) 230 | << "interrupt received, rc = " << rc << ", irq count = " << irq_count; 231 | return irq_count; 232 | } 233 | 234 | std::vector read_bulk(uint32_t offs, uint32_t size) final override { 235 | std::vector result; 236 | result.resize(size); 237 | auto src = _mem_ptr(offs); 238 | memcpy(&result[0], const_cast(src), size); 239 | return result; 240 | } 241 | 242 | virtual void write_bulk(uint32_t offs, std::vector data) final override { 243 | auto dst = _mem_ptr(offs); 244 | memcpy(const_cast(dst), &data[0], std::size(data)); 245 | } 246 | 247 | public: 248 | UioRegion get_phys_region() const final override { 249 | const size_t access_offs = 250 | static_cast(_access_mem) - static_cast(_mmap_mem); 251 | return {_region.addr + access_offs, _region.size - access_offs}; 252 | } 253 | void* get_virt_mem() const final override { return _access_mem; } 254 | }; 255 | 256 | // Hardware accessor for XDMA. Can support either 32 or 64 bit access 257 | template 258 | class HwAccessorXdma : public HwAccessorMmap { 259 | int _fd_int; 260 | 261 | public: 262 | // TODO: if we ever have to deal w/ unaligned mappings over XDMA (like on UIO), we'll have to pass an access_offs to HwAccessorMmap 263 | HwAccessorXdma(std::string xdma_path, 264 | std::string evt_dev, 265 | UioRegion region, 266 | uintptr_t pcie_offs) 267 | : HwAccessorMmap(xdma_path + "/user", 268 | {region.addr | pcie_offs, region.size}, 269 | region.addr, 270 | 0) { 271 | if (!evt_dev.empty()) { 272 | const auto evt_path = xdma_path + "/" + evt_dev; 273 | _fd_int = ::open(evt_path.c_str(), O_RDWR); 274 | 275 | if (_fd_int < 0) { 276 | throw std::runtime_error("could not open " + evt_path); 277 | } 278 | BOOST_LOG_SEV(HwAccessor::_lg, bls::trace) << "fd_int = " << _fd_int; 279 | } else { 280 | _fd_int = -1; 281 | } 282 | } 283 | 284 | virtual ~HwAccessorXdma() { 285 | if (_fd_int > 0) { 286 | ::close(_fd_int); 287 | } 288 | } 289 | 290 | int get_fd_int() const final override { return _fd_int; } 291 | }; // namespace udmaio 292 | 293 | // Hardware accessor for AXI. Always supports 64 bit access 294 | class HwAccessorAxi : public HwAccessorMmap { 295 | public: 296 | HwAccessorAxi(std::string dev_path, UioRegion region, uintptr_t mmap_offs, size_t access_offs); 297 | virtual ~HwAccessorAxi(); 298 | 299 | int get_fd_int() const final override { return _fd; } 300 | 301 | void arm_interrupt() final override { 302 | uint32_t mask = 1; 303 | int rc = write(_fd, &mask, sizeof(mask)); 304 | BOOST_LOG_SEV(HwAccessor::_lg, bls::trace) << "arm interrupt enable, ret code = " << rc; 305 | } 306 | }; 307 | 308 | // Base class for mock hardware (for unit tests) 309 | class HwAccessorMock : public HwAccessor { 310 | mutable std::vector _mem; 311 | 312 | public: 313 | HwAccessorMock(size_t mem_size) : HwAccessor{}, _mem(mem_size, 0) {} 314 | 315 | virtual ~HwAccessorMock() {} 316 | 317 | protected: 318 | template 319 | inline volatile access_width_t* _mem_ptr(uint32_t offs) const { 320 | if (offs + sizeof(access_width_t) > _mem.size()) { 321 | throw std::runtime_error("Buffer overflow (" + std::to_string(sizeof(access_width_t)) + 322 | " bytes at " + std::to_string(offs) + ")"); 323 | } 324 | return reinterpret_cast(&_mem[offs]); 325 | } 326 | 327 | uint32_t _rd32(uint32_t offs) const final override { 328 | const uint32_t tmp = *_mem_ptr(offs); 329 | if (_debug_enable) { 330 | BOOST_LOG_SEV(_lg, bls::trace) 331 | << "0x" << std::hex << offs << " --> 0x" << std::setw(sizeof(uint32_t) * 2) 332 | << std::setfill('0') << tmp << std::dec; 333 | } 334 | return tmp; 335 | } 336 | 337 | void _wr32(uint32_t offs, const uint32_t data) final override { 338 | if (_debug_enable) { 339 | BOOST_LOG_SEV(_lg, bls::trace) 340 | << "0x" << std::hex << offs << " <-- 0x" << std::setw(sizeof(uint32_t) * 2) 341 | << std::setfill('0') << data << std::dec; 342 | } 343 | *_mem_ptr(offs) = data; 344 | } 345 | 346 | void* get_virt_mem() const final override { return reinterpret_cast(&_mem[0]); } 347 | }; 348 | 349 | } // namespace udmaio 350 | --------------------------------------------------------------------------------