802.11ac Multi-User MIMO Precoding
This example shows transmit and receive processing for an 802.11ac™ (Wi-Fi 5) multi-user downlink transmission over a fading channel. The example uses linear precoding techniques based on a singular-value-decomposition (SVD) of the channel.
Introduction
802.11ac supports downlink (access-point to station) multi-user transmissions for up to four users and up to eight transmit antennas to increase the aggregate throughput of the link [ 1 ]. Based on a scheduled transmission time for a user, the scheduler looks for other smaller packets ready for transmission to other users. If available, it schedules these users over the same interval, which reduces the overall time taken for multiple transmissions.
This simultaneous transmission comes at a higher complexity because successful reception of the individual user's payloads requires precoding, also known as transmit-end beamforming. Precoding assumes that channel state information (CSI) is known at the transmitter. A sounding packet, as described in the 802.11ac Transmit Beamforming example, is used to determine the CSI for each user in a multi-user transmission. Each of the users feed back their individual CSI to the beamformer. The beamformer uses the CSI from all users to set the precoding (spatial mapping) matrix for subsequent data transmission.
This example uses a channel inversion technique for a three-user transmission with a different number of spatial streams allocated per user and different rate parameters per user. The system can be characterized by the figure below.
The example generates the multi-user transmit waveform, passes it through a channel per user and decodes the received signal for each user to calculate the bits in error. Prior to the data transmission, the example uses a null-data packet (NDP) transmission to sound the different channels and determines the precoding matrix under the assumption of perfect feedback.
Simulation Parameters and Configuration
For 802.11ac, a maximum of eight spatial streams is allowed. A 6x6 MIMO configuration for three users is used in this example, where the first user has three streams, second has one, and the third has two streams allocated to it. Different rate parameters and payload sizes for up to four users are specified as vector parameters. These are indexed appropriately in the transmission configuration based on the number of active users.
s = rng(21); % Set RNG seed for repeatability % Transmission parameters chanBW = 'CBW80'; % Channel bandwidth numUsers = 3; % Number of active users numSTSAll = [3 1 2 2]; % Number of streams for 4 users userPos = [0 1 2 3]; % User positions for maximum 4 users mcsVec = [4 6 2 2]; % MCS for maximum 4 users apepVec = [15120 8192 5400 6000]; % Payload, in bytes, for 4 users chCodingVec = {'BCC', 'LDPC', 'LDPC', 'BCC'}; % Channel coding for 4 users % Channel and receiver parameters chanMdl = 'Model-A'; % TGac fading channel model precodingType = 'ZF'; % Precoding type; ZF or MMSE snr = 38; % SNR in dB eqMethod = 'ZF'; % Equalization method % Create the multi-user VHT format configuration object, appropriately % indexing into the vector values for the active users if (numUsers==1) groupID = 0; else groupID = 2; end numSTSVec = numSTSAll(1:numUsers); numTx = sum(numSTSVec); cfgVHTMU = wlanVHTConfig('ChannelBandwidth', chanBW,... 'NumUsers', numUsers, ... 'NumTransmitAntennas', numTx, ... 'GroupID', groupID, ... 'NumSpaceTimeStreams', numSTSVec,... 'UserPositions', userPos(1:numUsers), ... 'MCS', mcsVec(1:numUsers), ... 'APEPLength', apepVec(1:numUsers), ... 'ChannelCoding', chCodingVec(1:numUsers));
The number of transmit antennas is set to be the sum total of all the used space-time streams. This implies no space-time block coding (STBC) or spatial expansion is employed for the transmission.
Sounding (NDP) Configuration
For precoding, channel sounding is first used to determine the channel experienced by the users (receivers). This channel state information is sent back to the transmitter, for it to be used for subsequent data transmission. It is assumed that the channel varies slowly over the two transmissions. For multi-user transmissions, the same NDP (Null Data Packet) is transmitted to each of the scheduled users [ 2 ].
% VHT sounding (NDP) configuration, for same number of streams cfgVHTNDP = wlanVHTConfig('ChannelBandwidth', chanBW,... 'NumUsers', 1, ... 'NumTransmitAntennas', numTx, ... 'GroupID', 0, ... 'NumSpaceTimeStreams', sum(numSTSVec),... 'MCS', 0, ... 'APEPLength', 0);
The number of streams specified is the sum total of all space-time streams used. This allows the complete channel to be sounded.
% Generate the null data packet, with no data
txNDPSig = wlanWaveformGenerator([], cfgVHTNDP);
Transmission Channel
The TGac multi-user channel consists of independent single-user MIMO channels between the access point and spatially separated stations [ 3 ]. In this example, the same delay profile Model-A channel is applied for each of the users, even though individual users can experience different conditions. The flat-fading channel allows a simpler receiver without front-end synchronization. It is also assumed that each user's number of receive antennas are equal to the number of space-time streams allocated to them.
Cell arrays are used in the example to store per-user elements which allow for a flexible number of users. Here, as an example, each instance of the TGac channel per user is stored as an element of a cell array.
% Create three independent channels TGAC = cell(numUsers, 1); chanSeeds = [1111 2222 3333 4444]; % chosen for a maximum of 4 users uIndex = [10 5 2 1]; % chosen for a maximum of 4 users chanDelay = zeros(numUsers, 1); for uIdx = 1:numUsers TGAC{uIdx} = wlanTGacChannel(... 'ChannelBandwidth', cfgVHTMU.ChannelBandwidth,... 'DelayProfile', chanMdl, ... 'UserIndex', uIndex(uIdx), ... 'NumTransmitAntennas', numTx, ... 'NumReceiveAntennas', numSTSVec(uIdx), ... 'RandomStream', 'mt19937ar with seed', ... 'Seed', chanSeeds(uIdx),... 'SampleRate', wlanSampleRate(cfgVHTMU), ... 'TransmitReceiveDistance',5); chanInfo = info(TGAC{uIdx}); chanDelay(uIdx) = chanInfo.ChannelFilterDelay; end
The channels for each individual user use different seeds for random number generation. A different user index is specified to allow for random angle offsets to be applied to the arrival (AoA) and departure (AoD) angles for the clusters. The channel filtering delay is stored to allow for its compensation at the receiver. In practice, symbol timing estimation would be used.
% Append zeroes to allow for channel filter delay txNDPSig = [txNDPSig; zeros(10, numTx)]; % Sound the independent channels per user for all transmit streams rxNDPSig = cell(numUsers, 1); for uIdx = 1:numUsers rxNDPChan = TGAC{uIdx}(txNDPSig); % Add WGN per receiver rxNDPSig{uIdx} = awgn(rxNDPChan, snr); end
Channel State Information Feedback
Each user estimates its own channel using the received NDP signal and computes the channel state information that it can send back to the transmitter. This example uses the singular value decomposition of the channel seen by each user to compute the CSI feedback.
mat = cell(numUsers,1); for uIdx = 1:numUsers % Compute the feedback matrix based on received signal per user mat{uIdx} = vhtCSIFeedback(rxNDPSig{uIdx}(chanDelay(uIdx)+1:end,:), cfgVHTNDP); end
Assuming perfect feedback, with no compression or quantization loss of the CSI, the transmitter computes the steering matrix for the data transmission using either Zero-Forcing or Minimum-Mean-Square-Error (MMSE) based precoding techniques. Both methods attempt to cancel out the intra-stream interference for the user of interest and interference due to other users. The MMSE-based approach avoids the noise enhancement inherent in the zero-forcing technique. As a result, it performs better at low SNRs.
% Pack the per user CSI into a matrix numST = length(mat{1}); % Number of subcarriers steeringMatrix = zeros(numST, sum(numSTSVec), sum(numSTSVec)); % Nst-by-Nt-by-Nsts for uIdx = 1:numUsers stsIdx = sum(numSTSVec(1:uIdx-1))+(1:numSTSVec(uIdx)); steeringMatrix(:,:,stsIdx) = mat{uIdx}; % Nst-by-Nt-by-Nsts end % Zero-forcing or MMSE precoding solution if strcmp(precodingType, 'ZF') delta = 0; % Zero-forcing else delta = (numTx/(10^(snr/10))) * eye(numTx); % MMSE end for i = 1:numST % Channel inversion precoding h = squeeze(steeringMatrix(i,:,:)); steeringMatrix(i,:,:) = h/(h'*h + delta); end % Set the spatial mapping based on the steering matrix cfgVHTMU.SpatialMapping = 'Custom'; cfgVHTMU.SpatialMappingMatrix = permute(steeringMatrix,[1 3 2]);
Data Transmission
Random bits are used as the payload for the individual users. A cell array is used to hold the data bits for each user, txDataBits
. For a multi-user transmission the individual user payloads are padded such that the transmission duration is the same for all users. This padding process is described in Section 9.12.6 of [ 1 ]. In this example for simplicity the payload is padded with zeros to create a PSDU for each user.
% Create data sequences, one for each user txDataBits = cell(numUsers, 1); psduDataBits = cell(numUsers, 1); for uIdx = 1:numUsers % Generate payload for each user txDataBits{uIdx} = randi([0 1], cfgVHTMU.APEPLength(uIdx)*8, 1, 'int8'); % Pad payload with zeros to form a PSDU psduDataBits{uIdx} = [txDataBits{uIdx}; ... zeros((cfgVHTMU.PSDULength(uIdx)-cfgVHTMU.APEPLength(uIdx))*8, 1, 'int8')]; end
Using the format configuration, cfgVHTMU
, with the steering matrix, the data is transmitted over the fading channel.
% Generate the multi-user VHT waveform txSig = wlanWaveformGenerator(psduDataBits, cfgVHTMU); % Transmit through per-user fading channel rxSig = cell(numUsers, 1); for uIdx = 1:numUsers % Append zeroes to allow for channel filter delay rxSig{uIdx} = TGAC{uIdx}([txSig; zeros(10, numTx)]); end
Data Recovery Per User
The receive signals for each user are processed individually. The example assumes that there are no front-end impairments and that the transmit configuration is known by the receiver for simplicity.
A user number specifies the user of interest being decoded for the transmission. This is also used to index into the vector properties of the configuration object that are user-specific.
% Get field indices from configuration, assumed known at receiver ind = wlanFieldIndices(cfgVHTMU); % Single-user receivers recover payload bits rxDataBits = cell(numUsers, 1); scaler = zeros(numUsers, 1); spAxes = gobjects(sum(numSTSVec), 1); hfig = figure('Name','Per-stream equalized symbol constellation'); for uIdx = 1:numUsers % Add WGN per receiver rxNSig = awgn(rxSig{uIdx}, snr); rxNSig = rxNSig(chanDelay(uIdx)+1:end, :); % User space-time streams stsU = numSTSVec(uIdx); % Perform channel estimation based on VHT-LTF rxVHTLTF = rxNSig(ind.VHTLTF(1):ind.VHTLTF(2),:); demodVHTLTF = wlanVHTLTFDemodulate(rxVHTLTF, chanBW, numSTSVec); [chanEst, chanEstSSPilots] = wlanVHTLTFChannelEstimate(demodVHTLTF, chanBW, numSTSVec); % Extract VHT Data samples from the waveform rxVHTData = rxNSig(ind.VHTData(1):ind.VHTData(2),:); % Estimate the noise power in VHT data field nVar = vhtNoiseEstimate(rxVHTData,chanEstSSPilots,cfgVHTMU); % Recover information bits in VHT Data field [rxDataBits{uIdx}, ~, eqsym] = wlanVHTDataRecover(rxVHTData, ... chanEst, nVar, cfgVHTMU, uIdx, 'EqualizationMethod', eqMethod, ... 'PilotPhaseTracking', 'None', 'LDPCDecodingMethod', 'norm-min-sum'); % Plot equalized symbols for all streams per user scaler(uIdx) = ceil(max(abs([real(eqsym(:)); imag(eqsym(:))]))); for i = 1:stsU subplot(numUsers, max(numSTSVec), (uIdx-1)*max(numSTSVec)+i); plot(reshape(eqsym(:,:,i), [], 1), '.'); axis square spAxes(sum([0 numSTSVec(1:(uIdx-1))])+i) = gca; % Store axes handle title(['User ' num2str(uIdx) ', Stream ' num2str(i)]); grid on; end end % Scale axes for all subplots and scale figure for i = 1:numel(spAxes) xlim(spAxes(i),[-max(scaler) max(scaler)]); ylim(spAxes(i),[-max(scaler) max(scaler)]); end pos = get(hfig, 'Position'); set(hfig, 'Position', [pos(1)*0.7 pos(2)*0.7 1.3*pos(3) 1.3*pos(4)]);
Per-stream equalized symbol constellation plots validate the simulation parameters and convey the effectiveness of the technique. Note the discernible 16QAM, 64QAM and QPSK constellations per user as specified on the transmit end. Also observe the EVM degradation over the different streams for an individual user. This is a representative characteristic of the channel inversion technique.
The recovered data bits are compared with the transmitted payload bits to determine the bit error rate.
% Compare recovered bits against per-user APEPLength information bits ber = inf(1, numUsers); for uIdx = 1:numUsers idx = (1:cfgVHTMU.APEPLength(uIdx)*8).'; [~, ber(uIdx)] = biterr(txDataBits{uIdx}(idx), rxDataBits{uIdx}(idx)); disp(['Bit Error Rate for User ' num2str(uIdx) ': ' num2str(ber(uIdx))]); end rng(s); % Restore RNG state
Bit Error Rate for User 1: 0 Bit Error Rate for User 2: 0 Bit Error Rate for User 3: 0
The small number of bit errors, within noise variance, indicate successful data decoding for all streams for each user, despite the variation in EVMs seen in individual streams.
Conclusion and Further Exploration
The example shows multi-user transmit configuration, independent per-user channel modeling, and the individual receive processing using the channel inversion precoding techniques.
Further exploration includes modifications to the transmission and channel parameters, alternate precoding techniques, more realistic receivers and feedback mechanism incorporating delays and quantization.
Selected Bibliography
IEEE Std 802.11™-2020. IEEE Standard for Information Technology - Telecommunications and Information Exchange between Systems - Local and Metropolitan Area Networks - Specific Requirements - Part 11: Wireless LAN Medium Access Control (MAC) and Physical Layer (PHY) Specifications.
Perahia, E., R. Stacey, "Next Generation Wireless LANS: 802.11n and 802.11ac", Cambridge University Press, 2013.
Breit, G., H. Sampath, S. Vermani, et al., "TGac Channel Model Addendum", Version 12. IEEE 802.11-09/0308r12, March 2010.