mcpbeat

Matlab Generate Wlan Waveform

matlab/matlab-generate-wlan-waveform

> Generate standard-compliant IEEE 802.11 waveforms using MATLAB WLAN Toolbox. Use when creating WLAN waveforms, PPDU packets, or the transmit side of a VHT (802.11ac), HE-SU/HE-MU/HE-TB (802.11ax), EHT-MU/EHT-TB (802.11be), UHR-MU/UHR-TB/UHR-ELR (802.11bn). Handles single-user, MU-MIMO, OFDMA, trigger-based uplink, extended range, preamble puncturing, UEQM, and DRU. Use when asked to generate test waveforms, create packets with MAC frames, configure OFDMA resource units, build trigger-based uplink transmissions, target a specific transmit duration, or build multi-packet waveforms.

14k tokens
context cost
the whole folder, loaded on every use
9
files
instructions only
0
copies elsewhere
how many repositories repackaged it
865
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-generate-wlan-waveform

The instruction itself

29 sections, as written by the author

Generate WLAN Waveforms

Generate standard-compliant IEEE 802.11 waveforms for device testing, link-level

simulation, or signal analysis. This skill covers the transmit chain: configure

the PHY format, size the payload, generate the time-domain IQ waveform, and plot.

When to Use

  • Generating WLAN/Wi-Fi test waveforms for any 802.11 standard
  • Creating the transmit side of a link-level simulation
  • Building packets with specific MAC frame types (Data, Block Ack, Beacon, etc.)
  • Configuring OFDMA resource unit allocations (HE-MU, EHT-MU, or UHR-MU)
  • Configuring MU-MIMO transmissions (VHT, HE, EHT, UHR)
  • UHR features: UEQM (per-stream MCS), DRU, LDPC2x, ELR (enhanced long range)
  • Targeting a specific packet duration or transmit time

When NOT to Use

  • Channel modeling, receiver processing, EVM/spectral mask — not covered
  • Non-WLAN waveforms (5G NR, LTE, Bluetooth)

UHR (802.11bn) requires R2026a or later. If the user requests a UHR waveform

and their release is older, inform them that UHR support was introduced in R2026a

and recommend upgrading.

Workflow

Every waveform generation follows this pipeline:

  • Select format → create the config object (see Format Selection)
  • Configure PHY → set bandwidth, MCS, spatial streams, antennas
  • Size the payload → set PSDU/APEP length directly or from a target duration
  • Create payload bits → random bits or MAC frame via wlanMACFrame
  • Generate waveformwlanWaveformGenerator(bits, cfg)
  • Plot and verify → time-domain magnitude, showAllocation for HE/EHT/UHR configs

Format Selection

| Standard | Marketing | Config Object | Users |

|----------|-----------|---------------|-------|

| 802.11b | Wi-Fi 1 | wlanNonHTConfig (DSSS) | SU only |

| 802.11a/g | Wi-Fi 1/3 | wlanNonHTConfig | SU only |

| 802.11n | Wi-Fi 4 | wlanHTConfig | SU only |

| 802.11ac | Wi-Fi 5 | wlanVHTConfig | SU or MU-MIMO |

| 802.11ax | Wi-Fi 6 | wlanHESUConfig | SU only |

| 802.11ax | Wi-Fi 6 | wlanHESUConfig (ExtendedRange) | SU — extended range |

| 802.11ax | Wi-Fi 6 | wlanHEMUConfig(allocIdx) | OFDMA and/or MU-MIMO |

| 802.11ax | Wi-Fi 6 | wlanHETBConfig | SU uplink (trigger-based) |

| 802.11be | Wi-Fi 7 | wlanEHTMUConfig(allocIdx) | OFDMA and/or MU-MIMO / MRU |

| 802.11be | Wi-Fi 7 | wlanEHTMUConfig("CBW...") | Non-OFDMA MU-MIMO (full-band RU) |

| 802.11be | Wi-Fi 7 | wlanEHTMUConfig("CBW...", EHTDUPMode=true) | SU — DUP mode (MCS 14 only) |

| 802.11be | Wi-Fi 7 | wlanEHTTBConfig | SU uplink (trigger-based) |

| 802.11bn | Wi-Fi 8 | uhrMUConfig(allocIdx) | OFDMA / MU-MIMO / UEQM (example helpers) |

| 802.11bn | Wi-Fi 8 | uhrMUConfig("CBW...") | Non-OFDMA MU-MIMO (example helpers) |

| 802.11bn | Wi-Fi 8 | uhrTBConfig | SU uplink / DRU (example helpers) |

| 802.11bn | Wi-Fi 8 | uhrELRConfig | SU enhanced long range (example helpers) |

For OFDMA formats (HE-MU, EHT-MU), the constructor takes allocation indices,

not RU sizes. See references/he-allocation-indices.md

and references/eht-allocation-indices.md.

UHR (802.11bn / Wi-Fi 8) uses example helper files, not built-in toolbox objects.

Copy helpers into the script's working folder with

setupExample("wlan/UHRParameterizationExample", scriptFolder). UHR supports

UEQM (per-stream MCS), DRU, LDPC2x, ELR (enhanced long range), and new MCS

values (15-23). Same allocation indices as EHT. See

references/uhr-waveform-generation.md.

See Critical Rules for format-specific constraints (allocation index schemes,

HT MCS encoding, DSSS properties).

EHT DUP mode duplicates the signal across subchannels for robust coverage.

Set at construction: wlanEHTMUConfig("CBW80", EHTDUPMode=true). Constraints:

MCS 14 (BPSK-DCM) only, single user, 1 spatial stream, no puncturing,

80/160/320 MHz. EHTDUPMode is read-only after construction.

Duration Targeting

Calculate payload size from target duration. All values are integer microseconds.

| Config | Function | Example |

|--------|----------|---------|

| wlanNonHTConfig | wlanPSDULength(cfg, 'TxTime', us) | cfg.PSDULength = wlanPSDULength(cfg, 'TxTime', 500); |

| wlanHTConfig | wlanPSDULength(cfg, 'TxTime', us) | cfg.PSDULength = wlanPSDULength(cfg, 'TxTime', 1000); |

| wlanVHTConfig (SU only) | wlanAPEPLength(cfg, 'TxTime', us) | cfg.APEPLength = wlanAPEPLength(cfg, 'TxTime', 2000); |

| wlanHESUConfig | wlanAPEPLength(cfg, 'TxTime', us) | cfg.APEPLength = wlanAPEPLength(cfg, 'TxTime', 3000); |

| wlanEHTMUConfig (SU non-OFDMA) | wlanAPEPLength(cfg, 'TxTime', us) | cfg.User{1}.APEPLength = wlanAPEPLength(cfg, 'TxTime', 1000); |

| Any MU/OFDMA config | Iterative transmitTime loop | See below — wlanAPEPLength errors on MU/OFDMA |

Duration argument is integer microseconds, not seconds. Pass 2000, not 2e-3.

MU Duration Targeting (iterative)

For any MU config (homogeneous users — same MCS, RU size, and spatial streams):

targetDuration = 1e-3; apepLen = 2000; % initial guess
for iter = 1:10
    for u = 1:numUsers, cfg.User{u}.APEPLength = apepLen; end
    txTime = transmitTime(cfg);
    if abs(txTime - targetDuration)/targetDuration < 0.01, break; end
    apepLen = round(apepLen * targetDuration / txTime);
end

Heterogeneous users (different MCS/RU/STS): Lowest-capacity user sets duration.

Set per-user APEPLength based on traffic demand; use transmitTime(cfg).

PSDU Length Access

The way to get PSDU length varies by format. Using the wrong pattern throws errors.

| Config | How to get PSDU length | Notes |

|--------|----------------------|-------|

| wlanNonHTConfig | cfg.PSDULength | Settable property |

| wlanHTConfig | cfg.PSDULength | Settable property |

| wlanVHTConfig | cfg.PSDULength | Read-only property (derived from APEPLength). Vector for MU. |

| wlanHESUConfig | getPSDULength(cfg) | Method call. Not a property. |

| wlanHEMUConfig | getPSDULength(cfg) | Method call. Returns vector (one per user). |

| wlanHETBConfig | getPSDULength(cfg) | Method call. Same as HE-SU/HE-MU. |

| wlanEHTMUConfig | psduLength(cfg) | Different method name from HE. Returns vector. |

| wlanEHTTBConfig | psduLength(cfg) | Same method name as EHT-MU. |

getPSDULength and psduLength are not interchangeable. HE uses

getPSDULength. EHT uses psduLength.

Key Functions

| Function | Purpose |

|----------|---------|

| wlanWaveformGenerator(bits, cfg) | Generate time-domain IQ waveform |

| wlanSampleRate(cfg) | Get sample rate for the configuration — always use this |

| wlanHETBConfig | Configure HE trigger-based uplink (single STA) |

| wlanEHTTBConfig | Configure EHT trigger-based uplink (single STA) |

| wlanMACFrame(payload, cfgMAC) | Generate MAC frame bits (see references/mac-frame-properties.md) |

| wlanAPEPLength(cfg, 'TxTime', us) | APEP length for target duration (VHT-SU, HE-SU, EHT-MU single-user) |

| wlanPSDULength(cfg, 'TxTime', us) | PSDU length for target duration (NonHT/HT) |

| transmitTime(cfg) or transmitTime(cfg, 'microseconds') | Get transmit time — use unit argument instead of * 1e6 |

| showAllocation(cfg) or showAllocation(cfg, ax) | Plot RU allocation — pass axes handle to embed in tiledlayout |

| ruInfo(cfg) | Query RU sizes, indices, user counts |

If you need to verify property names, check valid values for a config

object, or look up parameters not covered in this skill, consult the online

documentation links in references/documentation-links.md.

Patterns

Single-User Waveform with Target Duration

cfg = wlanVHTConfig;
cfg.ChannelBandwidth = 'CBW80';
cfg.MCS = 9;
cfg.NumTransmitAntennas = 4;
cfg.NumSpaceTimeStreams = 4;
cfg.SpatialMapping = 'Fourier';

% Size payload for 2 ms transmit time
cfg.APEPLength = wlanAPEPLength(cfg, 'TxTime', 2000);

% Generate waveform: 3 packets, 20 us idle
psduBits = randi([0 1], cfg.PSDULength * 8, 1);
waveform = wlanWaveformGenerator(psduBits, cfg, ...
    'NumPackets', 3, 'IdleTime', 20e-6);

fs = wlanSampleRate(cfg);

DSSS (802.11b) Waveform

cfg = wlanNonHTConfig;
cfg.Modulation = 'DSSS';
cfg.DataRate = '11Mbps';   % '1Mbps', '2Mbps', '5.5Mbps', or '11Mbps'
cfg.PSDULength = 1000;

psduBits = randi([0 1], cfg.PSDULength * 8, 1);
waveform = wlanWaveformGenerator(psduBits, cfg);
fs = wlanSampleRate(cfg);  % 11 MHz (chip rate)

HE-MU OFDMA Waveform

For OFDMA, the constructor takes allocation indices per 20 MHz subchannel.

Read references/he-allocation-indices.md for

the full index-to-RU mapping.

% 80 MHz, four 242-tone RUs (index 192 = one 242-tone RU per subchannel)
cfg = wlanHEMUConfig([192 192 192 192]);
cfg.NumTransmitAntennas = 4;

% Configure per-user parameters
for u = 1:4
    cfg.User{u}.MCS = u + 6;               % MCS 7, 8, 9, 10
    cfg.User{u}.NumSpaceTimeStreams = 1;
    cfg.User{u}.APEPLength = 4000;
    cfg.User{u}.ChannelCoding = 'LDPC';
end

% Use Fourier spatial mapping when NumSTS < NumTransmitAntennas per RU
for r = 1:numel(cfg.RU)
    cfg.RU{r}.SpatialMapping = 'Fourier';
end

% Generate PSDU bits per user
psduLen = getPSDULength(cfg);
txData = cell(1, numel(psduLen));
for u = 1:numel(psduLen)
    txData{u} = randi([0 1], psduLen(u) * 8, 1);
end

waveform = wlanWaveformGenerator(txData, cfg);
showAllocation(cfg);

Common HE allocation indices (per 20 MHz subchannel):

| Index | RU Layout | Users |

|-------|-----------|-------|

| 0 | Nine 26-tone | 9 |

| 96 | Two 106-tone | 2 |

| 112 | Four 52-tone | 4 |

| 192 | One 242-tone | 1 |

| 193 | One 242-tone | 2 (MU-MIMO) |

| 200 | One 484-tone (40 MHz pair) | 1 |

| 208 | One 996-tone (80 MHz quad) | 1 |

For 40 MHz, provide 2 indices. For 80 MHz, provide 4. For 160 MHz, provide 8.

EHT-MU OFDMA Waveform (with MRU)

EHT allocation indices support Multi-Resource Units (MRU) — non-contiguous tone

blocks assigned to a single user. Read

references/eht-allocation-indices.md for

the full mapping.

% 80 MHz: 484+242 MRU on subchannels 1-3, 106+26+106 on subchannel 4
% Index 120 = 484+242 MRU (1 MU-MIMO user), 29/28 = continuation, 25 = 106+26+106
cfg = wlanEHTMUConfig([120 29 28 25]);
cfg.NumTransmitAntennas = 2;

% Set APEPLength appropriate to RU size (26-tone RUs have low throughput)
apepPerUser = [2000, 500, 100, 500]; % MRU, 106-tone, 26-tone, 106-tone
mcsPerUser  = [7, 4, 2, 4];
for u = 1:numel(cfg.User)
    cfg.User{u}.APEPLength = apepPerUser(u);
    cfg.User{u}.MCS = mcsPerUser(u);
    cfg.User{u}.NumSpaceTimeStreams = 1;
    cfg.User{u}.ChannelCoding = 'LDPC';
end

for r = 1:numel(cfg.RU)
    cfg.RU{r}.SpatialMapping = 'Fourier';
end

% EHT uses psduLength(), NOT getPSDULength()
psduLens = psduLength(cfg);
txData = cell(1, numel(psduLens));
for u = 1:numel(psduLens)
    txData{u} = randi([0 1], psduLens(u) * 8, 1);
end

waveform = wlanWaveformGenerator(txData, cfg);
showAllocation(cfg);

EHT continuation indices: When an RU spans multiple 20 MHz subchannels, use

continuation indices for the additional subchannels:

  • 28 — subchannel is part of a larger 242-tone allocation
  • 29 — subchannel is part of a 484-tone allocation
  • 30 — subchannel is part of a 996-tone allocation

VHT MU-MIMO Waveform

cfg = wlanVHTConfig;
cfg.ChannelBandwidth = 'CBW80';
cfg.NumUsers = 2;
cfg.NumTransmitAntennas = 4;
cfg.NumSpaceTimeStreams = [2 2];
cfg.MCS = [8 8];
cfg.APEPLength = [1024 1024];
cfg.GroupID = 2;                    % MU-MIMO: must be 1-62
cfg.SpatialMapping = 'Fourier';    % Required when total STS < NumTxAntennas

psduLen = cfg.PSDULength;           % Read-only vector for MU
txData = cell(1, cfg.NumUsers);
for u = 1:cfg.NumUsers
    txData{u} = randi([0 1], psduLen(u) * 8, 1);
end

waveform = wlanWaveformGenerator(txData, cfg);

Non-OFDMA MU-MIMO Waveform (EHT and HE)

When the user requests MU-MIMO without OFDMA (all users share a single

full-bandwidth RU), use these patterns. **If the user says "MU-MIMO" without

mentioning OFDMA or multiple RUs, default to non-OFDMA.**

EHT — use the string constructor (no allocation indices needed):

% 80 MHz, 2 MU-MIMO users on a single 996-tone RU (non-OFDMA)
cfg = wlanEHTMUConfig("CBW80", NumUsers=2);
cfg.NumTransmitAntennas = 4;
cfg.User{1}.NumSpaceTimeStreams = 2;
cfg.User{1}.MCS = 9;
cfg.User{1}.APEPLength = 4000;
cfg.User{1}.ChannelCoding = 'LDPC';
cfg.User{2}.NumSpaceTimeStreams = 2;
cfg.User{2}.MCS = 7;
cfg.User{2}.APEPLength = 4000;
cfg.User{2}.ChannelCoding = 'LDPC';
cfg.RU{1}.SpatialMapping = 'Fourier';

psduLens = psduLength(cfg);
txData = cell(1, numel(psduLens));
for u = 1:numel(psduLens)
    txData{u} = randi([0 1], psduLens(u) * 8, 1);
end
waveform = wlanWaveformGenerator(txData, cfg);

Valid bandwidths: "CBW20", "CBW40", "CBW80", "CBW160", "CBW320".

Up to 8 users. Supports puncturing via PuncturedChannelFieldValue.

HE — no string constructor; use full-band allocation indices:

| Bandwidth | Index for N users | Zero-user index |

|-----------|-------------------|-----------------|

| 20 MHz | 192 + N - 1 | N/A |

| 40 MHz | [200 + N - 1, 114] | 114 |

| 80 MHz | [208 + N - 1, 115, 115, 115] | 115 |

| 160 MHz | [216 + N - 1, 115, 115, 115, 115, 115, 115, 115] | 115 |

Follow the same per-user/per-RU pattern as EHT above, using getPSDULength(cfg)

instead of psduLength(cfg). Example: cfg = wlanHEMUConfig([209 115 115 115])

for 80 MHz with 2 users.

MAC Frame Payload

See references/mac-frame-properties.md for:

  • Argument order, payload format (hex octets), and PHY config requirements
  • Frame types, properties, and aggregation (A-MSDU, A-MPDU)
  • Common mistakes and calling conventions

Multi-Packet Waveforms

See references/multi-packet-waveforms.md for:

  • A-MPDU aggregation with wlanMSDULengths (cell array of uint8 MSDUs)
  • Multi-packet waveforms — identical (NumPackets, IdleTime) or mixed-format

concatenation with SIFS/DIFS/PIFS spacing

Quick reference — inter-frame spacing: SIFS=16 μs, DIFS=34 μs, PIFS=25 μs.

See references/trigger-based-uplink.md for:

  • HE TB (wlanHETBConfig), EHT TB (wlanEHTTBConfig), UHR TB (uhrTBConfig)
  • RUSize/RUIndex from ruInfo(cfgMU), LTF symbols, OversamplingFactor
  • UHR DRU — distributed resource units with DBW/RU size rules

Conventions

  • After generating a waveform, unless the user asks for something specific:
  • Tell the user the waveform variable name, size, and sample rate
  • Plot the time-domain waveform magnitude with a time axis in milliseconds
  • For HE, EHT, or UHR configurations, call showAllocation(cfg)
  • Save the generation code as a plain-text Live Code .m file using the

matlab-create-live-script skill format (%[text] markup, %% sections,

required appendix). Open it in the editor with edit('scriptName.m').

  • UHR scripts use a working folder — copy helpers into the same folder as

the script with setupExample. See

references/uhr-waveform-generation.md.

  • Single-user payload is a plain column vector — use a cell array only for

multi-user. wlanWaveformGenerator(bits, cfg) not wlanWaveformGenerator({bits}, cfg).

  • Always use wlanSampleRate(cfg) — never hardcode. For UHR configs use

wlanSampleRate(cfg.ChannelBandwidth) (UHR objects are not yet supported).

  • OFDMA bandwidth is read-only — inferred from allocation index vector

length (1→20, 2→40, 4→80, 8→160 MHz).

  • Units: IdleTime in seconds (20e-6). Duration functions in integer

microseconds (2000). OversamplingFactor multiplies wlanSampleRate.

transmitTime(cfg, 'microseconds') — use the unit argument for display,

not * 1e6. Valid units: 'seconds', 'milliseconds', 'microseconds',

'nanoseconds'.

  • Verify multi-user configs with ruInfo(cfg) — after constructing any

OFDMA or MU-MIMO config, call ruInfo and check NumUsers, NumUsersPerRU,

and RUSizes match what was requested. Wrong allocation indices silently

produce wrong user counts.

  • ruInfo: HE returns numeric arrays. EHT returns cell arrays (MRU support).
  • Plotting: tiledlayout/nexttile (not subplot).

showAllocation(cfg, ax) embeds in existing layout.

Critical Rules

These constraints cause the most errors. Check them before calling

wlanWaveformGenerator.

PSDU Length Method Varies by Format

Using the wrong accessor throws "Undefined function" or silent wrong results.

| Format | How to get PSDU length | Wrong approach |

|--------|----------------------|---------------|

| Non-HT, HT | cfg.PSDULength (property) | getPSDULength(cfg) — errors |

| VHT | cfg.PSDULength (read-only) | Setting it — errors |

| HE-SU, HE-MU, HE-TB | getPSDULength(cfg) | psduLength(cfg) — errors |

| EHT-MU, EHT-TB | psduLength(cfg) | getPSDULength(cfg) — errors |

Allocation Indices Are Not RU Sizes

wlanHEMUConfig(242) is invalid. Use wlanHEMUConfig(192) for a

242-tone RU. wlanEHTMUConfig(242) is also invalid — use

wlanEHTMUConfig(64). Always consult the allocation index reference tables.

HE and EHT use completely different index schemes. A 242-tone RU is

index 192 for HE but index 64 for EHT. Never mix them.

HE-MU multi-subchannel RU indices signal users per content channel — see

references/he-allocation-indices.md.

Preamble Puncturing vs STAID=2046

| Goal | HE | EHT (OFDMA) | EHT (non-OFDMA) |

|------|-----|-------------|-----------------|

| Fully puncture subchannel | Allocation index 113 | Allocation index 26 | PuncturedChannelFieldValue at construction |

| Disable user data only | STAID=2046 | STAID=2046 | — |

STAID=2046 does not puncture — it only disables one user's data.

Non-OFDMA puncturing uses PuncturedChannelFieldValue (1–4 for 80 MHz,

mapping to subchannels 1–4). Must be set at construction:

wlanEHTMUConfig("CBW80", NumUsers=2, PuncturedChannelFieldValue=3)

Spatial Mapping Rules

NumSpaceTimeStreams < NumTransmitAntennas per RU  →  SpatialMapping = 'Fourier'
NumSpaceTimeStreams == NumTransmitAntennas         →  SpatialMapping = 'Direct'

Using 'Direct' when STS < antennas causes a dimension mismatch error.

Non-OFDMA Is the Default for MU-MIMO

When the user says "MU-MIMO" without mentioning OFDMA or multiple RUs,

default to non-OFDMA (single full-bandwidth RU shared by all users):

  • EHT: wlanEHTMUConfig("CBW80", NumUsers=N)
  • HE: Full-band allocation indices (see Patterns section)

Trigger-Based RUIndex From ruInfo

TB config RUIndex values are **subcarrier-based positions from

ruInfo(cfgMU).RUIndices**, not sequential RU numbers. Using wrong

indices causes subcarrier overlap errors.

Construction-Time-Only Properties

EHTDUPMode, PuncturedChannelFieldValue, and NumUsers on wlanEHTMUConfig

are read-only after construction — pass them as name-value pairs in the

constructor. See references/eht-allocation-indices.md

for the full list and examples.

Format-Specific Constraints

| Constraint | Details |

|-----------|---------|

| HT MCS encodes streams | MCS 0-7 = 1 SS, 8-15 = 2 SS, 16-23 = 3 SS, 24-31 = 4 SS. Set NumSpaceTimeStreams = floor(MCS/8) + 1. |

| DSSS has no OFDM properties | Use DataRate ('1Mbps'...'11Mbps'). No ChannelBandwidth, MCS, NumTransmitAntennas, or NumSpaceTimeStreams. |

| HE ER + Upper106ToneRU | Restricts MCS to 0. The 242-tone ER variant allows MCS 0-2. |

| VHT GroupID | 0/63 = SU. 1-62 = MU-MIMO. Silently accepts wrong values. |

| wlanAPEPLength is SU-only | Works for VHT-SU, HE-SU, and EHT-MU single-user (non-OFDMA). Errors on any MU/OFDMA config — use iterative transmitTime loop. |

| Do not fabricate IEEE versions | Direct the user to the WLAN Toolbox Release Notes. |

----

Copyright 2026 The MathWorks, Inc.

How to use it

Copy the folder

Take matlab/matlab-generate-wlan-waveform from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.