Perform TDD 16×1 Massive MIMO channel sounding on the ORBIT grid using one Massive MIMO rack (8 USRP-X310s) as the base station and one USRP-B210 as the user equipment, driven by the RENEW Sounder software.
This tutorial demonstrates Massive MIMO channel sounding in time-division duplex (TDD) mode. A Massive MIMO rack comprising 8 USRP-X310 radios acts as the 16-antenna base station (two antennas per X310), while a single USRP-B210 acts as the single-antenna user equipment (UE). The experiment uses the RENEW Sounder software to orchestrate frame transmission, capture uplink IQ data across all 16 base station antennas simultaneously, and record the results in HDF5 format for post-processing.
Post-processing produces pilot and uplink IQ waveforms, uplink user constellations, Error Vector Magnitude (EVM), and Symbol Error Rate (SER), giving a concrete measurement of the over-the-air MIMO channel.
The experiment runs on the ORBIT grid.
After completing this tutorial you will be able to:
| Difficulty | Advanced |
| Estimated time | TODO: verify |
| Domain / sandbox | grid.cosmos-lab.org |
| Topic group | channel-sounding-mimo |
| Last verified | Not re-tested (migrated 2026-06-20) |
Background knowledge
Account & access
Devices / nodes
| Resource | Role | Qty | Notes |
|---|---|---|---|
| node23-1 through node23-8 | Massive MIMO base station (BS) rack — USRP-X310 | 8 | 10 GbE ports hard-coded to 10.10.23.1–10.10.23.8; 2 antennas each (16 total) |
| node21-1 | BS server — runs RENEW Sounder in --bs_only mode |
1 | Connects to X310s via 10 GbE (CTRL interface, 10.10.21.1/16) |
| node8-7 | UE — USRP-B210 | 1 | B210 connected via USB 3 |
Disk images
| Image | Load onto | Provides |
|---|---|---|
| ubuntu2004-uhd4.1-gr3.9.ndz | node21-1, node8-7 | Ubuntu 20.04, UHD 4.1.0.4, GNU Radio 3.9 |
Software components
| Component | Version | Source |
|---|---|---|
| UHD | 4.1.0.4 | preinstalled in ubuntu2004-uhd4.1-gr3.9.ndz |
| GNU Radio | 3.9 | preinstalled in ubuntu2004-uhd4.1-gr3.9.ndz |
RENEW Sounder (Pure-UHD-Multi-USRP branch) |
TODO: verify commit/tag | github.com/renew-wireless/RENEWLab — built on node21-1 |
RENEW Sounder (USRP-B210-New-Sync branch) |
TODO: verify commit/tag | github.com/renew-wireless/RENEWLab — built on node8-7 |
| Python 3 + HDF5 post-processing scripts | as shipped in RENEWLab | RENEWLab/PYTHON/IrisUtils/plot_hdf5.py |
Spectrum / RF / special
The Massive MIMO rack operates over the air on the ORBIT grid. The carrier frequency, bandwidth, and power levels are configured through the RENEW Sounder JSON configuration file (files/special_conf/usrp-16QAM.json). Consult the RENEW documentation for allowed operating parameters and coordinate with grid staff before transmitting.
One server node (node21-1) connects to eight USRP-X310 radios (node23-1–node23-8) over 10 GbE. The X310s form the 16-antenna Massive MIMO base station array. A separate node (node8-7) hosts the USRP-B210 UE connected via USB 3. Both node21-1 and node8-7 reach the grid console over the control network. The RENEW Sounder BS process runs on node21-1 and the RENEW Sounder client process runs on node8-7; they synchronize over the air.
Reserve the grid resources (see Prerequisites) and log into the grid console:
ssh <username>@console.grid.cosmos-lab.org
Turn off the server, the B210 node, and all Massive MIMO rack nodes before imaging:
omf tell -a offh -t node21-1,node8-7
omf tell -a offh -t [23,1..8]
Load ubuntu2004-uhd4.1-gr3.9.ndz onto the server node and the B210 node (the X310 rack nodes do not need reimaging):
omf load -i ubuntu2004-uhd4.1-gr3.9.ndz -t node21-1
omf load -i ubuntu2004-uhd4.1-gr3.9.ndz -t node8-7
Turn all required resources on and confirm status:
omf tell -a on -t node21-1,node8-7
omf tell -a on -t [23,1..8]
omf stat -t all
Open two SSH sessions from the console (use -Y if you need GUI forwarding for post-processing plots):
# Terminal 1
ssh -Y root@node21-1
# Terminal 2
ssh -Y root@node8-7
On node21-1 (Terminal 1), raise the MTU on the control interface to 9000 and increase socket buffer sizes for better 10 GbE throughput to the X310s:
root@node21-1:~# ifconfig CTRL mtu 9000 up
root@node21-1:~# sudo sysctl -w net.core.wmem_max=24862979
net.core.wmem_max = 24862979
root@node21-1:~# sudo sysctl -w net.core.rmem_max=24862979
net.core.rmem_max = 24862979
root@node21-1:~# ifconfig CTRL
CTRL: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9000
inet 10.10.21.1 netmask 255.255.0.0 broadcast 10.10.255.255
inet6 fe80::9a03:9bff:fe3b:81e3 prefixlen 64 scopeid 0x20<link>
ether 98:03:9b:3b:81:e3 txqueuelen 1000 (Ethernet)
RX packets 28985875 bytes 107820952173 (107.8 GB)
RX errors 121 dropped 0 overruns 0 frame 121
TX packets 12711747 bytes 14132140459 (14.1 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
The IP addresses for Ethernet Port 1 (10 GbE) on node23-1 through node23-8 are hard-coded to 10.10.23.1 through 10.10.23.8 respectively. No additional network interface configuration is needed on those nodes.
Verify that one of the X310s is reachable:
root@node21-1:~# uhd_find_devices --args="addr0=10.10.23.1"
[INFO] [UHD] linux; GNU C++ version 9.3.0; Boost_107100; UHD_4.1.0.4-release
--------------------------------------------------
-- UHD Device 0
--------------------------------------------------
Device Address:
serial: 30A3570
addr: 10.10.23.1
fpga: XG
name: Mercury
product: X310
type: x300
On node8-7 (Terminal 2), the USRP-B210 is connected via USB 3 and needs no network configuration. Verify it is visible (use the serial number printed on your device):
root@node8-7:~# uhd_find_devices --args="serial=30AD2E0"
[INFO] [UHD] linux; GNU C++ version 9.3.0; Boost_107100; UHD_4.1.0.4-release
--------------------------------------------------
-- UHD Device 0
--------------------------------------------------
Device Address:
serial: 30AD2E0
name: MyB210
product: B210
type: b200
On node21-1 (Terminal 1), clone the Pure-UHD-Multi-USRP branch and build:
root@node21-1:~# git clone --branch Pure-UHD-Multi-USRP https://github.com/renew-wireless/RENEWLab.git
Follow the RENEW Sounder build instructions to compile. This branch supports multiple USRP-X310s; the master branch does not have UHD support.
On node8-7 (Terminal 2), clone the USRP-B210-New-Sync branch and build:
root@node8-7:~# git clone --branch USRP-B210-New-Sync https://github.com/renew-wireless/RENEWLab.git
Follow the same RENEW Sounder build instructions to compile. The Pure-UHD-Multi-USRP branch does not support USRP-B210.
In files/special_conf/usrp_16qam.json on node21-1, add the IP addresses of all eight X310s under BaseStations:
10.10.23.1 through 10.10.23.8
In the same file (or in the corresponding client config on node8-7), set the B210 serial 30AD2E0 under Clients as the user equipment.
On node8-7 (Terminal 2), generate the required binary reference file before running the experiment:
root@node8-7:~/RENEWLab/CC/Sounder# ./build/sounder --conf files/special_conf/usrp-16QAM.json --gen_data_bits
...
...
INFOR: Allocating 1 cores to receive threads ...
INFOR: Allocating 1 cores to record threads ...
INFOR: Allocating 1 cores to read threads ...
INFOR: Allocating 1 cores to client threads ...
Config: 1 BS, 1 BS radios (total), 1 UE antennas, 1 pilot symbols per frame,
1 uplink data symbols per frame, 0 downlink data symbols per frame,
64 OFDM subcarriers (52 data subcarriers), modulation 16QAM, frame time 18360.000 usec
Thread Config: 1 BS receive threads, 1 Client receive threads, 1 recording threads, 1 reading thread
Saving UL data bits for radio 0 to logs/ul_data_b_16QAM_52_64_10_1_1_A_0.bin
Saving UL frequency-domain data for radio 0 to logs/ul_data_f_16QAM_52_64_10_1_1_A_0.bin
Saving UL time-domain data for radio 0 to logs/ul_data_t_16QAM_52_64_10_1_1_A_0.bin
For correct EVM and SER calculation during post-processing, the exact binary file must be used on both the client and base station. Copy it to the server:
root@node21-1:~# scp root@node8-7:~/RENEWLab/CC/Sounder/logs/ul_data_f_16QAM_52_64_10_1_1_A_0.bin /root/RENEWLab/CC/Sounder/logs
On node8-7 (Terminal 2), start the Sounder in client mode:
root@node8-7:~/RENEWLab/CC/Sounder# ./build/sounder --conf files/special_conf/usrp-16QAM.json --client_only
...
...
INFOR: ClientRadioSetUHD done!
INFOR: Successfully pinned main scheduler thread to core 0finish start client threads
INFOR: Launching reader task thread with id: 1 and core 1
INFOR: Pinning reading thread 1 to core 1
Opening time-domain data for radio 0 to logs/ul_data_t_16QAM_52_64_10_1_1_A_0.bin
INFOR: Pinning client synctxrx thread 0 to core 2
INFOR: Scheduling TX: 2 Frames (48.576000 ms) in the future!
INFOR: Beacon detected at Time 197069085, sync_index: 106001
Client 0 Estimated CFO (Hz): -213.117
INFOR: Start main client txrx loop... tid=0
Using resync period of 82
The Beacon detected and Start main client txrx loop messages confirm the client is synchronized and transmitting.
On node21-1 (Terminal 1), start the Sounder in base station mode:
root@node21-1:~/RENEWLab/CC/Sounder# ./build/sounder --conf files/special_conf/usrp-16QAM.json --bs_only
...
...
INFOR: Successfully pinned main scheduler thread to core 073:082345 INFOR: Creating recorder thread: 0, with antennas 0:7 total 8
INFOR: Creating output HD5F file: logs/trace-uplink-2022-7-8-7-43-59_1_8x1_0_7.hdf5
INFOR: Launching recorder task thread with id: 0 and core 1
INFOR: Pinning rx thread 0 to core 2
INFOR: Pinning recording thread 0 to core 1
INFOR: Recv Thread 0: waiting for release
INFOR: Recording thread 0 has 8 antennas starting at 0
num channels are: 8
INFOR: Receiver thread 0 has 1 radios
INFOR: Sync BS host and FPGA timestamp for thread 0
INFOR: Start BS main recv loop in thread 0
INFOR: Saving HD5F: 2000 frames saved on CPU 1
...
...
The BS records the channel trace to an HDF5 file (e.g., logs/trace-uplink-2022-7-8-7-43-59_1_8x1_0_7.hdf5). Wait until a sufficient number of frames is saved, then stop both processes with Ctrl-C.
Because the client and base station ran in separate terminals, a minor modification to hdf5_lib.py is needed before post-processing. On node21-1, open the file and replace line 916:
tx_file_names = metadata['TX_FD_DATA_FILENAMES'].astype(str)
with:
manual_file = ['ul_data_f_16QAM_52_64_10_1_1_A_0.bin']
manual_file = np.array(manual_file)
tx_file_names = manual_file
Then run the post-processing script against the recorded HDF5 file:
root@node21-1:~/RENEWLab/PYTHON/IrisUtils# python3 plot_hdf5.py ../../CC/Sounder/logs/trace-uplink-2022-7-8-7-43-59_1_8x1_0_7.hdf5 --ref-frame=250 --signal-offset=1775 --demodulate zf
--ref-frame=250 selects the reference frame to analyze; --signal-offset=1775 sets the sample offset within that frame. These values may need adjustment depending on your capture.
Follow the full Analyze the HDF5 dataset instructions in the RENEW Sounder documentation for additional options.
uhd_find_devices --args="addr0=10.10.23.1" on node21-1 returns the X310 device with correct serial and product fields.uhd_find_devices --args="serial=<B210-serial>" on node8-7 returns the B210.INFOR: Saving HD5F: 2000 frames saved (or similar) and the HDF5 file grows on disk.Beacon detected and Start main client txrx loop.plot_hdf5.py produces pilot and uplink IQ waveforms, a 16QAM constellation, and EVM/SER values. The source tutorial example achieved EVM = 0.88 % and SER = 1.45 %.Example output plots (source images — re-upload needed):



Stop both the client and BS processes with Ctrl-C, then power off the nodes:
omf tell -a offh -t node21-1,node8-7
omf tell -a offh -t [23,1..8]
No omf save is needed unless you modified the disk image.
| Symptom | Likely cause | Fix |
|---|---|---|
uhd_find_devices returns no device on node21-1 |
CTRL interface MTU not set or routing issue |
Run ifconfig CTRL mtu 9000 up and verify ping 10.10.23.1 from node21-1 |
| B210 not found on node8-7 | USB 3 not enumerated or serial mismatch | Run uhd_find_devices without --args to list all visible devices; check dmesg for USB errors |
Client does not print Beacon detected |
B210 branch mismatch or X310s not yet running | Ensure the B210 uses the USRP-B210-New-Sync branch; start the client before or shortly after the BS |
| HDF5 file is empty or stops at 0 frames | BS cannot reach X310s | Verify 10 GbE connectivity (ping 10.10.23.1 from node21-1) and MTU/buffer settings from Step 1 |
plot_hdf5.py raises KeyError on TX data |
Separate-terminal run without patching hdf5_lib.py |
Apply the manual_file patch described in Step 8 |
Author(s): COSMOS team (original authors: Zhenzhou (Tom) Qi, Tingjun Chen — Duke University) · Last verified: Not re-tested (migrated 2026-06-20) · Tested image/release: ubuntu2004-uhd4.1-gr3.9.ndz · Tags: massive-mimo, channel-sounding, usrp, x310, b210, renew, hdf5, ofdm