A first example on real hardware
This section refers several commands provided by the device drivers. Use the Software and Device Drivers page as a reference while working through this example.
Alright, let the fun begin. Let's start with the simplest possible test: transmit a waveform from SDR
node1, and receive it on
node2. Set up two SDR nodes facing each other, with a few feet spacing between them. Open MATLAB, and navigate to the Release_revA_v1.0 directory.
Open the connection to the SDRs
Run the script
open_sdr.m, which has been shown below.
node1 = sdr();
node1 = node1.zcu111_open('192.168.1.44', 'trx-0002', 101);
node1.tx_blob = zeros(4, 8192);
node1 = node1.piradio_load_cal_factors();
node2 = sdr();
node2 = node2.zcu111_open('192.168.1.45', 'trx-0003', 102);
node2.tx_blob = zeros(4, 8192);
node2 = node2.piradio_load_cal_factors();
This script creates two
sdr objects called
zcu111_open function is used to open all the required sockets to the SDR.
tx_blob is initialized such that the TX waveform for all 4 channels is zeros of length 8192. Note that the zcu111_open function accepts three parameters: a) the IP address of the node (this was set while loading the SD card); b) the name of the transceiver board (this is written on the transceiver board using a label); and c) the figure number in MATLAB that is dedicated to seeing what this particular node is transmitting and receiving. So,
node1 has an IP address of 192.168.1.44, has a name of
trx-0002, and uses MATLAB figure 101 to show what it is up to.
zcu111_wr_data_blob is used to write tx_blob to the FPGA, thereby ensuring that the baseband signals to all 4 TX chains is the NULL waveform.
zcu111_rd_data_blob is used to make a dummy read to clear the receive side buffers (this is not strictly required). Finally,
piradio_load_cal_factors is used to load the calibration factors from file; this will be explained in detail in a separate section on calibration, but ignore this for now.
Configure the Pi-Radio Transceiver Board
Now that the SDRs have been opened, the next step is to configure the Pi-Radio transceiver board (i.e., the LMX2595 LO chip, the HMC6300 up-converters, and the HMC6301 down-converters) on the SDRs. Run the
config_lmx_hmc.m script. this configures the Pi-Radio transceiver board on
node2; only the relevant parts corresponding to
node1 have been annotated and shown below.
% 1. Power down the LMX and HMC chips
node1 = node1.piradio_hmc6300_pdn();
node1 = node1.piradio_hmc6301_pdn();
node1 = node1.piradio_lmx_config('pdn');
% 2. Configure the clocking. Do this twice to make sure the LMX2595 is working correctly.
node1 = node1.piradio_lmx_config('58ghz');
node1 = node1.piradio_lmx_config('58ghz');
% 3. Configure the 6300 and 6301. Set attenutations appropriately.
node1 = node1.piradio_hmc6300_config(9, 'hmc6300_registers');
node1 = node1.piradio_hmc6301_config(9, 'hmc6301_registers');
node1 = node1.piradio_hmc6300_set_att(0, 0);
node1 = node1.piradio_hmc6301_set_att(18, 0, 0);
% 4. Make sure that the node is transmitting nothing. Do a dummy read.
node1.tx_blob = zeros(4,8192);
node1 = node1.zcu111_wr_data_blob();
node1 = node1.zcu111_rd_data_blob();
Refer to the previous section on "Software and Device Drivers" for more information about the various commands issued. In step 1, the LMX and HMC chips are powered down. In step 2, the LMX chip is powered on and configured to provide an LO signal corresponding to an RF center frequency of F =58 GHz. Note that for a desired RF center frequency of F GHz, the LMX is configured to provide an LO of (F/3.5) GHz; the HMC chips will use this external LO to generate the correct LO at F GHz (look at the HMC6300 and HMC6301 datasheets for more information). In step 3, the HMC chips are configured (i.e., powered on) using a default register values. The TX and RF attenuations are also set appropriately. Finally, in step 4, the TX baseband buffer on the RFSoC is cleared (set to all 0s), and a dummy read is performed to clear the RX-side buffers (this step is not strictly required).
Transmit a waveform from one SDR and receive it on another
Run the script
simple_test, which uses one SDR as the TX and another SDR as the RX. This script transmits a single tone from each TX channel sequentially; in each case, the RX will capture the waveforms on all 4 channels, and we will analyze them. The important parts of
simple_test.m are shown below.
% 1. Which is the TX and which is the RX node?
node_tx = node1;
node_rx = node2;
% 2. Initialize the TX waveform
N = 8192;
scToUse = 500;
tx_fd = zeros(1, N);
tx_fd(N/2 + 1 + scToUse) = 1+1i;
tx_fd = fftshift(tx_fd);
tx_td = 1*N*N*ifft(tx_fd);
% 3. Transmit the waveform from TX channel txIndex. All other channels are
% silent. In each case, receive 10 times, and see what we get on each RX
iterations = 10;
for txIndex = 1:4
node_tx.tx_blob = zeros(4, N);
node_tx.tx_blob(txIndex, :) = tx_td;
for iter = 1:iterations
node_rx = node_rx.zcu111_rd_data_blob();
% Add your own code to process rx_blob (size 4 by 8192)
Step 1 initializes the TX as
node1, and the RX as
node2. Step 2 generates a single tone sinusoid using OFDM with N (i.e., the FFT size) set to 8192. All subcarriers are set to 0, except subcarrier with index
scToUse set to 500. The frequency domain waveform is
tx_fd, and the time-domain waveform
tx_td is obtained through a simple IFFT and some scaling. We assume you are familiar with MATLAB's
In step 3, we pick one TX channel
txIndex at a time, and populate
tx_blob corresponding to that
txIndex; all other channels have their time-domain complex baseband waveforms set to 0. These waveforms are written to the FPGA using
zcu111_wr_data_blob. Read the waveforms from the RX node
iterations = 10 times using
zcu111_rd_data_blob. The received complex baseband time-domain waveform is available in
rx_blob, and you can process this in an way that you please. Note that the other loop runs for all 4 TX channels
During execution of this script, turn your attention to Fig. 101 and 102 (recall in the open_sdr script,
node2 were configured to plot out certain information in MATLAB figures 101 and 102 respectively). Expand these figures to a larger size, since they contain many sub-plots.
MATLAB figure 101 (corresponding to
node1) during the execution of
simple_test.The Figure has 4 rows and 4 columns. Row #1 shows the time-domain waveform transmitted by the node (one column for each TX channel). Similarly, row #2 shows the spectrum of the transmitted waveform (linear scale). Row #3 shows the time-domain waveform received by the node; and finally, row #4 shows the received spectrum (there is no signal; only noise). For Row #1 and #2, the columns correspond to TX channels 1-4. For Row #3 and #4, the columns correspond to RX channels 1-4. Note that row #1 and #2 automatically update when
zcu111_wr_data_blob is executed on that node; symmetrically, row #3 and #4 automatically update when
zcu111_rd_data_blob is executed on that node.
Above is an example of what we might observe in MATLAB figure 101 (corresponding to the TX node,
node1) while running
simple_test. Looking at row #1, we can see that TX channels 1,2,4 are inactive, while channel 3 is currently active. Using the MATLAB zoom functionality, zoom into this waveform and observe that it is indeed a simplex sinusoid. Similarly, row #2 shows the spectrum of the transmitted waveform. The X-axis is subcarrier index (-4096 through +4095) and the Y-axis is the power in linear scale. Using the MATLAB data tips, observe that the signal is indeed present on subcarrier index +500. Row #3 and #4 correspond to the RX side of node1, so we will ignore these for now.
During execution of simple_test, you would have observed MATLAB figure 102 (corresponding to the RX node,
node2) look something like this below.
MATLAB figure 102 (corresponding to
node2) during the execution of
simple_test. Observe that row #1 and #2 are flat, indicating that nothing is being transmitted from any of the 4 TX channels on
node2. However, row #3 that contains the time-domain received complex baseband signal shows that some signal indeed being received by all 4 RX channels on
node2. Zoom in to observe the waveforms. Row #4 shows the received spectrum (in dB scale). Use MATLAB data tips to see the dominant peak at subcarrier index +500, with an undesired sideband at subcarrier index -500; we will discuss sidebands and how to calibrate them out in a different section.
Play around with this example
This simple example has shown you how to generate a waveform, transmit it, capture the received waveforms, and visualize them. Try a few different things (and repeat the execution) to get familiar with this simple example. Note that you should run
config_lmx_hmc only once, right at the beginning. After that, you can run
simple_test as many times as you like. Here are things to try on your own:
- Make node2 the TX and node1 the RX.
- Transmit and receive using the same node (say, node1) to measure self interference.
- Change the frequency of the transmitted baseband signal to a different subcarrier.
- Change the power in the transmitted signal by changing the scaling factor (see the code where
tx_tdis created). Alternatively, you can change the power in
tx_fd, prior to the IFFT. In all cases, ensure that the resulting time-domain signals are located (digitally) between -32k and +32k, otherwise they are out of range for the DACs. However, keep them between -20k and +20k to prevent saturation of the mmWave up-converter chips.
- Modify the code to simultaneously transmit four different tones, one from each TX channel.
- Modify the code to transmit two tones (from one TX channel) instead of just one tone. Observe the intermodulation products. Change the scaling factor and observe the behavior of the intermodulation peaks.
- Observe that row #3 contains the received power (measured digitally on the time-domain signals, in dB scale). Deactivate the TX to observe the noise floor. Then reactivate the TX to measure the received power. Calculate SNR. Compare with the SNR in the desired band in frequency domain (using data tips to measure the power in the required subcarriers).
Refer to the page on Software and Device Drivers for more information on the commands used in this example. Most importantly, have fun!
Close the connection to the SDRs
When you've had enough, we need to release the sockets that were previously opened by
open_sdr. Doing this is simple: just run the script
close_sdr. This script simply calls
node2. We are done.