This web page is meant to be an introduction to how to use the RealDigital 4x2 RFSoC development
board. This work was done thanks to a collaboration between me,
Drew Baden, and
Alessandro Restelli
from JQI, and in fact Alessandro
taught me most of what I know about this subject.
This board is a "lite" version of the more powerful boards (e.g.
AMD ZCU208). The focus is on the firmware in the Xilinx part,
not on the PYNQ software, but of course
you can't use one without the other so there will be some useful information on PYNQ here.
A good place to start for a PYNQ tutorial might be
here.
An excellent online book that delves nicely into software radio can be found
here, where you can download the
latest version. Or you can grab
this one that dates from 2023.
I'm using Vivado 2024.2 here, although most of the base project that AMD supplies is made with
Vivado 2022.1. But I found 2024.2 works fine, and it's more mdern.
You can find the reference manaual (revision A5) for the 4x2 board
here. A schematic can be found
here.
The 4x2 has 2 DACs that run at 9.85 GSps
(actually, the max is more like 7 GSps, more on that below)
and 4 ADCs that can run at 5 GSps.
The more powerful boards have 8 DACs and 8 ADCs, different (although not necessarily better)
internal clocking, etc.
The data converters allow you to modulate a carrier wave with a lot of flexibility.
The architecture is outlined in this figure:
As you can see from the diagram, there are the following subsystems (which is why this is called a
System on Chip, or SOC:
The inputs to the Digital Up Converter (DUC) on the Zynq RFSoC image above are
the modulation waveforms that you can send into the device from
the ARM chip using DMA on the AXI bus (which is a pretty simple bus, derived from the old Wishbone
bus). The DACs have digital up-convertors (DUC) for taking input at some frequency and producing
an output that can change at 9.85GSps max. The ADCs have digital down-convertors (DDC) that can produce
data at any sampling rate.
The RealDigital portal to the RFSoC 4x2 is
here.
Scroll down to the bottom of that page and click on the "Resources" tab, which shows
you this:
Back to top
All Jupyter notebooks are kepts on the ARM chip, so when you edit them, they reside there.
You will have to periodically backup by hand (until we find a better way). To do this:
I keep a copy on my dropbox account in case anyone needs it.
The RFSoC chip has a "processing system" part, or PS, and a "programmable logic" part, or PL.
The PL is the FPGA, and the PS consists of 2 processors:
The 32-bit R5F chip is a real-time processor, designed for low-latency tasks like real-time control
loops, interrupt handling, and fast responses to hardware events. It runs without a
general-purpose operating system which means no scheduler jitter or overhead. So you use the R5F
for real-time DAQ, hardware handshaking, etc.
Back to top
Clocking on the 4x2 is summarized in the following diagram, which you can find on page 8 of the
RFSoC 4x2 Reference Manual.
There are a great many clocks floating around on this board, but pretty much all
of these clocks start in the Skyworks Si5395
chip (SI5395B-A13886-GM). This is a clock multiplier and jitter attenuator, driven by a 48MHz crystal.
It can generate a
range of frequencies, with parameters programmed by the user stored in internal ROM. It looks like
this chip is already programmed ("factory programmed"). It has 12 clock outputs, any-frequency
synthesis architecture, jitter roughly 70-100 femtoseconds, an integrated jitter attenuation,
is I2C or SPI programmable.
The Si5395 has 2 main jobs:
The 10MHz LVDS clock output from the Si5395 is the most critical clock for the RF circuitry.
This clock that is fed
into the TI LMK04848 (LMK) chip, which also has an
input from a 160MHz
crystal oscillator made by Abracon.
The crystal oscillator has very low phase noise, so the Si5395 chip phase locks it to the 10MHz reference
clock to provide a reference clock for the RF converters.
The LMK chip has 2 output clocks that go into the FPGA fabric:
The LMK chip also has 2 sets of outputs where each pair feeds a
TI LMX2594clock synthesizer (LMX)
chips. These chips provide reference clocks for the RF converter circuitry that has the ADC and DAC components.
Of the pair of outputs, one is a 245.76MHz frequency reference used continuously by the LMX chip,
and the other is a phase alignment signal, usually a pulse that is asserted after powerup and phase
locking by the LMX chip.
To make the reference clock for the RF Converter circuitry,
each LMX chip has an internal voltage controlled oscillator (VCO) built from an LC circuit
where the capacitance derives from a diode (called a varactor) whose capacitance changes with voltage.
So suppose you want an output that is 4.9152GHz (20 times the incoming 256.52MHz)
reference clock for the RF converter
(for the ADC or DAC). (Note: the RF converter uses this reference clock to make the sampling clock that
it needs!) The software figures out all of the dividers that are needed, and programs the
LMX chip. The LMX actually has several VCO's for different ranges, so what it does is to first do a
calibration that picks the closest one, use the divider the software loaded in, divide down the VCO, and
then phase lock it to the incoming signal from the LMK to produce the right output signal.
The other magic happens in the RFConverter. So say you want to run an ADC at a frequency of
something like 3.558GHz. You would load this frequency into the RF Converter block inside
Vivado and tell it that the reference clock will be 491.52MHz. The software will figure out
how to multiply and divide that clock to get you 3.558GHz, and load those numbers into the
file to download to the RFSoC. If the rate you want isn't possible for some reason, the
software will tell you! For instance, to start with 491.52MHz and get 3.558GHz, that ratio
is 3558/491.52=7.23828125. That's not an integer, but the RFSoC can still generate that
frequency by starting at 7 and adding 61/256 to get the right multiplier of 491.52MHz.
Clock precision
If you look at the Si5395 data sheet, you will
see that the 48MHz oscillator has a frequency that varies by ±15ppm. This uncertainty is a due to
cutting and manufacturing uncertainties, and is constant over the lifetime of the crystal.
This means that the actual frequency can be as much as 48,000,720Hz (+15ppm) but will always have
that value every time you power up the board.
In addition to the initial ±15ppm, there are variations in the frequency
due to temperature, but these crystals are usually somewhat flat in temperature variability around
25°C. For this crystal, the temperature range of -40 to +85°C increases the frequency
uncertainty to ±25ppm. There is also a small variation of a few ppm due to aging, and even
smaller ones (load capacitance, humidity, etc). But for a board sitting in a lab at constant
temperature, one would expect a frequency due to just the initial manufacturing of ±15ppm.
That means that if you want
to use this system to generate a 1GHz tone, you can expect it to be within around 15kHz in a
controlled temperature environment.
To get the 4x2 to generate a tone with a more precise frequency, you have to use an external 10MHz precision
clock connected into the "CLK_IN" SMA input on the board. Then you can use a python Jupyter notebook
to use that clock as the reference. To do that, check out the following code. This sets up a class to overlay
to load any file you want (here it's called 'cyclic_clock.bit'), invokes the class via gen=Gen(), and tells
the system to use the external clock via gen.set_external_rf_clks(). It also sets the processor clock
to 250MHz (I think the default is 100 MHz).
Back to top
Python code
Here is an example of how to set up the clocks in the Python PYNQ environment:
Back to top
In the top image you will see a micro SD card input at the top left of the 1st 3 push buttons.
This micro SD card holds the image of the Linux OS that runs in the A53 ARM chip, implementing
the PYNC environment.
All of the images for all of the boards can be found
here
The RealDigital 4x2 board ships with an SD card that is supposed to contain a working image.
When we bought one in summer 2024, we plugged in the micro SD card that comes with the kit,
but it didn't work at all. So we downloaded and installed the image
from the web site, version v3.0.1 (codenamed "Belfast").
This image was built with Vivado 2022.1 in mind, which means that all of the base projects
that go with the tutorials use the Vivado 2022.1 toolchain.
You can download it from the
PYNQ site.
It's a 2.26GB zip file, so
unzip, and it will create a file called "rfsoc4x2_v3.0.1.img" that will be around 10GB.
To install onto a micro SD card, best to use a Windows 11 machine. Download the app called
Win32DiskImager which you can get at a lot of sites, but I got it from
the official site. "
Plug the micro SD card into the laptop somehow, it will show up as a Windows disk,
and use Win32DiskImager to write it to the SD card.
In 2025, we bought another 4x2 board and it came with an SD card, but instead of using
it, we downloaded the image from the web site, but we kept getting USB errors after bootup.
After lots of exchanges with the kind people at RealDigital, we found out that there had
been a slight change to the USB3 chip on the new versions of the 4x2, and that this change
necessitated a new version of v3.0.1. Then I found that they had actually shipped a
micro SD card that had the latest version of v3.0.1 with the USB3 fix! But they also
sent me a link to that version, which is
here. I'm not exactly sure whether v3.0.1 on the
PYNQ site has the old or new version
of v3.0.1.
Now, there's a new version on that site, v3.1.1. This version is codenamed "Carlisle",
and differs from v3.0.1 in that v3.1.1 goes with all of the project overlays that were built
with Vivado 2024.1, so you get the advantage of 2 years of AMD toolchain advances baked into
the bitstreams and kernel. V3.1.1 also fixes a bug with the transmitter gain, although
AMD didn't really document exactly what the bug was but we did find a problem with setting
the gain in software, so probably that is what has been fixed.
Also, v3.1.1 introduces a beta release of PYNQ that allows for remote access workflows, and
a new C++ integration path but I haven't looked much into these. Accessing this
new version is the same as v3.0.1 described above: download and flash onto a SD card
using Win32DiskImager.
If all goes well, after loading the card,
you can then put the SD card into the board SD slot and plug in the USB3
connector (middle right side of board) and plug the USB-A side into your computer. Plug in
the power cord (upper right side of board) and
boot it up the board by toggling the power switch.
When the board boots, wait around 30 seconds, then you should see on the LCD display
the following:
You can also ssh into the RFSoC and have the full Linux kernel capabilities. Probably
best to just use the terminal window on a MacOS machine and ssh in, or from Windows
use PowerShell.
Back to top
If you want to see the typical stdout stream to a terminal while the thing boots up (and this
can be useful for when the thing shuts down), you can do the following:
Back to top
The RealDigital 4x2 board has a ZYNQ Ultrascale+ RFSoC ZU48DR chip made by AMD/Xilinx, and that
chip has electronics for 8 DACs and 8 ADCs. However, the RealDigital RFSoC board only allows
the use of 2 DACs and 4 ADCs. Each of the 2 DACS on that board
can be run with sampling times of up to 9.85 GSps,
and 4 ADCs with sampling times of 5 GSps.
Having ADCs that can digitize so fast allows radio processing to all be done in software.
The incoming signal, consisting of a carrier frequency in the GHz range with lower frequency
modulation, can be digitized directly, and Fourier analyzed to extract the modulation, with no
need for the usual heterodyne or superheterodyne downmixing.
The RF Converters on the ZYNQ chip are fully
integrated into the FPGA as described in the following figure:
NCO stands for numerically controlled oscillator, which means the carrier wave, and the converter
will generate both the in-phase (I, a cosine) with the quadrature (Q, sin) carrier wave. For the
DAC, you input a waveform, with separate I and Q parts, and the converter will multiply the carrier
I with the waveform I, same with Q, and mix the two together.
For the ADC, it will mix the carrier (from the NCO) I and Q with the incoming waveform to recover
the modulation I and Q parts.
The NCO is worth understanding: how does it produce a sine and cosine with arbitrary frequencies
without tuning an analog oscillator? The way it works is complicated, but you can get a good idea from
the following: imaging a lookup table (LUT) that has $2^{40}$
addresses, and at each address is a value such that if you plot that value for all
$2^{40}$ addresses, you'd see a pretty high resolultion cosine wave. Then you have a
40-bit register (called a "phase accumulator")
that points to one of the values in the table, and that value is
taken out and shoved into the phase accumulator DAC.
If you had a clock that would run at 9.85 GHz, and every tick of that clock you increment
the phase accumulator by 1/2 * $2^{40}-1$,
then the analog output from the DAC would change at 9.85 GHz, from $+$ to $-$ and generate
a wave at 1/2*98.5 GHz = 4.925 GHz. If on the other hand you increment the phase
accumulator by 1, then it would take $2^{40}$ ticks to go through an entire cycle,
which gives you an output frequency of $4.925 GHz/2^{40} = 4.48 mHz$ (milliHerz),
and that would look pretty continuous since the phase would only be changing by 1 part
in $2^{40}$ on each clock cycle.
Now let's say that you wanted the output of the DAC to change slower such that the
outgoing analog carrier wave was at, for example, 100MHz. You can do this easily by
changing the value you increment the phase accumulator. So to get a 100MHz tone,
you increment the phase accumulator by a value of $(100\times 10^6)/(4.925 \times 10^9)
\times 2^{40}$ which comes to $2.233\times 10^{10}$, which in hex is $532AE24F0$. Voila,
you can generate any frequency you want to within 4.48 mHz resolution.
This is basically how it works, except that in actuality it employs a great deal of tricks.
For instance, the cosine wave is very symmetric over the 4 quarters of a cycle, and there
are ways to reduce the number of values in the LUT by a great deal (imagine the graduate
students with theses that worked on this problem!).
Back to top
On the board there is a 30pin 2-row connector at the bottom right with the labels
"PMODB" on the left and "PMODA" on the right.
The following table gives the mapping for what pins on the connector are connected to
what FPGA IO pin number:
The 30-pin connector is constructed so that it looks like 2 "standard" 12-pin
PMOD connectors (that's what PmodA and PmodB label) separated by 2 rows of 3 pins
in between. That standard, which is probably
not universal but seems to be generally accepted,
has 2 rows of signals, with 4 signals in the first right-most pin followed by ground
and power (usually 3.3V). The pins are usually labeled with the top right ("Pin 1") as pin pin 1,
followed by 2, 3, 4, followed by ground on pin 5, and power on pin 6.
The bottom row starts with pin 7, with pin 11 as ground and 12 as power.
It's probably not so important to make this 30-pin connector look like 2 PMOD
connectors with 6 extra signals, since what we will probably be using this connector for
is to bring out signals for debugging. So let's make a labeling scheme that matches
a 22-bit vector (call it PMOD[21:0]) that gets translated onto this connector such that
PMOD[0] is the upper right, and PMOD[11] is the lower right, with numbers increasing to the
left and skipping the 4 grounds and 4 power pins.
So the connector looks like this:
The contstraints file that you will have to edit should have the following (the last 3
constraints are just generally good things to enable):
Each of these data pins are connected in a pretty funky labeling on the schematic,
because they want to
be able to accommodate 2 12-pin PMOD connectors, and in such connectors, the pin
assignments run 1-6 on the top, right to left, then 7-12 on the bottom, left to
right. But let's make it simpler and remap them by defining the PMOD 30-pin connector
that has pin 1 in the upper right, with odd number pins on the top and even numbers
on the bottom. That would give us grounds on pins 9 and 10 on the right and 27, 28
on the left, and 3.3V on pins 11 and 12 on the right and 29 and 30 on the left.
To send a signal to one of the data pins, you first have to create the output port
on the project diagram. First, right click somewhere on the diagram,
and select "Create Port". In the window that pops up, give it a name, change the
"Direction" to Output, and change the "Type" to Data or Clock, the 2 most common
outputs. Then hit OK. That will instantiate an output port (this one is named
"test") that will look like this:
Next, we have to instantiate an output buffer, something that will take a signal
from somewhere and drive the output. Right click on an empty part of the diagram,
and select "Add IP" (or click the boldface $+$ sign on the menu bar under
"Diagram") and search for "Utility Buffer", and double click. That will bring up
a new circuit that will look like this:
To configure the buffer, double click and you should see a configuration window
that looks like this:
There are all kinds of buffers, and you can look them up but probably all you
will need for simple outputs are either "BUFG". I've found this to be adequate
for signals, even clocks as long as they aren't super fast (GHz).
The last thing you do on the diagram schematic entry
is to use the mouse to click on the signal you want to
output, and drag the connection to the buffer input, and likewise for the buffer
output to your output port.
Each of the 22 pinouts on the PMOD connector are connected to an IO pin on the
FPGA, The relevant table is reproduced below:
So now you have an output port, and in our example, the name is "test". This port
exists in the PL, and you have to connect to the right IO pin on the FPGA.
, and each
of these 22 pinouts are connected to
Those connections
Back to top
There's an IP called "Processor System Reset" that is quite useful, and you will see a lot
of these in the project. This IP is used to generate reset signals for the various
components in a project, and ensures that that resets are all synchronized correctly
during startup and when resets occur. It is also very useful for simulation, as most
simulation tools won't tell you how a wire or reg changes state without knowing its initial
value, which is determined via the reset.
Back to top
When we want to send a waveform from the ARM chip to the RF converter, we use DMA
(explained elsewhere). The DMA engine sends data into the FPGA, which sends it into
a FIFO at a high speed. The output of the FIFO will be a 32 bit word, with the lower
16 bits being the input to the I port on the RF converter and the upper 16 bits
being the input to the Q port.
To make a new FIFO, right click on any empty space on the design, and select "Add IP...".
In the "Search" window type "FIFO generator" and select "FIFO Generator" and hit return.
That brings up a new "FIFO Generator" block. Double click. It should look like this:
Here's how you configure it in each of the tabs:
Now let's discuss the inputs and outputs. But first, remember that we want this FIFO to be
written into by the DMA engine, and read out by the RF Converter. So we want the FIFO to have
separate read and write clocks.
The outputs are all in the context of the AXI bus, which means that there's internal
logic you don't have to write to do all of the bus business. Click on the + sign
next to "M_AXIS" to expand the list of output ports, which are:
Back to top
The RealDigital 4x2 is a complex board with lots of parts that Vivado needs to know about.
All of these can be loaded into Vivado so that you can select the entire board in the
"Default Parts" panel when you build the project. To do this you do the following:
Back to top
There are 2 basic ways to save the project in Vivado: you can archive the project into a zip
file, or you can create a tcl script that can be used to regenerate the project (explained below).
Archiving is good for preserving the important project files into a zip file, and storing it. The
files that get archived are the output of the Vivado build process (synthesis, implementation, and
bitstream generation). You make the zip archive by going through the File/Project/Archive menu.
There used to be a way in the program ISE, before Vivado, to "clean" unnecessary files but Vivado
doesn't seem to have such a command. There might be a way to do it but it's probably overly
complicated. Anyway by archiving you can copy the project to another computer, unzip, and then
use Vivado on that computer.
There are 2 issues with archving: the zip file can be large; and it's difficult to use the zip file
on any version of Vivado other than the version that made the project that was zipped. The alternative
is to use tcl, the scripting language that Vivado uses.
Go into the tcl command line (click on "Tcl Console" tab at the bottom panel of Vivado), and
issue the following command:
will produce a file with filename name.tcl that contains a script that can be used to rebuild
the project from scratch by any version of Vivado (within reason, has to support all the IPs etc).
So for example, say you build the project with Vivado 2024.2 and want to rebuild it using Vivado 2022.1
on the same computer. You make the name.tcl usign 2024.2, then run 2022.1, hit
"Create Project", set the project name and location in the "New Project" panel and hit Next. Skip
the "Project Type" panel (just hit Next) and "Add Sources", and "Add Constraints (optional)" panels
until you get to the "Default Part" panel. Here you click on "Boards", and search for 4x2 in the
Search window (about halfway down). Click on "Zynq UltraScale+ RFSoC 4x2 Development Board" when
that pops up (see 4x2parts if you don't see the 4x2). Note that clicking
on that has the annoying habit of popping up a browser window, which you can ignore and go back
to Vivado. Hit Next and that brings you into the Summary window. Hit Finish. That runs Vivao
where you will see all the familiar panels. Click on "Tcl Console" towards the bottom, and that
brings up the console and a text window. Type:
(or change "./" to where you put name.tcl) and it will build the project for you. Voila.
However, there is a caveat: any source file must be copied with the .tcl file and input by hand.
That means any verilog or vhdl source, and any constraint file that sets ports etc (*.xdc files).
Back to top
The 4x2 ARM chip that is running all the PYNQ software is the 64 bit chip, running Ubuntu Linux.
If you move the power switch on the board from ON to OFF to power it down, you could catch it in
a state of doing something that will result in the SD card becoming read-only on the next powerup.
So best to open up a terminal window in the Jupyter notebook (click on the "New Launcher") button,
which is a big blue rectangle with a "+" sign) and that brings up a new Launcher. Click on
the "Terminal" icon, that will log you into the ARM chip. Type
and wait a few seconds, then you can safely power off the board.
Back to top
There are a handful of good tutorials out there, and I will try to collect them here.
Table of Contents
Introduction

Not shown are the SMA inputs and outputs for the analog signals and external clocks. Also not
shown are 4 GBytes of 64 bit 2.4 GHz DDR4 connected to the PS, and a similar number connected to
the PL. These are the 8 rectangular chips on the board, 4 above (PL) and 4 below (PS)
the FPGA (covered
by a fan in the top image here).
The inputs and outputs and SMA clock and sync in ports are the SMA connectors on the left in
the top image, and those signals come in single ended on
SMA connectors, and are converted to differential on the inputs.

Backup from Zynq
/home/xilinx/jupyter_notebooks/ADMX
scp -r xilinx@192.168.3.1:/home/xilinx/jupyter_notebooks/ADMX .
It will prompt you for the password, which is "xilinx" and after you enter that, the
entire directory will be backed up.
Processors
The 64-bit A53 chip is the application processor, running a full Ubuntu Linux kernel and supporting
the PYNQ environment. It handles high-level tasks, for instance the Jupyter notebooks, file I/O,
networking, etc. It is optimized for throughput and general-purpose computing and is the one
you interact with when you SSH in, or use the Jupyter notebook to run Python scripts
or to open up a terminal window.
So you use the A53 for the Python data analysis, FPGA loading and configuration, DMA from memory
into the FPGA and RF Converter, etc.
Clocking

and the clock outputs include:
RF Converter clock
Why such arbitrary frequencies like 122.88MHz and 7.28MHz? It's because in the world of telecom,
especially LTE and 5G, 491.52MHz is one of the standard clock frequencies. The reason for such
odd numbers: LTE starts with a bunch of subcarriers spaced by Δf = 15kHz, and if there are
2048 subcarriers, then the sampling rate has to be 2048×15kHz=30.72MHz. Then 7.68MHz is
30.72/4, and 122.88MHz is 30.72*4, and so on. Welcome to the world of telecom, which is really
what the RFSoC boards are built for (not to find axions!!!)
from pynq import Overlay, MMIO, allocate
from xrfdc import RFdc
from pynq.overlays.base import BaseOverlay
import time
import xrfclk
import numpy as np
from pynq import Clocks
class Gen(BaseOverlay):
def __init__(self,NCO=False,ext_clock=True):
Overlay.__init__(self,'cyclic_clock.bit')
if self.is_loaded():
self.i2c_initialized = False
self.display_port_initialized = False
self.radio = self.usp_rf_data_converter_0
self.xrfdc_alias = MMIO(self.ip_dict['usp_rf_data_converter_0']['phys_addr'],
self.ip_dict['usp_rf_data_converter_0']['addr_range']).array
self.init_rf_clks()
self.freq_a = 0.01
self.radio.dac_tiles[2].blocks[0].MixerSettings['EventSource'] = 0
self.freq_b = 0.01
self.radio.dac_tiles[0].blocks[0].MixerSettings['EventSource'] = 0
def set_external_rf_clks(self):
for lmk in xrfclk.lmk_devices:
with open(lmk['spi_device'], 'rb+', buffering=0) as f:
data = b'\x01\x47\x0A' # Only works for LMK0482x series
f.write(data)
def set_internal_rf_clks(self):
for lmk in xrfclk.lmk_devices:
with open(lmk['spi_device'], 'rb+', buffering=0) as f:
data = b'\x01\x47\x1A' # Only works for LMK0482x series
f.write(data)
@property
def freq_a(self): # MHz
return self.radio.dac_tiles[2].blocks[0].MixerSettings['Freq']
@freq_a.setter
def freq_a(self,freq): # MHz
self.radio.dac_tiles[2].blocks[0].MixerSettings['Freq'] = freq
self.radio.dac_tiles[2].blocks[0].UpdateEvent(1)
@property
def freq_b(self): # MHz
return self.radio.dac_tiles[0].blocks[0].MixerSettings['Freq']
@freq_b.setter
def freq_b(self,freq): # MHz
self.radio.dac_tiles[0].blocks[0].MixerSettings['Freq'] = freq
self.radio.dac_tiles[0].blocks[0].UpdateEvent(1)
print("Clock before overclocking: "+str(1.0e-6*Clocks.fclk0_mhz)+" MHz")
gen = Gen()
gen.set_external_rf_clks()
Clocks.fclk0_mhz = 250
print("Clock after overclocking: "+str(1.0e-6*Clocks.fclk0_mhz)+" MHz")
print("all done")
Install Linux and boot up
RFSoC-PYNQ
Version 3.0.1 or 3.1.1
Wait another 20 seconds and then the board will try to connect to the computer over
USB3 and establish itself as a web address. You should see something like this on the LCD
display:
IP Addr(usb0):
192.168.3.1
Go to your computer, fire up a browser, and in the url type
192.168.3.1/lab
Log in as user xilinx, password xilinx, (no comma) and you should be good to go.
Getting STDOUT Linux bootup output
/dev/cu.usbserial-8873392300A50
/dev/cu.usbserial-8873392300A51
/dev/tty.usbserial-8873392300A50
/dev/tty.usbserial-8873392300A51
The difference between tty and cu is that tty listens without responding but
cu will respond, so we want tty. Note the 2 tty numbers. I don't know how
to tell which one is the right one, but the numbers will differ by 1 usually.
You will need these numbers below (they won't be the same as above).
RF Converter

Driving PMOD output ports

TOP 3.3V GND AV13 AU13 AR13 AW13 AW14 AW15
AW16 3.3V GND AK17 AJ16 AG17 AF16
BOT 3.3V GND AU14 AT15 AP14 AU15 AT16 AV16 AR16
3.3V GND AK16 AH17 AF17 AF15
Using the 30-pin connector
TOP 3.3V GND 10=AV13 9=AU13 8=AR13 7=AW13 6=AW14 5=AW15
4=AW16 3.3V GND 3=AK17 2=AJ16 1=AG17 0=AF16
BOT 3.3V GND 21=AU14 20=AT15 19=AP14 18=AU15 17=AT16
16=AV16 15=AR16 3.3V GND 14=AK16 13=AH17 12=AF17 11=AF15
set_property PACKAGE_PIN AF16 [ get_ports "PMOD[0]" ]
set_property PACKAGE_PIN AG17 [ get_ports "PMOD[1]" ]
set_property PACKAGE_PIN AJ16 [ get_ports "PMOD[2]" ]
set_property PACKAGE_PIN AK17 [ get_ports "PMOD[3]" ]
set_property PACKAGE_PIN AW16 [ get_ports "PMOD[4]" ]
set_property PACKAGE_PIN AW15 [ get_ports "PMOD[5]" ]
set_property PACKAGE_PIN AW14 [ get_ports "PMOD[6]" ]
set_property PACKAGE_PIN AW13 [ get_ports "PMOD[7]" ]
set_property PACKAGE_PIN AR13 [ get_ports "PMOD[8]" ]
set_property PACKAGE_PIN AU13 [ get_ports "PMOD[9]" ]
set_property PACKAGE_PIN AV13 [ get_ports "PMOD[10]" ]
set_property PACKAGE_PIN AF15 [ get_ports "PMOD[11]" ]
set_property PACKAGE_PIN AF17 [ get_ports "PMOD[12]" ]
set_property PACKAGE_PIN AH17 [ get_ports "PMOD[13]" ]
set_property PACKAGE_PIN AK16 [ get_ports "PMOD[14]" ]
set_property PACKAGE_PIN AR16 [ get_ports "PMOD[15]" ]
set_property PACKAGE_PIN AV16 [ get_ports "PMOD[16]" ]
set_property PACKAGE_PIN AT16 [ get_ports "PMOD[17]" ]
set_property PACKAGE_PIN AU15 [ get_ports "PMOD[18]" ]
set_property PACKAGE_PIN AP14 [ get_ports "PMOD[19]" ]
set_property PACKAGE_PIN AT15 [ get_ports "PMOD[20]" ]
set_property PACKAGE_PIN AU14 [ get_ports "PMOD[21]" ]
set_property IOSTANDARD LVCMOS18 [ get_ports "PMOD[*]" ]
# configure unused IO pins to have internal pull-up resistors
set_property BITSTREAM.CONFIG.UNUSEDPIN PULLUP [current_design]
# enable the over-temperature shutdown feathers
set_property BITSTREAM.CONFIG.OVERTEMPSHUTDOWN ENABLE [current_design]
# compress the bitstream to make it smaller
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
Then we can define a 22-bit vector and map the FPGA IO pins to that vector in
the constraints file, which would look like this:
The pins are organized so that you can put a standard 12-pin PMOD connector into
the right-most pins (PmodA, 1st 6 pairs of top/bottom pins) and the left-most pins
(PmodB, last 6 pairs of top/bottom pins).
That leaves 3 pairs of top/bottom pins in the middle. Each of these pins are routed to
an IO pin on the FPGA, specified on page 16 of the
Reference Manual.



Implementing Resets
AXI Fifo

Then hit OK on the bottom right, and that gives you a FIFO that should look like this:

To configure the FIFO, launch the IP
tool, and find "FIFO Generator". There will be 4 tabs: Basic, AXI4 Stream Ports, Config,
and Status Flags with a 5th tab called Summary.
Importing 4x2 board parts into Vivado
set_param board.repoPaths RFSoC4x2-BSP-2020.2/board_files/rfsoc4x2/1.0/
xhub::refresh_catalog [xhub::get_xstores xilinx_board_store]
Saving the project
write_bd_tcl -make_local -no_ip_version name.tcl
source ./name.tcl
Powering off the 4x2 board
shutdown now
Tutorials
Drew Baden
Last update June 4, 2026
All rights reserved. No part of this publication may be reproduced, distributed, or transmitted in any
form or by any means, including photocopying, recording, or other electronic or mechanical methods, without
the prior written permission of the publisher, except in the case of brief quotations embodied in critical
reviews and certain other noncommercial uses permitted by copyright law.