Hi everyone,
In PYNQ 3.1 we introduced PYNQ.remote, which lets you run PYNQ and interact with a board directly from your host computer, instead of on the board itself. Our latest release extends PYNQ.remote with RFSoC support, so you can now configure the RF Data Converters, program the clocks, and stream RF data, all from Python running on your laptop. There’s no need for SSH or on-board Jupyter; just point PYNQ.remote at your board’s IP address and go.
This tutorial walks you through that new functionality using a popular spectrum analyser design from the University of Strathclyde, running on the RFSoC4x2. You’ll learn how to build a PYNQ.remote image for the RFSoC4x2, install PYNQ on your host computer in a virtual environment, and run the overlay remotely with PYNQ.remote.

Part 1: Building a PYNQ.remote image
The board can’t talk to your laptop until it’s running an image that includes the PYNQ.remote server. Creating that image is what this part is about.
Building a PYNQ image requires having a lot of system tools. Rather than ask you to install all of them by hand, PYNQ provides a Docker container that already has everything inside. We build the image inside that container, so your own machine stays clean.
What you’ll need
- An Ubuntu 22.04 host machine
- Vivado 2024.1 and PetaLinux 2024.1 installed
- Docker installed
Note: PetaLinux only runs on Linux, so this build step can’t be done on Windows or macOS. Once the image is built, PYNQ.remote works with Linux, Windows, and macOS.
1. Set up and run the PYNQ Docker container
First, tell the scripts where your Xilinx tools live. These two paths point at your Vivado/PetaLinux install folders; we’ll reuse them a few times, so we set them as environment variables:
export XILINX_TOOLS_PATH=/path/to/Xilinx/tools # e.g. /tools/Xilinx
export PETALINUX_TOOLS_PATH=/path/to/petalinux # e.g. /home/$USER/petalinux
Make a working folder and download the PYNQ source. We need the PYNQ repository because it contains the sdbuild folder; that’s where the Dockerfile and all the image-building scripts live:
mkdir rfsoc4x2-pynq-remote-image-build
cd rfsoc4x2-pynq-remote-image-build
git clone https://github.com/Xilinx/PYNQ.git PYNQ
Build the Docker image. This creates a container with all the build tools baked in. The --build-arg flags tell it to use your username and IDs, so any files it creates are owned by you, not by root:
cd PYNQ/sdbuild/
docker build \
--build-arg USERNAME=$(whoami) \
--build-arg USER_UID=$(id -u) \
--build-arg USER_GID=$(id -g) \
-t pynqdock:latest .
cd ../..
Start and run the Docker container. The -v flags mount folders from your machine into the container: your Xilinx tools (read-only, hence :ro) and your current working folder, so the build can see all of them. --privileged is needed because building the SD-card image involves creating and mounting disk partitions (via loopback devices), operations that require elevated privileges the container doesn’t have by default. After this command, your shell prompt is now running inside the container:
docker run --init --rm -it \
-v $XILINX_TOOLS_PATH:$XILINX_TOOLS_PATH:ro \
-v $PETALINUX_TOOLS_PATH:$PETALINUX_TOOLS_PATH:ro \
-v $(pwd):/workspace \
--name pynq-sdbuild-env \
--privileged \
pynqdock:latest /bin/bash
You should see something like this, with your own username

2. Build the image (from inside the container)
You’re now at a shell inside the container. It’s a fresh shell, so nothing is set up yet. We’ll do that step by step.
Point at your Xilinx tools again. We need to set the environment variables pointing to your tool paths, inside the Docker container this time:
export XILINX_TOOLS_PATH=/path/to/Xilinx/tools
export PETALINUX_TOOLS_PATH=/path/to/petalinux
Source Vivado and PetaLinux. These source lines run the tools’ setup scripts, which put Vivado and PetaLinux on your PATH so the build can actually find and run them:
source $XILINX_TOOLS_PATH/Vivado/2024.1/settings64.sh
source $PETALINUX_TOOLS_PATH/2024.1/settings.sh
Download the RFSoC-PYNQ source and the board’s BSP. RFSoC-PYNQ holds the board designs and the build recipe. The BSP (Board Support Package) is the board vendor’s starting point for the board’s Linux, and wget downloads it and drops it exactly where the build expects it. Each board has its own BSP; for a different board you’d swap this file and the BOARD= value later:
git clone --recursive https://github.com/Xilinx/RFSoC-PYNQ.git
cd RFSoC-PYNQ
wget https://github.com/RealDigitalOrg/RFSoC4x2-BSP/raw/refs/heads/master/bsp_releases/RFSoC4x2_2024_1.bsp -O boards/RFSoC4x2/RFSoC4x2.bsp
Start the build. make pynqremote is the target that produces a PYNQ.remote image (as opposed to a plain PYNQ image), and BOARD=RFSoC4x2 tells it which board to build for:
make pynqremote BOARD=RFSoC4x2
When it finishes you’ll have an .img file, that’s your SD-card image.
3. Flash the SD card and boot the board
Flash the .img you just built onto an SD card, using tools like BalenaEtcher. If you run into any trouble, the official Writing the SD card image guide walks through it in detail.
Then set the board up physically, as shown below:
- Insert the flashed SD card, and make sure the board’s boot mode is set to SD.
- Connect power and switch the board on.
- Connect the Ethernet cable to the same network as your laptop.
- Connect the USB cable, this gives you a serial console into the board, which we’ll use in the next step to set its IP address.
4. Give the board an IP address
Your laptop finds the board over the network by its IP address, so the board needs one on the same subnet as your laptop. The PYNQ.remote image is a minimal system and it doesn’t configure network automatically, so we need to configure this over the serial console you connected via USB.
Open the serial console. When the board is plugged in over USB it shows up as a serial port on your laptop (/dev/ttyUSB* on Linux, a COM port on Windows). Open it with any serial terminal, screen, PuTTY, or the terminal built into your IDE, at a baud rate of 115200.
Log in. At the prompt, log in with the default PYNQ.remote user:
username: petalinux
The first time you log in you’ll be asked to set a password, choose one and remember it, you’ll need it for the sudo command below.
Assign the IP address. Now give the board’s Ethernet interface (eth0) a static address on your laptop’s subnet:
sudo ip addr add 192.168.2.99/24 dev eth0
Make sure to pick an address that matches your laptop’s network (if your laptop is 192.168.2.x, this works as-is; otherwise adjust it). You’ll use this same IP in Part 2 to point PYNQ.remote at the board.
Check it worked. From your laptop, confirm you can reach the board:
ping 192.168.2.99
If you also need to configure the network on your laptop’s side, use this guide from the PYNQ documentation: Assign a static IP address.
Note: An address set this way is temporary and is lost when the board reboots. To make it permanent, configure it in the board’s network settings. In a future release, this step will be handled automatically, so no manual IP setup will be required.
Part 2: Running the demo from your laptop
This section deals with running PYNQ.remote on the host side. Everything from here runs on your computer, not on the board, and works on Linux, Windows, or macOS.
We’ll use uv from Astral, a fast, modern tool for managing Python versions, projects, and packages. It creates an isolated environment for the project automatically, so nothing clashes with the rest of your system, and it records every dependency in a pyproject.toml file for you. Each step below gives the command for Linux / macOS and for Windows (PowerShell), run whichever matches your machine.
1. Install uv
Linux / macOS:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
2. Create the project
uv init --bare sets up a minimal project (just a pyproject.toml). Passing --python "==3.11.*" locks the project to Python 3.11. uv will create and manage the virtual environment for you, no manual activation needed.
uv init rfsoc-remote --bare --python "==3.11.*"
cd rfsoc-remote
3. Add PYNQ and the dependencies
Next, we need to tell PYNQ’s installer to build in remote mode and skip the native, board-only components it would otherwise try to compile (those only make sense on the board itself). We do this by setting the PYNQ_REMOTE environment variable.
Linux / macOS:
export PYNQ_REMOTE=1
Windows (PowerShell):
$env:PYNQ_REMOTE = "1"
Your system is now set up to install PYNQ. uv add records each package in your pyproject.toml and installs it into the project’s environment. We add PYNQ first, then we add the overlay’s package dependencies.
uv add "pynq @ git+https://github.com/Xilinx/PYNQ.git"
uv add "setuptools<78" "pycparser<3" numpy scipy ipywidgets plotly anywidget matplotlib pillow ipython jupyterlab voila
Note: This guide uses the latest stable version of PYNQ.remote by installing from the
masterbranch. For bleeding-edge PYNQ.remote changes, try installing from thepynq-nextbranch instead by runninguv add "pynq @ git+https://github.com/Xilinx/PYNQ.git@pynq-next".
4. Install the demo
The rfsoc_sam installer requires two more environment variables: BOARD, which lets it pick the right board-specific overlay (the FPGA design) and drivers, and PYNQ_JUPYTER_NOTEBOOKS, which tells the installer which folder to install the example notebooks.
Linux / macOS:
export BOARD=RFSoC4x2
export PYNQ_JUPYTER_NOTEBOOKS=$PWD/notebooks
mkdir -p "$PYNQ_JUPYTER_NOTEBOOKS"
Windows (PowerShell):
$env:BOARD = "RFSoC4x2"
$env:PYNQ_JUPYTER_NOTEBOOKS = "$PWD\notebooks"
mkdir -Force "$env:PYNQ_JUPYTER_NOTEBOOKS"
Once those variables are set and we’ve made the directory, we can now install the rfsoc_sam overlay using uv.
uv add "rfsoc-sam @ git+https://github.com/strath-sdr/rfsoc_sam"
5. Point at the board and launch
PYNQ_REMOTE_DEVICES is the final piece of the puzzle: it’s how PYNQ.remote knows which board to talk to. Set it to the IP you gave the board in Part 1, then use uv run to launch JupyterLab inside the project’s environment (no activation step needed):
Linux / macOS (set the variable inline, just for this command):
export PYNQ_REMOTE_DEVICES=192.168.2.99
uv run jupyter-lab
Windows (PowerShell) (PowerShell needs the variable set on its own line first):
$env:PYNQ_REMOTE_DEVICES = "192.168.2.99"
uv run jupyter-lab
Run it!
In JupyterLab, open rfsoc_spectrum_analysis.ipynb and run through the cells. The first part of the notebook shows how to physically set up the board for rfsoc_sam (including a photo of the required connections), follow that before running the rest, then execute the code. Every overlay load, clock configuration, and DMA transfer is quietly forwarded to the board over gRPC. The notebook looks and behaves exactly as if it were running on-board, except it’s your laptop doing the driving.
Here’s the spectrum analyser running, entirely from a host computer:
Notes and troubleshooting
- Failed to connect to remote device or no device found? Check you can reach it first:
ping 192.168.2.99from your laptop. If that fails, the board’s IP isn’t set correctly, revisit Part 1, step 4 (Give the board an IP address). Also make sure your laptop and the board are on the same subnet. The static IP guide in PYNQ’s documentation explains this. - Other boards. This demo is tested on the RFSoC4x2. The
xrfdcandxrfclksupport should also work on the ZCU208 and ZCU111.
Resources
- RFSoC-PYNQ: GitHub - Xilinx/RFSoC-PYNQ: Python productivity for RFSoC platforms · GitHub
- PYNQ: GitHub - Xilinx/PYNQ: Python Productivity for ZYNQ · GitHub
- Demo (rfsoc_sam): GitHub - strath-sdr/rfsoc_sam: RFSoC Spectrum Analyser Module on PYNQ. · GitHub
PYNQ.remote is still in active development, and more demos and posts are on the way.
Thank you all,
The PYNQ team.

