__ _ __ _ __ __ ______ _______ ___
/ / (_) /____ | |/_/___/ |/ /_ |/ __/ _ \/ _ \
/ /__/ / __/ -_)> </___/ /|_/ / __/_\ \/ // / , _/
/____/_/\__/\__/_/|_| /_/ /_/____/___/____/_/|_|
LiteX based M2 SDR FPGA board.
Copyright (c) 2024-2026 Enjoy-Digital.
- What? LiteX‑based M.2 2280 SDR board featuring a Xilinx Artix‑7 XC7A200T FPGA and an ADI AD9361 RFIC.
- Why? Open‑source gateware/software, up to 61.44 MSPS (122.88 MSPS†) over PCIe Gen2 ×4, hack‑friendly clocking & debug.
- Who? SDR tinkerers, FPGA devs, time‑sync enthusiasts, or anyone hitting the limits of other SDRs.
- How fast?
apt install …→./build.py→ stream/record IQ in ≈5 min with our C API/tools or any SoapySDR compatible software.
- Docs:
litex_m2sdr/doc/libm2sdr/README.md - Examples:
litex_m2sdr/doc/libm2sdr/example_sync_rx.c,litex_m2sdr/doc/libm2sdr/example_sync_tx.c - Install metadata:
litex_m2sdr/software/user/libm2sdr/m2sdr.pc - Current public library version:
1.0.0(ABI1) - The library is built as a runtime PCIe/Ethernet library; external applications only need to link
libm2sdr. - Recent API additions: backend accessors
m2sdr_get_transport()/m2sdr_get_eb_handle()and finer-grainedparse/range/stateerror classes.
† Oversampling needs PCIe Gen2 ×2/×4 bandwidth.
We know what you'll first ask when discovering this new SDR project: what's the RFIC? 🤔 Let's answer straight away: Another AD936X-based SDR! 😄
Why yet another SDR based on this RFIC? Because we've been designing FPGA-based projects for clients with this chip for almost 10 years now and still think this RFIC has incredible capabilities and possibilities that haven't been fully tapped by open-source projects. We believe it can provide a fantastic and simple solution when paired with the LiteX framework we're developing. 🚀
Imagine a minimalist AD9361-based SDR with:
- A compact form factor (M2 2280). 📏
- Minimal on-board RF frontend that could be specialized externally.
- 2T2R / 12-bit @ 61.44MSPS (and 2T2R / 12-bit @ 122.88MSPS for those wanting to use/explore Cellwizard/BladeRF findings).
- PCIe Gen 2 X4 (~14Gbps of TX/RX bandwidth) with LitePCIe, providing MMAP and several possible DMAs (for direct I/Q samples transfer or processed I/Q samples). ⚡
- A large XC7A200T FPGA where the base infrastructure only uses a fraction of the available resources, allowing you to integrate large RF processing blocks. 💪
- The option to reuse some of the PCIe lanes of the M2 connector for 1Gbps or 2.5Gbps Ethernet through LiteEth. 🌐
- Or ... for SATA through the LiteSATA gateware core. 💾
- Or ... for inter-board SerDes-based communication through LiteICLink. 🔗
- Powerful debug capabilities through LiteX Host <-> FPGA bridges and LiteScope logic analyzer. 🛠️
- Multiboot support to allow secure remote update over PCIe (or Ethernet).
- ...and we hope a welcoming/friendly community as we strive to encourage in LiteX! 🤗
OK, you probably also realized this project is a showcase for LiteX capabilities, haha. 😅 Rest assured, we'll do our best to gather and implement your requests to make this SDR as flexible and versatile as possible!
This board is proudly developed in France 🇫🇷 by Enjoy-Digital, managing the project and litex_m2sdr/gateware/software development, and our partner Lambdaconcept designing the hardware. 🥖🍷
Ideal for SDR enthusiasts, this versatile board fits directly into an M2 slot or can team up with others in a PCIe M2 carrier for more complex projects, including coherent MIMO SDRs. 🔧
For Ethernet support with 1000BaseX/2500BaseX and SATA connectivity to directly record/play samples to/from an SSD, mount it on the LiteX Acorn Mini Baseboard! 💽
Unlock new possibilities in your SDR projects with this cutting-edge board—we'll try our best to meet your needs! 🎉
- Hardware Availability
- Capabilities Overview
- M.2 / GPIO Voltage Levels
- PCIe SoC Design
- Ethernet SoC Design
- Release Artifacts
- Quick Start
- Contact
The LiteX-M2SDR board is now fully commercialized and available for purchase from our webshop: Enjoy-Digital Shop.
The hardware has been thoroughly tested with several SDR softwares compatible with SoapySDR as well as with our Bare metal C utilities.
We offer two variants:
- SI5351C Variant – Uses the SI5351C clock generator with flexible clocking (local XO or external 10MHz via FPGA/uFL). Host-side selection now exposes the dedicated SI5351C FPGA
10MHzCLKIN path (--sync fpga/clock_source=fpga) in addition to the uFL10MHzinput mode. Recommended for general usage. More details - SI5351B Variant – Uses the SI5351B clock generator, clocked from the local XO with FPGA-controlled VCXO for software-regulated loops. More details
Note: The differences between the variants are relevant only for specific use cases. The SI5351B variant is mostly intended for advanced users with specialized clock control requirements.
| Feature | Mounted in M.2 Slot | Mounted in Baseboard | Parameter(s) to Enable |
|---|---|---|---|
| SDR Functionality | |||
| SDR TX (AD9361) | ✅ | ✅ | (always included) |
| SDR RX (AD9361) | ✅ | ✅ | (always included) |
| Oversampling (122.88MSPS) | ✅ (PCIe Gen2 x2/x4 only) | ❌ | `--with-pcie --pcie-lanes=2 |
| C API + Utilities | ✅ | ✅ | (included in software build) |
| SoapySDR Support | ✅ | ✅ | (via optional SoapySDR driver) |
| Connectivity | |||
| PCIe (up to Gen2 x4) | ✅ | ✅ (x1 only) | `--with-pcie --pcie-lanes=1 |
| Ethernet (1G/2.5G) | ❌ | ✅ | --with-eth |
| ├─ Ethernet RX (LiteEth) | ❌ | ✅ | (included with --with-eth) |
| └─ Ethernet TX (LiteEth) | ❌ | ✅ | (included with --with-eth) |
| Timing & Sync | |||
| PTM (Precision Time Measurement) | ✅ (PCIe Gen2 x1 only) | ✅ (PCIe Gen2 x1 only) | --with-pcie --pcie-lanes=1 --with-pcie-ptm |
| Ethernet PTP Time Discipline | ❌ | ✅ | --with-eth --with-eth-ptp |
| Ethernet PTP RFIC Ref Clock | ❌ | ✅ | --with-eth --with-eth-ptp --with-eth-ptp-rfic-clock |
| White Rabbit Support | ❌ | ✅ | --with-white-rabbit |
| External Clocking | ✅ (SI5351C: ext. 10MHz) | ✅ (SI5351C: ext. 10MHz) | (SI5351B VCXO mode in dev for PTM regulation) |
| Storage | |||
| SATA | ❌ | ✅ (source build) | --with-sata |
| System Features | |||
| Multiboot / Remote Update | ✅ | ✅ | (always included) |
| GPIO | ✅ | ✅ | (always included) |
The board exposes a single monochrome user_led, so the gateware uses it as a layered status indicator rather than a simple on/off flag:
- Not ready yet: double-heartbeat while time is still invalid or while an enabled PCIe/Ethernet transport is not ready. PCIe becomes ready when the link is up and DMA/PPS synchronization is established; Ethernet becomes ready when the link is up.
- Idle / ready state: gentle low-amplitude breathing.
- PPS event: short bright accent pulse over the base animation.
- RF or Ethernet RX/TX activity: bright accent pulse.
When PCIe is not enabled in the build, the PCIe-specific states are naturally skipped and the LED falls back to the generic timing/activity behavior.
LiteX-M2SDR does not use a single M.2 I/O voltage:
- FPGA banks 13/14/15/16 on the SDR are powered at 3.3V.
- FPGA banks 34/35 on the SDR are powered at 1.8V.
- The general-purpose sideband signals routed directly from the M.2 connector to the FPGA on LiteX-M2SDR (
PPS,Synchro_GPIO,PERST#, optionalPEWAKE#,SUSCLK,PEDET) sit on 3.3V FPGA banks on the SDR side. - The M.2
SMB_CLK/SMB_DATApins are a special case: on LiteX-M2SDR r02 they reach the FPGA bank-16 pins through optional resistorsR82/R83, which are not mounted by default. - PCIe lanes and the PCIe reference clock are transceiver signals, not single-ended 1.8V/3.3V GPIOs.
Additional notes:
- M.2 pin 44 (
ALERT#/SMB_ALERT#) is currently not routed to the FPGA on LiteX-M2SDR r02. - M.2 pin 52 (
CLKREQ#) is pulled up to3V3_PCIeand is not routed to the FPGA. - M.2 pin 10 (
LED#) is not connected on the FPGA side. - The dedicated FPGA JTAG/config pins and the Acorn JTAG header are separate 3.3V JTAG paths.
- When discussing M.2 sideband voltages, distinguish the FPGA bank voltage on the SDR from the connector-side voltage expected by a host/baseboard. For example, the Acorn baseboard implements the M.2 SMBus pins as a 1.8V SMBus domain with translation to 3.3V for the SFP modules.
| Signal | Connector Location | FPGA Pin | Bank | Voltage On SDR Side | Notes |
|---|---|---|---|---|---|
GPIO0 |
TP1 |
E22 |
16 | 3.3V | General-purpose test point (FPGA_GPIO0). |
GPIO1 |
TP2 |
D22 |
16 | 3.3V | General-purpose test point (FPGA_GPIO1). |
PPS_IN |
M.2 pin 22 (NC22) |
K18 |
15 | 3.3V | Routed to the FPGA. |
PPS_OUT |
M.2 pin 24 (NC24) |
Y18 |
14 | 3.3V | Routed to the FPGA. |
Synchro_GPIO1 |
M.2 pin 28 (NC28) |
A19 |
16 | 3.3V | Routed to the FPGA. |
Synchro_GPIO2 |
M.2 pin 30 (NC30) |
A18 |
16 | 3.3V | Routed to the FPGA. |
Synchro_GPIO3 |
M.2 pin 32 (NC32) |
A21 |
16 | 3.3V | Routed to the FPGA. |
Synchro_GPIO4 |
M.2 pin 34 (NC34) |
A20 |
16 | 3.3V | Routed to the FPGA. |
Synchro_GPIO5 |
M.2 pin 36 (NC36) |
B20 |
16 | 3.3V | Routed to the FPGA. |
SMB_CLK |
M.2 pin 40 | A13 |
16 | 3.3V FPGA bank on SDR | Optional path through R82, not mounted by default; connector-level SMBus compatibility depends on the host/baseboard. |
SMB_DATA |
M.2 pin 42 | A14 |
16 | 3.3V FPGA bank on SDR | Optional path through R83, not mounted by default; connector-level SMBus compatibility depends on the host/baseboard. |
ALERT# / SMB_ALERT# |
M.2 pin 44 | - | - | Host-defined sideband | Not routed to the FPGA on LiteX-M2SDR r02. |
PERST# |
M.2 pin 50 | A15 |
16 | 3.3V | Routed to the FPGA. |
CLKREQ# |
M.2 pin 52 | - | - | 3.3V | Pulled up to 3V3_PCIe with R59; not routed to the FPGA. |
PEWAKE# |
M.2 pin 54 | B16 |
16 | 3.3V | Optional path through R88, not mounted by default. |
SUSCLK |
M.2 pin 68 | B17 |
16 | 3.3V | Routed through R84 (0R). |
PEDET / PRESENT |
M.2 pin 69 | A16 |
16 | 3.3V | Routed through R85 (0R). |
LED# |
M.2 pin 10 | - | - | Host-defined sideband | Not connected on LiteX-M2SDR. |
The PCIe design is the first variant developed for the board and does not require an additional baseboard. Just pop the M2SDR into a PCIe M2 slot, connect your antennas, and you're ready to go! 🚀
The SoC has the following architecture:
- The SoC is built with the LiteX framework, allowing highly efficient HDL coding and integration. 💡
- You'll also find that most of the complexity is managed by LiteX and LitePCIe. The SoC itself only has an MMAP interface, DMA interface, and integrates the specific SDR/RFIC cores and features. ⚙️
- It provides debugging over PCIe or JTAG for MMAP peek & poke or LiteScope. 🛠️
- LitePCIe and its Linux driver (sorry, we only provide Linux support for now 😅) have been battle-tested on several commercial projects. 🏆
The PCIe design has already been validated at the maximum AD9361 specified sample rate: 2T2R @ 61.44MSPS (and also seems to correctly handle the oversampling at 2T2R @ 122.88MSPS with 7.9 Gbps of bandwidth on the PCIe bus; this oversampling feature is already in place and more tests/experiments will be done with it in the future).
Note
Ethernet support is intended for LiteX Acorn Baseboard Mini deployments and is bandwidth-limited by the selected 1000BaseX/2500BaseX link.
The Ethernet design variant gives flexibility when deploying the SDR. The PCIe connector has 4 SerDes transceivers that are in most cases used for... PCIe :) But these are 4 classical GTP transceivers of the Artix7 FPGA that are connected to the PCIe hardened PHY in the case of a PCIe application but can be used for any other SerDes-based protocol: Ethernet 1000BaseX/2500BaseX, SATA, etc...
In this design, the PCIe core will then be replaced with LiteEth, providing the 1000BaseX or 2500BaseX PHY but also the UDP/IP hardware stack + Streaming/Etherbone front-end cores.
The Ethernet SoC design supports control plus RX/TX sample streaming over the LiteEth UDP path. The achievable 2T2R sample rate is capped by link bandwidth, so Ethernet builds also cap the RFIC clock to the selected link speed.
Ethernet-only baseboard builds can also enable SATA storage with --with-sata, using SATA on PCIe lane 0 and Ethernet on the selected SFP lane. PCIe, Ethernet/White-Rabbit, and SATA cannot all be enabled in one image because the shared QPLL exposes two channels.
When built with --with-eth --with-eth-ptp, LiteEth PTP disciplines the existing time_gen timebase instead of replacing it. This keeps PPS generation, VRT timestamps, RX/TX headers, and the PCIe PTM/PHC view on the same logical board clock while sourcing that time from Ethernet PTP. Runtime servo tuning, master/sourcePortIdentity reporting, and live status/counter monitoring are available from the host side. Ethernet PTP and White Rabbit are mutually exclusive. For SI5351C boards, --with-eth-ptp-rfic-clock adds an optional low-bandwidth PTP-to-FPGA-10MHz discipline loop; software must still select the FPGA clock input with --sync fpga / clock_source=fpga before the AD9361 reference is derived from that path.
The AD9361's analog baseband filters are normally limited to ~56 MHz. Requesting a sample rate above 61.44 MSPS (up to 122.88) selects the wide-bandwidth mode automatically:
m2sdr_rf --channel-layout 1t1r --sample-rate 122.88e6 ...Converter half-band stages are bypassed so the data port runs at 2x rate, and the TX/RX baseband filters are force-widened with tuned register words, opening a true ~100 MHz analog passband at 122.88 MSPS. The interface DATA_CLK follows the channel layout: 245.76 MHz in 1T1R (stock gateware) and 491.52 MHz in 2T2R (gateware built with --with-rfic-oversampling). The doubled-rate interface framing can come up misaligned, so the configuration PRBS-verifies the interface and retries the clock programming in place until it is aligned (typically 1-2 attempts).
Measured (loopback TX1->RX1 with 40 dB pad + external-receiver cross-check, 3.6 GHz, 100 MHz NR-FR1-TM3.1 64QAM test model):
| Metric | Wide (overclock) | Stock ~56 MHz |
|---|---|---|
| Usable analog bandwidth | ~100 MHz | ~56 MHz |
| EVM, 100 MHz NR TM3.1 (PDSCH) | ~10% RMS (*) | n/a (BW > filter) |
| EVM, 10 MHz NR TM3.1 | ~3% RMS | ~3% RMS |
| No-signal RX floor (rx-gain 55) | -31.6 dBFS | -34 dBFS |
| TX image rejection, tx-att 0 | ~30 dB | ~42 dB |
| TX image rejection, tx-att >=10 | ~44-58 dB | ~44-58 dB |
(*) With TX magnitude pre-emphasis flattening the widened-filter rolloff the per-band effective SNR is uniform (+/-50 MHz); the ~10% ceiling decomposes into ~7% TX (reference/LO phase noise) and ~8.5% RX (widened-BBF noise floor). Best EVM is obtained on the internal clock reference; an external 10 MHz reference costs ~6% EVM through the Si5351's higher multiplication ratio (N=84 vs N=34).
If you are an SDR enthusiast looking to get started with the LiteX-M2SDR board, follow these simple steps to get up and running quickly:
-
Install Prerequisite Packages:
- On a fresh Ubuntu system, install the required development and SDR packages to ensure compatibility with the LiteX-M2SDR software:
sudo apt install build-essential cmake git \ pkg-config libsdl2-dev libgl1-mesa-dev \ libsoapysdr-dev soapysdr-tools libsoapysdr0.8 \ gnuradio gnuradio-dev libgnuradio-soapy3.10.9t64 gqrx-sdr \ libsndfile1-dev libsamplerate0-dev
- Note: For non-Ubuntu Linux distributions (e.g., Fedora, Arch), install the equivalent packages using your distribution's package manager (e.g.,
dnffor Fedora orpacmanfor Arch).
-
Connect the Board:
- Insert the LiteX-M2SDR board into an available M2 slot on your Linux computer and connect your antennas.
Warning
If an error related to DKMS appears during installation, run sudo apt remove --purge xtrx-dkms dkms and then re-execute the installation command.
-
Clone the Repository:
- Clone the LiteX-M2SDR repository using the following command:
git clone https://github.com/enjoy-digital/litex_m2sdr -
Build Software: Software build uses
makeand CMake for the C kernel driver and utilities, but since we also like Python 😅, we created a small script on top of it to simplify development and installation:cd litex_m2sdr/software ./build.py- This builds the kernel driver, the user-space utilities,
libm2sdr, and the SoapySDR driver. - The default software build is a runtime PCIe/Ethernet build: the same
libm2sdr, user tools, and SoapySDR module can open PCIe or Ethernet devices from the device arguments. Use--interface=litepcieor--interface=liteethonly when you want the legacy shorthand/default transport to favor one side during local testing. - If you also want the optional SDL/OpenGL GUI tools (
m2sdr_check/m2sdr_scan), first populate the pinnedcimguisubmodule:
git submodule update --init --recursive litex_m2sdr/software/user/cimgui- By default,
./build.pybuilds incrementally and does not install when run as a normal user. - Use
./build.py --cleanwhen you want a full rebuild. - Use
sudo ./build.pywhen you also want to install the kernel driver, the user-space utilities /libm2sdr, and the SoapySDR module under the default prefix. m2sdr_checkandm2sdr_scanare optional SDL/OpenGL GUI tools. They are built only when SDL2/OpenGL development packages are installed andlitex_m2sdr/software/user/cimgui/has been populated;m2sdr_scanalso needs libpng. If these optional GUI dependencies are absent, only the affected GUI tools are skipped; the CLI tools,libm2sdr, and the SoapySDR module still build normally.
- This builds the kernel driver, the user-space utilities,
-
Install the Built Software:
- Install the kernel driver:
cd litex_m2sdr/software/kernel sudo make install sudo insmod m2sdr.ko # Optional if you do not want to reboot yet.- Install the public C API headers/library for external applications:
cd litex_m2sdr/software/user make sudo make install_dev PREFIX=/usr/local sudo ldconfig- Install the SoapySDR module:
cd litex_m2sdr/software/soapysdr/build sudo make install- If you already used
sudo ./build.py, the kernel and SoapySDR install steps above are already done.libm2sdrstill needssudo make install_dev ...if you want to develop external applications against the public C API. - 🚀 Ready for launch!
-
Run Your SDR Software:
- Now, you can launch your preferred SDR software (like GQRX or GNU Radio) and select the LiteX-M2SDR board through SoapySDR. 📡
- IOMMU / DMA: For PCIe streaming, set IOMMU to passthrough mode. If you don't see I/Q data streams in your SDR app, this is the first thing to check.
- PCIe Gen & Lanes: Oversampling (122.88 MSPS) requires PCIe Gen2 x2/x4 bandwidth. Gen2 x1 is enough for standard 61.44 MSPS.
- Runtime transport selection: The installed user tools and SoapySDR module support both transports in one build. Use
--device pcie:/dev/m2sdr0or--device eth:192.168.1.50:1234with the CLI tools, anddriver=LiteXM2SDR,path=/dev/m2sdr0ordriver=LiteXM2SDR,eth_ip=192.168.1.50with SoapySDR. - PCIe PTM host-time sync: Build with
--with-pcie --pcie-lanes=1 --with-pcie-ptmand runscripts/m2sdr_pcie_time_sync.pyon the host to make the board PHC followCLOCK_REALTIMEthroughphc2sys. If the host clock is locked by NTP/PTP, the board follows that disciplined host time over PCIe. - Ethernet VRT (optional RX path): Build with
--with-eth --with-eth-vrtto enable an Ethernet RX VRT UDP streamer in hardware. A simple host receiver utility is available atlitex_m2sdr/software/user/m2sdr_vrt_rx.py. - Ethernet / SATA: Ethernet RX/TX streaming is supported on the LiteX Acorn Baseboard Mini. Source builds can combine Ethernet and SATA with
./litex_m2sdr.py --variant=baseboard --with-eth --eth-sfp=0 --with-sata --build.m2sdr_satasupports low-level sector tests and named capture workflows for RF-to-SATA recording, host import/export, SATA-to-RF replay, and SATA replay into the normal PCIe/Ethernet RX path used by SoapySDR/GQRX. - Ethernet RFIC clocking: Ethernet builds cap the RFIC clock to the link-speed streaming budget for 2T2R SC8: 122.88MHz with
1000basexand 245.76MHz with2500basex. PCIe builds keep the full 245.76MHz/491.52MHz non-oversample/oversample options. - Ethernet PTP (optional timing path): Build with
--with-eth --with-eth-ptpto discipline the existing boardtime_genfrom LiteEth PTP.m2sdr_util info,m2sdr_util --watch ptp-status, andm2sdr_util ptp-configexpose the current lock/holdover state, learned port identity, runtime servo controls, and board-side discipline counters. While PTP discipline is active, host-side time writes are rejected to avoid two masters steering the same clock. - Ethernet PTP RFIC reference (optional clock path): Add
--with-eth-ptp-rfic-clockto expose a PTP-referenced FPGA 10MHz monitor/discipline loop. Enable it at runtime withm2sdr_util ptp-clock10-config enable on, verifyReference LockedandClock Locked, then select the FPGA clock input for RF setup withm2sdr_rf --sync fpgaor the matching SoapySDRclock_source=fpgasetting. This gives RFIC reference frequency coherence; deterministic sample/RF phase alignment still needs AD9361 synchronization and timestamped stream start.
Date-named release archives are generated with:
./release.py
The release script checks the final Vivado timing report before creating each archive, so a bitstream with setup/hold timing failures is not packaged. Release manifests include the parsed timing summary; PCIe images may record the known Xilinx PCIe IP pulse-width warning when setup/hold timing is otherwise clean. Ethernet-enabled builds default to a 100MHz system clock for timing margin; PCIe-only M.2 builds keep the 125MHz system clock. The first release matrix builds the core PCIe/Ethernet images plus the validated Ethernet PTP RFIC-reference image:
| Archive prefix | Build command |
|---|---|
litex_m2sdr_baseboard_eth |
./litex_m2sdr.py --variant=baseboard --with-eth --eth-sfp=0 --build |
litex_m2sdr_baseboard_eth_ptp_rfic_clock |
./litex_m2sdr.py --variant=baseboard --with-eth --eth-sfp=0 --with-eth-ptp --with-eth-ptp-rfic-clock --build |
litex_m2sdr_baseboard_pcie_x1_eth |
./litex_m2sdr.py --variant=baseboard --with-pcie --pcie-lanes=1 --with-eth --eth-sfp=0 --build |
litex_m2sdr_m2_pcie_x1 |
./litex_m2sdr.py --variant=m2 --with-pcie --pcie-lanes=1 --build |
litex_m2sdr_m2_pcie_x2 |
./litex_m2sdr.py --variant=m2 --with-pcie --pcie-lanes=2 --build |
Each build/*_<YYYY_MM_DD>.zip contains the .bit, .bin, multiboot fallback/operational images, CSR exports, and a JSON manifest. Generated archives and bitstreams are release artifacts and are not committed to git.
GitHub release publication uses the same date string as the archive suffix, with no v prefix on the tag. After generating the archives from the final release commit, validate the upload plan and then publish it with:
scripts/github_release.py --date 2026_05_15 --dry-run
scripts/github_release.py --date 2026_05_15
The helper requires the GitHub CLI gh to be installed and authenticated with release write access. It checks for a clean tracked tree, verifies that the expected build/*_2026_05_15.zip files exist, reads each archive manifest, creates and pushes the annotated 2026_05_15 tag on the manifest git revision, extracts the matching CHANGELOG.md section as release notes, and uploads the archive set to the GitHub release.
Tip
If you don't see I/Q data streams in your SDR app, make sure IOMMU is set to passthrough mode. Add the following to your GRUB configuration:
x86/PC:
# Add to GRUB config (/etc/default/grub):
GRUB_CMDLINE_LINUX="iommu=pt"
sudo update-grub && sudo rebootARM (ex NVIDIA Jetson/Orin):
# Add to extlinux.conf (/boot/extlinux/extlinux.conf):
APPEND ... iommu.passthrough=1 arm-smmu.disable=1
sudo rebootWarning
For intel CPU: if a kernel panic occurs with the message Corrupted page table at address,
add intel_iommu=off to GRUB_CMDLINE_LINUX. (This has been observed on
an 11th Gen Intel(R) Core(TM) i7-11700B @ 3.20GHz)
Warning
WiP 🧪 Content below is more our memo as developers than anything useful to read 😅. This will be reworked/integrated differently soon.
For some platforms we created detailed tutorials. For everything else, please follow the earlier Getting Started tutorial.
For those who want to dive deeper into development with the LiteX-M2SDR board, follow these additional steps after completing the SDR enthusiast steps:
For a broader hardware/software debug workflow, see Debugging Guide.
-
Test Structure (CI-safe vs hardware scripts):
- Gateware simulation/unit tests live in
test/and are CI-safe (no hardware needed):
pytest -v test- Board control/debug scripts live in
scripts/and require a running board/server:
python3 scripts/test_xadc.py python3 scripts/test_dashboard.py- CI runs both software build checks and simulation tests with:
# Software build checks (kernel/user/SoapySDR) are run in CI. python3 -m pytest -v test - Gateware simulation/unit tests live in
-
Run Software Tests:
- Test the kernel:
cd litex_m2sdr/software/kernel make clean all sudo make install sudo insmod m2sdr.ko (To avoid having to reboot the machine)- Test the user-space utilities:
cd litex_m2sdr/software/user make clean all ./m2sdr_util info ./m2sdr_rf --sample-rate=30720000 --tx-freq=2400000000 --rx-freq=2400000000 ./m2sdr_gen --sample-rate 30720000 --signal tone --tone-freq 1000000 --amplitude 0.5- C API (libm2sdr) quick start and examples:
See litex_m2sdr/doc/libm2sdr/README.md cd litex_m2sdr/software/user make examples ../../doc/libm2sdr/example_sync_rx > /tmp/rx.iq ../../doc/libm2sdr/example_tone_txlibm2sdris the common host interface used by the user utilities and the SoapySDR module, so example code there is the reference starting point for new host applications.
-
SoapySDR Detection/Probe:
- Detect the LiteX-M2SDR board:
SoapySDRUtil --probe="driver=LiteXM2SDR" -
Run GNU Radio FM Test:
- Open and run the GNU Radio FM test:
gnuradio-companion litex_m2sdr/software/gnuradio/test_fm_rx.grc -
Enable Debugging in Kernel:
- Enable debugging:
sudo sh -c "echo 'module m2sdr +p' > /sys/kernel/debug/dynamic_debug/control"
For those who want to explore the full potential of the LiteX-M2SDR board, including FPGA development, follow these additional steps after completing the software developer steps:
-
Install LiteX:
- Follow the installation instructions from the LiteX Wiki: LiteX Installation. 📘
-
Ethernet and PCIe Tests:
- For Ethernet tests, if the board is mounted in an Acorn Mini Baseboard:
./litex_m2sdr.py --variant=baseboard --with-eth --eth-sfp=0 --build --load ping 192.168.1.50- After loading an Ethernet image, use
m2sdr_utilloopback tests to exercise the stream path before starting Soapy/Gqrx:
cd litex_m2sdr/software/user make m2sdr_util ./m2sdr_util -i 192.168.1.50 --duration 4 --pace=rx --sample-rate 1920000 --window 32 fpga-phy-loopback-test ./m2sdr_util -i 192.168.1.50 --duration 8 --pace=rx --sample-rate 1920000 --window 32 ad9361-loopback-test- The loopback tests reset FPGA stream state at startup/cleanup, so they can
be run after Soapy/Gqrx sessions. Add
--verboseto show detailed RF setup logs and LiteEth counters. See Debugging Guide for the full loopback workflow. - For Ethernet + SATA source-build tests:
./litex_m2sdr.py --variant=baseboard --with-eth --eth-sfp=0 --with-sata --build --load cd litex_m2sdr/software/user make m2sdr_sata ./m2sdr_sata -i 192.168.1.50 info ./m2sdr_sata -i 192.168.1.50 diag etherbone-bench ./m2sdr_sata -i 192.168.1.50 --pattern counter diag pattern-write 0x8000 4096 ./m2sdr_sata -i 192.168.1.50 --pattern counter diag pattern-check 0x8000 4096 ./m2sdr_sata -i 192.168.1.50 init ./m2sdr_sata -i 192.168.1.50 capture fm_test --seconds 2 --sample-rate 4M --format sc16 --channel-layout 1t1r --rx-freq 100M --rx-gain 20 --bandwidth 5M ./m2sdr_sata -i 192.168.1.50 list ./m2sdr_sata -i 192.168.1.50 export fm_test /tmp/fm_test.sigmf-meta ./m2sdr_sata -i 192.168.1.50 export fm_test /tmp/fm_test.sc16 --raw ./m2sdr_sata -i 192.168.1.50 import tx_test /tmp/tx.sc16 --sample-rate 4M --format sc16 --channel-layout 1t1r --tx-freq 2400M --tx-att 20 ./m2sdr_sata -i 192.168.1.50 import tx_sigmf /tmp/tx.sigmf-meta ./m2sdr_sata -i 192.168.1.50 play tx_test ./m2sdr_sata -i 192.168.1.50 play tx_sigmfTo replay a stored capture into an existing SoapySDR/GQRX receive flow, start the RX application normally, then feed the Ethernet RX path from SATA:
./m2sdr_sata -i 192.168.1.50 serve fm_testThe SATA Capture Volume is stored on the SATA disk at sector
0x800; automatic capture allocation starts at sector0x100000, and named captures keep a SigMF metadata region next to the sample data. It is a small capture index, not a general file system: captures stay in contiguous sector ranges for the SATA streamers, SigMF provides interchange metadata, and avoiding FAT/ext keeps validation and recovery simple.initrefuses to reset a non-empty volume unless--forceis provided; it does not erase sample data sectors. See SATA Workflows for the fullm2sdr_sataworkflow and SATA Hardware Validation for measured PCIe/Ethernet validity and throughput results.- For PCIe + SATA source-build tests:
./litex_m2sdr.py --variant=baseboard --with-pcie --pcie-lanes=1 --with-sata --build --load cd litex_m2sdr/software/user make m2sdr_util m2sdr_sata ./m2sdr_util -c 0 info ./m2sdr_sata -c 0 info ./m2sdr_sata -c 0 --pattern counter diag pattern-write 0x8000 4096 ./m2sdr_sata -c 0 --pattern counter diag pattern-check 0x8000 4096 ./m2sdr_sata -c 0 init ./m2sdr_sata -c 0 capture fm_test --seconds 2 --sample-rate 4M --format sc16 --channel-layout 1t1r --rx-freq 100M --rx-gain 20 --bandwidth 5M ./m2sdr_sata -c 0 export fm_test /tmp/fm_test.sc16 --raw ./m2sdr_sata -c 0 export fm_test /tmp/fm_test.sigmf-meta ./m2sdr_sata -c 0 serve fm_test- For Ethernet PTP time-discipline tests on the baseboard:
./litex_m2sdr.py --variant=baseboard --with-eth --with-eth-ptp --eth-sfp=0 --build --load sudo ptp4l -i <host-eth-iface> -4 -E -S -m cd litex_m2sdr/software/user make m2sdr_util ./m2sdr_util -i 192.168.1.50 info ./m2sdr_util -i 192.168.1.50 --watch ptp-statusm2sdr_util inforeports whether the LiteEth PTP core is locked, whether the board time is locked to PTP, and whether the clock is in holdover.m2sdr_util --watch ptp-statusshows the live discipline state, learned master identity, and lock/loss counters from the board-time discipline loop.m2sdr_util --json ptp-statusandm2sdr_util ptp-smokeprovide machine-readable and pass/fail checks for lab automation; use tcpdump when protocol message visibility is needed.m2sdr_util ptp-configexposes runtime servo tuning.- To also discipline the FPGA-generated 10MHz RFIC reference path:
./litex_m2sdr.py --variant=baseboard --with-eth --with-eth-ptp --with-eth-ptp-rfic-clock --eth-sfp=0 --build --load sudo ptp4l -i <host-eth-iface> -4 -E -S -m cd litex_m2sdr/software/user make m2sdr_util ./m2sdr_util -i 192.168.1.50 ptp-clock10-config ./m2sdr_util -i 192.168.1.50 ptp-clock10-config enable on ./m2sdr_util -i 192.168.1.50 ptp-clock10-config align ./m2sdr_util -i 192.168.1.50 --watch ptp-clock10-status cd ../../.. scripts/m2sdr_ptp_check.py smoke --ip 192.168.1.50 --iface <host-eth-iface> --with-clock10 --require-clock10-lock- After the clk10 loop is stable, use
m2sdr_rf --sync fpgaor SoapySDRclock_source=fpgaso the SI5351C derives the AD9361 reference from the FPGA 10MHz path. - See Ethernet PTP Bring-Up for known-good
ptp4lconfigs, host timestamping checks, and smoke/soak validation commands. - For PCIe tests, if the board is mounted directly in an M2 slot:
./litex_m2sdr.py --with-pcie --variant=m2 --build --load lspci- To have a PCIe/PTM image automatically follow host time, build with PTM and start the host-side sync helper after the kernel driver has created the M2SDR PHC:
./litex_m2sdr.py --with-pcie --pcie-lanes=1 --with-pcie-ptm --variant=m2 --build --load scripts/m2sdr_pcie_time_sync.py --dry-run sudo scripts/m2sdr_pcie_time_sync.py --stdout- For boot-time use, install a systemd service similar to:
[Unit] Description=Synchronize M2SDR PCIe board time to host time After=multi-user.target [Service] ExecStart=/path/to/litex_m2sdr/scripts/m2sdr_pcie_time_sync.py --stdout Restart=always RestartSec=2 [Install] WantedBy=multi-user.target-
The helper auto-detects
/sys/class/ptp/ptp*/clock_name == m2sdrand runsphc2sys -s CLOCK_REALTIME -c /dev/ptpN, so the board is the sink and the host is the source. For multi-board systems, pass--phc /dev/ptpN. -
For PCIe tests, if the board is mounted directly in a LiteX Acorn Baseboard:
./litex_m2sdr.py --with-pcie --variant=baseboard --build --load lspci -
White Rabbit (Baseboard):
- White Rabbit is supported on the baseboard variant only:
./litex_m2sdr.py --with-pcie --with-white-rabbit --variant=baseboard --build- The White Rabbit helper logic is provided by
litex_wr_nic; install it, setLITEX_WR_NIC_DIR, or keep a sibling../litex_wr_niccheckout. --wr-sfpis optional; when omitted, the first availablesfpindex is auto-selected.- Firmware path lookup order:
--wr-firmware--wr-nic-dirLITEX_WR_NIC_DIR- auto-discovery of
../litex_wr_nicand../../litex_wr_nic
- If a stale local
wr-cores/checkout is detected, refresh it:
mv wr-cores wr-cores.old ./litex_m2sdr.py --with-pcie --with-white-rabbit --variant=baseboard --build -
Use JTAGBone/PCIeBone:
- Start the LiteX server for JTAG or PCIe:
litex_server --jtag --jtag-config=openocd_xc7_ft2232.cfg # JTAGBone sudo litex_server --pcie --pcie-bar=04:00.0 # PCIeBone (Adapt bar) -
Flash the Board Over PCIe:
- Flash the board:
cd litex_m2sdr/software ./flash.py ../build/litex_m2sdr_platform/litex_m2sdr/gateware/litex_m2sdr_platform.bin -
Reboot or Rescan PCIe Bus:
- Rescan the PCIe bus:
echo 1 | sudo tee /sys/bus/pci/devices/0000\:0X\:00.0/remove # Replace X with actual value echo 1 | sudo tee /sys/bus/pci/rescan
Got a unique idea or need a tweak? Whether it's custom FPGA/software development or hardware adjustments (like adapter boards) for your LiteX M2 SDR, we're here to help! Feel free to drop us a line or visit our website. We'd love to hear from you!
E-mail: florent@enjoy-digital.fr Website: http://enjoy-digital.fr/





