adrv903x no-OS Example Project
See projects/adrv903x (doxygen) for the Doxygen documentation.
Supported Devices
Supported Carriers
Overview
The ADRV9032 is a 4-transmitter / 4-receiver (4T4R) integrated RF transceiver operating from 650 MHz to 6 GHz with an on-chip ARM Cortex-A55 application processor. This project brings up the ADRV9032 on the ZCU102 using a full JESD204C link managed by the no-OS JESD204 FSM.
The active profile is UC101 4T4R1OR NLS (4 TX / 4 RX / 1 observation receiver, no loopback sync):
JESD204C, 64B/66B encoding, 8 lanes TX + 8 lanes RX
Lane rate: 16220.160 Mbps
DEVCLK: 245.760 MHz (AD9528 channel 1)
LEMC rate: 7.680 MHz
8 TX / 8 RX IQ channels (4 physical antennas × I + Q)
Three example applications are provided: Basic, DMA, and IIO.
Prerequisites
Prior to building the project, the Xilinx development environment must be set up. Follow the build guide for your Vitis version:
Vitis 2025.1: https://analogdevicesinc.github.io/no-OS/build_guides/build_xilinx_vitis2025.html
General no-OS build guide: https://wiki.analog.com/resources/no-os/build
Connect the ADRV9032 evaluation board to the FMC2 connector on the ZCU102 before programming.
A system_top.xsa file for the ZCU102 + ADRV9032 HDL design must be present
in the project directory before building. Either obtain a pre-built file or
build the HDL yourself:
https://wiki.analog.com/resources/fpga/docs/build.
Building the Project
Open a terminal, navigate to the project directory, and invoke make. The
heap size is already configured in the Makefile (-Xlinker
--defsym=_HEAP_SIZE=0x800000); no manual linker-script editing is required.
cd no-OS/projects/adrv903x
make -j
A successful build ends with output similar to:
[HH:MM:SS] Creating BOOT.BIN and archive with files
text data bss dec hex filename
... /path/to/build/adrv903x.elf
[HH:MM:SS] Done (build/adrv903x.elf)
For more details on available make targets, see: https://wiki.analog.com/resources/no-os/make.
Project Layout
no-OS/projects/adrv903x/
├── Makefile
├── README.rst
├── src
│ ├── common
│ │ ├── app_config.h ← JESD204 parameters, UART baud rate
│ │ ├── clkgen_routines.c/h ← AD9528 + AXI clkgen helper
│ │ ├── common_data.c/h ← Shared init structures
│ │ ├── initdata.c/h ← Pre-initialized PostMcsInit defaults
│ │ ├── firmware
│ │ │ ├── ADRV9030_DeviceProfileTest.h
│ │ │ ├── ADRV9030_FW.h
│ │ │ ├── ADRV9030_RxGainTable.h
│ │ │ └── ADRV9030_stream_image.h
│ │ └── hal
│ │ ├── no_os_platform.c
│ │ └── no_os_platform.h
│ ├── examples
│ │ ├── basic_example
│ │ │ ├── basic_example.c
│ │ │ └── basic_example.h
│ │ ├── dma_example
│ │ │ ├── dma_example.c
│ │ │ └── dma_example.h
│ │ └── iio_example
│ │ ├── iio_example.c
│ │ ├── iio_example.h
│ │ └── iio_example.mk
│ ├── LICENSE_API
│ ├── LICENSE_FW
│ └── platform
│ ├── platform_includes.h
│ └── xilinx
│ ├── main.c
│ ├── parameters.c
│ ├── parameters.h ← AXI base addresses, buffer sizes
│ └── platform_src.mk
├── src.mk
└── system_top.xsa
Driver Layout
no-OS/drivers/rf-transceiver/palma/
├── adrv903x.c ← no-OS driver + JESD204 FSM callbacks
├── adrv903x.h
├── common/ ← ADI common error, HAL, logging
│ ├── adi_common.h
│ ├── adi_error/
│ ├── adi_hal/
│ └── adi_logging/
├── devices/
│ └── adrv903x/ ← Palma device API
│ ├── private/ ← Bitfield access, internal calibrations
│ └── public/ ← Public API headers and sources
└── platforms/ ← Platform abstraction layer
Demo Applications
Three mutually exclusive examples are provided. Select one by passing
EXAMPLE=<name> to make; the default is basic_example.
Example |
Build command |
Description |
|---|---|---|
Basic |
|
JESD204C link bring-up only; no data movement |
DMA |
|
TX sine wave + RX one-shot DMA capture |
IIO |
|
IIOD server; use IIO Oscilloscope for live capture and DDS control |
Only one example can be active at a time.
Note
All three examples share the same JESD204C link bring-up sequence. The debug UART baud rate is 115200 for the Basic and DMA examples. The IIO example uses 921600 for the IIOD protocol (the same UART port is shared between the initial link-up log and the IIO server).
Basic Example
Initializes all hardware components and brings up the JESD204C link via the FSM. Once the link is running, JESD204 TX/RX status is printed and the application exits. No DMA transfers take place.
Build and run
make EXAMPLE=basic_example
To program the FPGA and run the ELF over JTAG in one step:
make run
Alternatively, open the project in Vitis IDE for source-level debugging:
make sdkopen
Then use Run As or Debug As from within the IDE.
Connect a serial terminal at 115200 8-N-1 to observe output. Example
using picocom:
picocom -b 115200 /dev/ttyUSB1
Expected output
ADRV903X basic example - JESD204 link bring-up
AD9528 locked, DEVCLK 245.76 MHz on channel 1
rx_adxcvr: Using QPLL with previously defined settings.
adrv903x-phy Rev 0, API version: 2.12.1.4 found
adrv903x: FW 0.0.0.0, stream 0.0.0.0
adrv903x: firmware loaded, ARM CPU running
ADRV903X initialized successfully
rx_clkgen: MMCM-PLL locked (245760000 Hz)
tx_clkgen: MMCM-PLL locked (245760000 Hz)
adrv903x: deframer0 M=4 F=4 K=64 Np=16 lanes=2 204C
adrv903x: framer0 M=4 F=4 K=64 Np=16 lanes=2 204C
tx_adxcvr: OK (16220160 kHz)
adrv903x: MCS complete (status=0x1, 1 pulse(s))
adrv903x: PostMcsInit complete
adrv903x: framer 1 enabled
adrv903x: SERDES calibration complete
adrv903x: deframers enabled, JESD204 link ready
rx_adxcvr: OK (16220160 kHz)
rx_adxcvr: OK (16220160 kHz)
... (repeated ~100 times during CDR lock retry loop)
ERR: adrv903x/.../axi_adxcvr.c:508: QPLL RX buffer underflow error, status: 0x21
adrv903x: ADC_RX calibration complete
adrv903x: link0 deframer linkState 0x3
adrv903x: link0 lane0 status 0xE
adrv903x: link0 lane1 status 0xE
adrv903x: link2 framer status 0x82
adrv903x: initialized channels 0x11311 (TX=0x11 RX=0x11)
adrv903x: signal chain activated
ADRV903X JESD204 link up
tx_jesd status:
Link is enabled
Measured Link Clock: 245.761 MHz
Reported Link Clock: 245.760 MHz
Lane rate: 16220.160 MHz
Lane rate / 66: 245.760 MHz
LEMC rate: 7.680 MHz
Link status: DATA
SYSREF captured: Yes
SYSREF alignment error: No
rx_jesd status:
Link is enabled
Measured Link Clock: 245.761 MHz
Reported Link Clock: 245.760 MHz
Lane rate: 16220.160 MHz
Lane rate / 66: 245.760 MHz
LEMC rate: 7.680 MHz
Link status: DATA
SYSREF captured: Yes
SYSREF alignment error: No
Note
The repeated rx_adxcvr: OK lines and the single QPLL RX buffer
underflow error are expected and do not indicate a failure.
The HDL transceiver block is built for 8 physical lanes to support the lane
remapping required between the ADRV9032 chip and the FPGA GTH transceivers
(for example, chip lane 7 may be wired to FPGA GTH lane 0). However, the
active JESD204C link uses only 2 of those 8 lanes. During the CDR lock
retry loop in adxcvr_clk_enable(), the driver polls the buffer status
of all 8 transceiver instances; the 6 inactive instances report a transient
buffer underflow. This error is non-fatal: the driver continues retrying
until the 2 active lanes lock, after which the link reaches DATA state
normally.
If SYSREF alignment error: Yes appears in the JESD status, it indicates
a SYSREF timing issue. Re-running the application typically resolves this.
DMA Example
Transmits a sine wave continuously on all 8 TX channels via DMA, waits 1 s for the signal to settle, then captures one full RX buffer (262144 bytes, 16384 samples per channel) using the AXI data offload in normal (store-and-forward) mode. To observe the received sine wave, connect a loopback cable from TX0 → RX0 and TX4 → RX4 on the evaluation board.
Build and run
make EXAMPLE=dma_example
To program and run over JTAG:
make run
Alternatively, open the project in Vitis IDE:
make sdkopen
Connect a serial terminal at 115200 8-N-1:
picocom -b 115200 /dev/ttyUSB1
Expected output
After the JESD204 link-up messages (same as Basic Example), the DMA example prints:
RX data offload: normal mode (store+forward)
ADRV903X JESD204 link up
DMA TX: address=0x001e4900 size=262144
DMA RX: address=0x002f2c00 bytes=262144 samples_per_ch=16384 channels=8 bits=16
ADRV903X DMA example complete
Note
The TX and RX buffer addresses shown above are determined by the linker and
will vary between builds. Always use the address printed by the application
in the DMA RX: address= line when running the capture script below.
Capturing data with xsct
Use the capture.tcl script from the no-OS tools directory to read RX
samples from DDR memory into per-channel CSV files. The script arguments are:
xsct ../../tools/scripts/platform/xilinx/capture.tcl \
ZYNQ_PSU <rx_address> <total_samples> <num_channels> <bits>
Substitute <rx_address> with the address printed by the application. For
the DMA example configuration (8 channels, 16-bit, 16384 samples/channel):
xsct ../../tools/scripts/platform/xilinx/capture.tcl \
ZYNQ_PSU 0x002f2c00 131072 8 16
Where 131072 is the total number of 16-bit samples across all 8 channels
(262144 bytes ÷ 2 bytes/sample = 131072 samples). The script generates 8 CSV
files, one per channel.
Visualising the CSV data
Use the plot.py script in the no-OS tools directory. It requires
matplotlib, so create and activate a virtual environment first:
python3 -m venv .venv
source .venv/bin/activate
pip install matplotlib
Then plot all 8 channels (all samples):
python ../../tools/scripts/platform/xilinx/plot.py 8
To plot only the first N samples per channel, pass a second argument:
python ../../tools/scripts/platform/xilinx/plot.py 8 1024
For more information about the DMA capture workflow, see: https://wiki.analog.com/resources/no-os/dac_dma_example.
IIO Example
Starts an IIOD server over UART. Once running, connect IIO Oscilloscope to configure the DAC (DDS tones) and stream live ADC captures.
The following IIO devices are exposed:
Device name |
Type |
Description |
|---|---|---|
|
ADC |
4 RX channels (voltage0–voltage3), 16-bit IQ samples |
|
DAC |
2 TX channels (TX 1, TX 2) with dual DDS tone generators per channel |
Build
make EXAMPLE=iio_example
Alternatively, open the project in Vitis IDE:
make sdkopen
Running and connecting
Program and run the ELF:
make runConnect a serial terminal at 115200 8-N-1 to observe initialisation messages (same JESD link-up sequence as the Basic Example, but at 115200 baud because the IIO server takes over the same UART at that rate):
picocom -b 115200 /dev/ttyUSB1
After the JESD204 link-up messages (the
rx_adxcvrCDR retry loop may also produce aQPLL RX buffer overflow errorin addition to the underflow — both are non-fatal), the application prints:Running IIOD server... If successful, you may connect an IIO client application by: 1. Disconnecting the serial terminal you use to view this message. 2. Connecting the IIO client application using the serial backend: Baudrate: 921600 Data size: 8 bits Parity: none Stop bits: 1 Flow control: none
Close the serial terminal. The UART is shared between debug output and the IIOD protocol; leaving it open will prevent IIO Oscilloscope from connecting.
Launch IIO Oscilloscope.
In the Connect dialog, select Serial Context and enter:
Port: /dev/ttyUSB1 (or the appropriate serial port) Baud rate: 921600 Data bits: 8 Parity: none Stop bits: 1
Click Refresh to scan for devices, then click Connect.
Configuring DAC tones with IIO Oscilloscope
Open the DAC Data Manager tab.
Select DDS Mode.
Set Scale(dBFS) to 0 dB and select One CW Tone.
Set the desired Frequency (Hz) for each TX channel. The DAC starts transmitting immediately.
Capturing ADC data with IIO Oscilloscope
Go to File → New Plot to open a capture window.
In the Plot Channels panel, expand cf-adrv903x-lpc and check the channels you want to capture (voltage0–voltage3), or click Enable All.
Under Plot Type, select Time Domain to view the sine waveform, or Frequency Domain (FFT) to verify the tone frequency and spectral purity.
Set the desired sample count (default 400; use a larger value such as 16384 for the FFT to improve frequency resolution).
Click Capture for a single-shot capture or enable continuous mode for a live view.