Main Content

Generate and Transmit 5G Waveforms Using Test and Measurement Equipment

This example shows how to generate and transmit an over-the-air 5G waveform using 5G Toolbox™, Instrument Control Toolbox™ and Keysight Technologies® RF instruments.


In this example, interface with an RF signal generator and analyzer using the Instrument Control Toolbox. Then, generate a 5G NR-TM waveform using the Wireless Waveform Generator App from the 5G Toolbox. Download the generated waveform to a Keysight Technologies E4438C signal generator for over-the-air transmission. Lastly, capture the over-the-air signal using a Keysight Technologies N9030A signal analyzer and analyze it in MATLAB.


To run this example you need:

  • Keysight Technologies® E4438C signal generator

  • Keysight Technologies® N9030A signal analyzer

  • 5G Toolbox™

  • Instrument Control Toolbox™

  • Signal Processing Toolbox™

  • Instrument Control Toolbox™ Support Package for Keysight™ IO Libraries and VISA Interface

Generate a Baseband Waveform Using the Wireless Waveform Generator App

Generate a 5G NR-TM waveform using the Wireless Waveform Generator App. Since the waveform generation is app based, you do not need to write any MATLAB code.

For more information on Wireless Waveform Generator App, refer to 5G Waveform Generator.

In the MATLAB Apps toolbar, select the Wireless Waveform Generator App. Open the Wireless Waveform Generator app and configure to generate 5G NR-TM (Test Model) waveform. Using this feature of the app requires the 5G Toolbox™.

In the Waveform Type section, select Test Models (NR-TM) waveforms. In the left pane of the app, on the Waveform tab, you can set the parameters of the selected waveform. For this example:

  • Set the Frequency range as FR1 (450 MHz - 6 GHz).

  • Set the Test model as Full band, uniform 64QAM represented as NR-FR1-TM3.1.

  • Set the Channel bandwidth as 10 MHz, Subcarrier spacing as 30 kHz, and Duplex mode as FDD.

  • Click on Generate.

As expected, the 10 MHz signal bandwidth is visible at baseband.

Transmit Over-the-Air Signal

After you generate the baseband signal, connect to an RF signal generator over one of the supported communication interfaces using the Instrument Control Toolbox. In the Wireless Waveform Generator App, go to the Transmitter tab for sending the generated waveform over-the-air. The App automatically finds the signal generator connected over the TCP/IP interface through LAN cable. Select the Agilent/Keysight Signal Generator SCPI driver from the Driver dropdown menu. Set the center frequency as 3.4 GHz and output power as -15 dBm. The baseband sample rate has already been calculated from the generated waveform in MATLAB. For transmission, click on the Transmit Waveform button.

Read In-phase and Quadrature (IQ) Data from Signal Analyzer over TCP/IP

For analysis of over-the-air transmission in MATLAB, the Instrument Control Toolbox is used to configure the Keysight Technologies N9030A signal analyzer and read in-phase and quadrature phase data.

Define the parameters used to configure the instrument before making the measurement. Based on the signal you are measuring, you may need to modify some of the following parameters.

% Center frequency of the modulated waveform (Hz)
centerFrequency = 3.4e9; 

% Bandwidth of the signal (Hz)
bandwidth = 12.287999e6;

% Measurement time (s)
measureTime = 20e-3;

% Mechanical attenuation in the signal analyzer (dB)
mechAttenuation = 0;

% Start frequency for Spectrum Analyzer 
startFrequency = 3.39e9; 

% Stop frequency for Spectrum Analyzer  
stopFrequency = 3.41e9;

% Resolution Bandwidth for Spectrum Analyzer 
resolutionBandwidth = 220e3; 

% Video Bandwidth for Spectrum Analyzer view
videoBandwidth = 220000;

Follow these steps before connecting to the spectrum analyzer:

  • Set up instrument connectivity using a TCP/IP connection.

  • Adjust the input buffer size so that it can hold the data the instrument returns.

  • Set the timeout to allow sufficient time for the measurement and transfer of data.

  • Connect to the instrument.

sigAnalyzerObj = visadev("");
sigAnalyzerObj.ByteOrder = "big-endian";
configureCallback(sigAnalyzerObj,"byte", 85e6, @callbackFcn) 
sigAnalyzerObj.Timeout = 20;

Reset the instrument to a known state using the appropriate SCPI command. Query the instrument identity to make sure the right instrument is connected.

writeline(sigAnalyzerObj, "*RST");
instrumentInfo = writeread(sigAnalyzerObj, "*IDN?");
fprintf("Instrument identification information: %s", instrumentInfo);
Instrument identification information: Agilent Technologies,N9030A,US00071181,A.14.16

The X-Series signal and spectrum analyzers perform IQ measurements as well as spectrum measurements. In this example, you acquire time domain IQ data, visualize the data using MATLAB, and perform signal analysis on the acquired data. SCPI commands are used to configure the instrument and define the format of the data transfer once the measurement is made.

% Set up signal analyzer mode to Basic IQ mode
writeline(sigAnalyzerObj,":INSTrument:SELect BASIC");

% Set the center frequency
writeline(sigAnalyzerObj,[':SENSe:FREQuency:CENTer %s' num2str(centerFrequency)]);

% Set the resolution bandwidth
writeline(sigAnalyzerObj,[':SENSe:WAVEform:BANDwidth:RESolution ' num2str(bandwidth)]);

% Turn off averaging
writeline(sigAnalyzerObj,":SENSe:WAVeform:AVER OFF");

% Set to take one single measurement once the trigger line goes high
writeline(sigAnalyzerObj,":INIT:CONT OFF"); 

% Set the trigger to external source 1 with positive slope triggering
writeline(sigAnalyzerObj,":TRIGger:WAVeform:SOURce IMMediate");
writeline(sigAnalyzerObj,":TRIGger:LINE:SLOPe POSitive");

% Set the time for which measurement needs to be made
writeline(sigAnalyzerObj,[':WAVeform:SWE:TIME '  num2str(measureTime)]);

% Turn off electrical attenuation 
writeline(sigAnalyzerObj,":SENSe:POWer:RF:EATTenuation:STATe OFF");

% Set mechanical attenuation level
writeline(sigAnalyzerObj,[':SENSe:POWer:RF:ATTenuation ' num2str(mechAttenuation)]);

% Turn IQ signal ranging to auto
writeline(sigAnalyzerObj,":SENSe:VOLTage:IQ:RANGe:AUTO ON");

% Set the endianness of returned data
writeline(sigAnalyzerObj,":FORMat:BORDer NORMal");

% Set the format of the returned data
writeline(sigAnalyzerObj,":FORMat:DATA REAL,64");

Trigger the instrument to make the measurement. Wait for the measurement operation to be completed and read in the waveform. Before processing the data, separate the I & Q components from the interleaved data returned by the instrument and create a complex vector in MATLAB.

% Trigger the instrument and initiate measurement

% Wait till measure operation is complete
measureComplete = writeread(sigAnalyzerObj,"*OPC?");

% Read the IQ data
data = readbinblock(sigAnalyzerObj,"double");

% Separate the data and build the complex IQ vector
inphase = data(1:2:end);
quadrature = data(2:2:end);
IQData = inphase+1i*quadrature;

The instrument provides information about the most recently acquired data. Capture this information and display it.

signalSpec = readbinblock(sigAnalyzerObj,"double");

% Display the measurement information
sampleRate = 1/signalSpec(1);
fprintf("Sample Rate (Hz) = %s", num2str(sampleRate));
Sample Rate (Hz) = 15359998.75
fprintf("Number of points read = %s", num2str(signalSpec(4)));
Number of points read = 307200
fprintf("Max value of signal (dBm) = %s", num2str(signalSpec(6)));
Max value of signal (dBm) = -39.4151
fprintf("Min value of signal (dBm) = %s", num2str(signalSpec(7)));
Min value of signal (dBm) = -104.1205

Plot the first 1000 points of acquired time domain data and annotate the figure.

hold on;
legend('Inphase signal', 'Quadrature signal');
title('IQ Data for the first 1000 points of acquired signal');
xlabel('Sample number');
hold off;

The spectrum view might have more information than the time domain view of the data. For example, you can use the spectrum view to identify the main frequency bands, signal bandwidth, etc. You must have the Signal Processing Toolbox to plot the spectrum view.

% Plot the PSD of the acquired signal
periodogram(IQData, hamming(length(IQData)),[], sampleRate, 'centered');

Switch the instrument to spectrum analyzer mode and compare the spectrum view generated in MATLAB with the view on the signal analyzer. Use additional SCPI commands to configure the instrument measurement and display settings.

% Switch back to the spectrum analyzer view
writeline(sigAnalyzerObj,':INSTrument:SELect SA');

% Set mechanical attenuation level
writeline(sigAnalyzerObj,[':SENSe:POWer:RF:ATTenuation ' num2str(mechAttenuation)]);

% Set the center frequency, RBW and VBW and trigger
writeline(sigAnalyzerObj,[':SENSe:FREQuency:CENTer ' num2str(centerFrequency)]);
writeline(sigAnalyzerObj,[':SENSe:FREQuency:STARt ' num2str(startFrequency)]);
writeline(sigAnalyzerObj,[':SENSe:FREQuency:STOP ' num2str(stopFrequency)]);
writeline(sigAnalyzerObj,[':SENSe:BANDwidth:RESolution ' num2str(resolutionBandwidth)]);
writeline(sigAnalyzerObj,[':SENSe:BANDwidth:VIDeo ' num2str(videoBandwidth)]);

% Continuous measurement
writeline(sigAnalyzerObj,':INIT:CONT ON'); 

% Trigger

For instrument cleanup, clear the instrument connection.

% Clear instrument connection
clear sigAnalyzerObj;

Click on Stop Transmission button in the Waveform Generator App to stop the 5G NR-TM waveform transmission

Measure EVM of 5G NR-TM Waveform

After you acquire the captured baseband IQ data from the signal analyzer , you can visualize, decode, and analyze the data using the 5G Toolbox.

% Ensure the IQData is a column vector
if ~iscolumn(IQData)
    IQData = IQData(:);

Each NR-TM waveform is defined by a combination of:

  • NR-TM name

  • Channel bandwidth

  • Subcarrier spacing

  • Duplexing mode

% Select the NR-TM simulation parameters
nrtm = "NR-FR1-TM3.1"; % reference channel
bw   = "10MHz"; % channel bandwidth
scs  = "30kHz"; % subcarrier spacing
dm   = "FDD";   % duplexing mode

TM Waveform Generation

The channel bandwidth and subcarrier spacing combination must be a valid pair from the associated FR bandwidth configuration table.

% Create generator object for the selected NR-TM
tmwavegen = hNRReferenceWaveformGenerator(nrtm,bw,scs,dm);

Generate the waveform txWaveform. By default, one frame is created for FDD and two for TDD. The tmwaveinfo output contains information about the generated signal. The resourcesinfo output contains information about the resources allocated to the PDSCH and PDSCH DM-RS in the generated signal.

[txWaveform,tmwaveinfo,resourcesinfo] = generateWaveform(tmwavegen);
txWaveform = IQData(1:2*numel(txWaveform));

The generated TM waveform may contain more than one PDSCH. The function hListTargetPDSCHs selects the target PDSCHs to analyze. This is based on the RNTI. By default, the following RNTI is considered for EVM calculation:

  • NR-FR1-TM3.1: RNTI = 0 and 2 (64QAM EVM)

These values can be overridden. The function hListTargetPDSCHs also accepts a third parameter with the set of RNTIs to consider for EVM measurement.

pdschArray is an array of the PDSCH configuration structures to analyze. Each structure contains the set of resources used for the PDSCH and the associated DM-RS and PT-RS. It also contains the set of parameters to generate the PDSCH.

[pdschArray, targetRNTIs] = hListTargetPDSCHs(tmwavegen.Config,resourcesinfo.WaveformResources);

Impairment: Phase Noise and Nonlinearity

The impairments phase noise and memoryless nonlinearity can be enabled or disabled by toggling the flags phaseNoiseOn and nonLinearityModelOn.

phaseNoiseOn = false;
nonLinearityModelOn = false;

Normalize the waveform to fit the dynamic range of the nonlinearity.

txWaveform = txWaveform/max(abs(txWaveform));


The receiver in this example performs the following steps:

  • Synchronization using the DM-RS over one frame for FDD (two frames for TDD)

  • OFDM demodulation of the received waveform

  • Channel estimation

  • Equalization

Get waveform parameters for synchronization and OFDM demodulation

gnb.NRB = tmwavegen.Config.SCSCarriers{1}.NSizeGrid;
gnb.CyclicPrefix = tmwavegen.Config.BandwidthParts{1}.CyclicPrefix;
gnb.SubcarrierSpacing = tmwavegen.Config.SCSCarriers{1}.SubcarrierSpacing;

Coarse Carrier Offset Compensation

DMRS-based, looking for offsets in increment of 10kHz up to 200kHz

peak = [];
t = 0:numel(txWaveform)-1;
foffsetRange = -20:20;
for foffsetnr = foffsetRange
    % Correction
    phase = 2*pi*foffsetnr*1e4*t'/tmwaveinfo.Info.SamplingRate;
    rxWaveform = txWaveform .* exp(1j*phase);

    % Generate a reference grid spanning 10 msec (one frame). This grid
    % contains only the DM-RS and is used for synchronization.
    refGrid = referenceGrid(tmwaveinfo,pdschArray);
    NSlot = 0;
    [offset,mag] = nrTimingEstimate(rxWaveform,gnb.NRB,gnb.SubcarrierSpacing,NSlot,refGrid);
    peak = [peak; max(mag)];
[~,ind] = max(peak);
coarseOffset = foffsetRange(ind);
fprintf('Coarse frequency offset = %.0f *10kHz\n', coarseOffset)
Coarse frequency offset = 0 *10kHz
% Correction
phase = 2*pi*coarseOffset*1e4*t'/tmwaveinfo.Info.SamplingRate;
rxWaveform = txWaveform .* exp(1j*phase);

Finer Frequency Offset Correction

Frequency offset estimation based on cyclic prefix correlation. This can estimate a frequency offset (abs(foffset)) which is less than half of subcarrier spacing.

foffset = simpleFreqEstimation(rxWaveform,tmwaveinfo.Info);
fprintf('Cyclic-prefix-based frequency offset = %f Hz\n', foffset)
Cyclic-prefix-based frequency offset = -5.913695 Hz
% Correction
t = 0:numel(txWaveform)-1;
phase = 2*pi*foffset*t'/tmwaveinfo.Info.SamplingRate;
rxWaveform = rxWaveform .* exp(1j*phase);

Finer Correction

Finer correction based on DMRS, with 5Hz granularity.

peak = [];
t = 0:numel(rxWaveform)-1;
foffsetRange = -100:5:100;
for foffsetnr = foffsetRange
    % Correction
    phase = 2*pi*foffsetnr*t'/tmwaveinfo.Info.SamplingRate;
    rxWaveform0 = rxWaveform .* exp(1j*phase);
    % Generate a reference grid spanning 10 msec (one frame). This grid
    % contains only the DM-RS and is used for synchronization.
    refGrid = referenceGrid(tmwaveinfo,pdschArray);
    NSlot = 0;
    [offset,mag] = nrTimingEstimate(rxWaveform0,gnb.NRB,gnb.SubcarrierSpacing,NSlot,refGrid);
    peak = [peak; max(mag)];
[~,ind] = max(peak);
fineOffset = foffsetRange(ind);
fprintf('DMRS-based frequency offset = %.1f Hz\n', fineOffset)
DMRS-based frequency offset = -25.0 Hz
% Correction
phase = 2*pi*fineOffset*t'/tmwaveinfo.Info.SamplingRate;
rxWaveform = rxWaveform .* exp(1j*phase);

Generate a reference grid spanning 10 msec (one frame). This grid contains only the DM-RS and is used for synchronization.

refGrid = referenceGrid(tmwaveinfo,pdschArray);
NSlot = 0;
[offset,mag] = nrTimingEstimate(rxWaveform,gnb.NRB,gnb.SubcarrierSpacing,NSlot,refGrid);
tmWaveformSync = rxWaveform(1+offset:end,:);

OFDM Demodulation

rxGrid = nrOFDMDemodulate(tmWaveformSync,gnb.NRB,gnb.SubcarrierSpacing,0);

For all slots, get the PDSCH and DM-RS resources (resource element locations). This information is present in pdschArray. Then perform channel estimation and equalization. The resulting data is stored to calculate the EVM.

% Get total number of slots and the number of subcarriers and symbols per slot
symbolsPerSlot = tmwaveinfo.Info.SymbolsPerSlot;
totalNrSlots = floor(size(rxGrid,2)/symbolsPerSlot);
noSubcarriers = size(rxGrid,1);

% Declare storage variables
constellationSymbols = [];  % equalized symbols for constellation plot
constellationRef = [];      % reference symbols for constellation plot
refSqGrid = []; % grid with magnitude square of reference symbols
evSqGrid = [];  % grid with magnitude square of error vector (symbols - reference)

for NSlot=0:totalNrSlots-1
    % Extract grid for current slot
    currentGrid = rxGrid(:,NSlot*symbolsPerSlot+(1:symbolsPerSlot),:);
    % Retrieve resources
    [pdschIndices,refSymbols,dmrsIndices,dmrsSymbols] = hSlotResources(pdschArray,NSlot);
    % Do not include first two slot symbols for PDSCH EVM (used for control
    % TS 38.141-1 table
    idx = pdschIndices <= 2*noSubcarriers;
    pdschIndices(idx) = [];
    refSymbols(idx) = [];
    % Channel estimation
    [estChannelGrid,~] = nrChannelEstimate(currentGrid,dmrsIndices,dmrsSymbols,...
        'AveragingWindow',[3 3]);
    % Get PDSCH resource elements from the received grid
    [pdschRx,pdschHest] = nrExtractResources(pdschIndices,currentGrid,estChannelGrid);
    % Equalization: set noiseEst to 0 for zero-forcing equalization
    noiseEst = 0;
    [pdschEq,csi] = nrEqualizeMMSE(pdschRx,pdschHest,noiseEst);
    refSqGridSlot = zeros(size(currentGrid)); % slot grid magnitude square of reference symbols
    evSqGridSlot = zeros(size(currentGrid));  % slot grid magnitude square of error vector
    if ~isempty(refSymbols) % for slots with data
        % Error vector magnitude square
        evSq = abs(refSymbols-pdschEq).^2;
        % Reference symbols magnitude square
        refSymSq = abs(refSymbols).^2;
        % Store constellation symbols, reference symbols and square error vector
        constellationSymbols = [constellationSymbols; pdschEq];
        constellationRef = [constellationRef; refSymbols];
        evSqGridSlot(pdschIndices) = evSq;
        refSqGridSlot(pdschIndices) = refSymSq;
    refSqGrid = [refSqGrid refSqGridSlot];
    evSqGrid = [evSqGrid evSqGridSlot];

Display the constellation diagram.

hold on;
title('Equalized symbols constellation');
grid on; xlabel('In-Phase'); ylabel('Quadrature');
hold off;

EVM Calculation

Calculate the following EVM (RMS and maximum) values:

  • EVM per OFDM symbol

  • EVM per slot

  • EVM per subcarrier

  • Total EVM for the whole signal

Calculate composite EVM across all channels. The EVM values shown are normalized by the power of the reference symbols. This power is calculated for the measurement interval considered. For example, when calculating the EVM per slot, all the reference symbols in that slot are used to calculate the power used in the normalization. In the case of the EVM per subcarrier, the measurement interval is one frame for FDD and two frames for TDD. The total EVM is measured over one frame for FDD and two frames for TDD.

Display the EVM per OFDM symbol, slot, and subcarrier.

% EVM per symbol
[evmPerSymbol, evmMaxSymbol] = evm(evSqGrid,refSqGrid);
figure; subplot(3,1,1)
title('EVM vs OFDM symbol')
grid on; xlabel('Symbol number'); ylabel('EVM (%)'); legend('rms EVM','peak EVM','Location','bestoutside')

% EVM per slot
% reshape grids to one column per slot
evSqGridSlot = reshape(evSqGrid,size(evSqGrid).*symbolsPerSlot.^[1 -1]);
refSqGridSlot = reshape(refSqGrid,size(refSqGrid).*symbolsPerSlot.^[1 -1]);
[evmPerSlot, evmMaxSlot] = evm(evSqGridSlot,refSqGridSlot);
slotNo = 0:length(evmPerSlot)-1;
title('EVM vs slot')
grid on; xlabel('Slot number'); ylabel('EVM (%)'); legend('rms EVM','peak EVM','Location','bestoutside')

% EVM per subcarrier
[evmPerSubcarrier, evmMaxSubcarrier] = evm(evSqGrid.',refSqGrid.');
subcarrierNo = 0:length(evmPerSubcarrier)-1;
title('EVM vs subcarrier')
grid on; xlabel('Subcarrier number'); ylabel('EVM (%)'); legend('rms EVM','max EVM','Location','bestoutside')

Calculate overall EVM.

[evmRMS, evmMax] = evm(evSqGrid(:),refSqGrid(:));
fprintf("PDSCH RNTIs: %s", mat2str(targetRNTIs));
PDSCH RNTIs: [0 2]
fprintf("RMS EVM = %s", num2str(evmRMS));
RMS EVM = 2.7521
fprintf("Max EVM = %s", num2str(evmMax));
Max EVM = 13.9067

Local functions:

function [evmRMS, evmMax] = evm(evSqGrid,refSqGrid)
% Calculates the evm per column of the input matrix
% inputs (both of the same size):
%   - evSqGrid = matrix of error vector magnitude square |refSymb-rxSymb|.^2
%   - refSqGrid = matrix of ref symbols magnitude square |refSymb|.^2

if all(size(evSqGrid)~=size(refSqGrid))
    error('Input matrices must have the same size');

% RMS EVM (%)
evmRMS = rmsEVM(sum(evSqGrid,1),sum(refSqGrid,1));

% Max EVM (%)
evmMax = zeros(1,size(refSqGrid,2));

% Find non zero REs in reference grid
nonZeroREs = (refSqGrid~=0);
for ncol = 1:length(evmMax)
    % Remove reference symbols with value zero
    nzEvSqREsSlot = evSqGrid(nonZeroREs(:,ncol),ncol);
    nzRefSqREsSlot = refSqGrid(nonZeroREs(:,ncol),ncol);
    evmMax(ncol) = maxEVM(nzEvSqREsSlot,nzRefSqREsSlot);

function evmRMS = rmsEVM(evSq,refSq)
% Calcuates RMS EVM. Inputs
%   - evSq = matrix of error vector magnitude square
%   - refSq = matrix of ref symbols magnitude square
% They have the same size or refSq can be a scalar
evmRMS = 100*sqrt(evSq./refSq);

function evmMax = maxEVM(evSq,refSq)
% Inputs (both of the same size):
%   - evSq = array of error vector magnitude square
%   - refSq = array of ref symbols magnitude square
if isempty(evSq) && isempty(refSq)
    evmMax = NaN;
    evmMax = max(100*sqrt(evSq./mean(refSq)));

function refGrid = referenceGrid(tmwaveinfo,pdschArray)
% Create a reference grid for synchronization. This should span a whole
% frame, starting in slot 0. It contains the DM-RSs specified in
% pdschArray.

L = tmwaveinfo.Info.SymbolsPerSubframe*10; % symbols in a frame
refGrid = zeros(tmwaveinfo.Info.NSubcarriers,L); % empty grid
rbsPerSlot = tmwaveinfo.Info.NSubcarriers*tmwaveinfo.Info.SymbolsPerSlot;

% Populate the DM-RSs in the reference grid for all slots
for NSlot=0:(10*tmwaveinfo.Info.SlotsPerSubframe)-1 % 1 frame worth of subframes
    [~,~,dmrsIndices,dmrsSymbols] = hSlotResources(pdschArray,NSlot);
    refGrid(dmrsIndices+NSlot*rbsPerSlot) = dmrsSymbols;