This web page is meant to be an introduction to how to use the RealDigital 4x2 RFSoC development
board.
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 might be something on PYNQ here.
A good place to start for a PYNQ tutorial might be
here
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, 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.
For the DAC, what the chip does internally is to produce digital values representing
an I and Q periodic wave at whatever
carrier frequency you want, and multiply the I with the incoming modulation I and Q digital values
that come in over the AXI bus.
The modulated I and modulated Q are then added together and
sent to the DAC for digital-to-analog conversion.
(End of Page)
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:
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.
There are a handful of good tutorials out there, and I will try to collect them here.
In the top image you will see an SD card input at the top left of the 1st 3 push buttons.
The RealDigital 4x2 board ships with an SD card that is supposed to be loaded, but it doesn't
work for some odd reason. So you will have to build your own from scratch. Here's a
tutorial on how to do it:
Sometimes you might get an error like this:
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:
When the board boots, wait around 30 seconds, then you should see on the LCD display
the following:
The RealDigital 4x2 board has data converters integrated with the FPGA and the ARM processors.
The converters do everything needed to produce 2 DACs that with sampling times of 9.85 GSps,
and 4 ADCs with sampling times of 5 GSps. These devices are used in the context of software
radio, which means the specialize at mixing signals with carrier waves, as 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!).
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
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.
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:
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:
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).
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.
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 Traumerei.admx0 and after you enter that, the
entire directory will be backed up.
Tutorials
Install Linux and boot up
If all goes well, 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.
diskutil list
Identify the disk (not partition) of your SD card (e.g. disk4). Then unmount
your SD crd byusing the disk identifier to prepare for copying data to it (
mine was "disk4")
diskutil unmountDisk /dev/disk4
sudo dd bs=1m if=rfsoc4x2_v3.0.1.img of=/dev/rdisk4
Note the "rdisk4" as opposed to "disk4". The "r" speeds things up.
dd: invalid number '1m'
That has something to do with GNU coreutils, but anyway in that case you
need to use a 1M block size, so change the "bs=1m" to "bs=1M" and redo the
above command. If that fails, change "rdisk4" to "disk4" and try again.
If that fails, use a PC!
/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, so...
RFSoC-PYNQ
Version 3.0.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.
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 1o=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
Drew Baden
Last update May 7, 2024
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.