- src: transmitter, receiver, ofdm_bench, common (libiio local: + liquid-dsp) - scripts: кросс-сборка зависимостей, деплой scp -O, e2e-тест с md5 - docs: бенчмарк CPU (выбор rate 1.92 MSPS), грабли FDD/регуляторика - все текстовые файлы нормализованы в LF (.gitattributes принудительно): CRLF в toolchain.env/makefile ломал source и make на Linux - .vscode: IntelliSense в режиме linux-gcc-arm, заголовки из .xarm/include Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.8 KiB
BENCHMARK.md — протокол замеров CPU (ofdmflexframesync на ARM Pluto)
Этот документ — основание для выбора sample rate. При любом изменении PHY-параметров (M, CP, модуляция, payload) бенчмарк перегоняется и таблица обновляется в том же коммите (правило №6 CLAUDE.md).
Зачем
ofdmflexframesync (liquid-dsp) — самый дорогой блок тракта. Если его
пропускная способность на одном ядре ниже sample rate, приёмник отстаёт от
эфира: IIO-буферы какое-то время амортизируют, затем переполняются и кадры
теряются лавинообразно. Опасность в том, что на редких пакетах система
выглядит работающей — отказ проявляется только под нагрузкой.
Методика
Инструмент: src/ofdm_bench.c. Генерирует эталонный буфер
(8 кадров + гэпы, duty cycle кадров ~57%), делает int16-версии
«сигнал+шум» (SNR ~28 дБ) и «чистый шум», прогоняет каждый через
синхронизатор ≥5 с. Конвертация int16→float входит в замер (как в
receiver.c). Процесс пинится на ядро 0 (sched_setaffinity).
Меряются два режима, лимитирует худший:
- шум — стоимость дежурного поиска преамбулы (>90% времени в линии);
- сигнал+кадры — полный цикл демодуляции под нагрузкой.
Валидация замера: CRC ok должен равняться 8 × число проходов
(все сгенерированные кадры пойманы и декодированы). Иначе сборка кривая
и цифрам верить нельзя.
Окружение эталонного замера
| Параметр | Значение |
|---|---|
| Дата | 2026-07-10 |
| Плата | Pluto+ (Zynq-7020), 2 × Cortex-A9 @ 667 МГц, NEON, 1 ГБ |
| Прошивка | v0.38-4-g95aad-dirty |
| liquid-dsp | 1.8.0, с FFTW 3.3.10 (NEON) — HAVE_LIBFFTW3F 1 в config.log |
| Сборка | -O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -ffast-math -static |
| Кадр | M=64, CP=16, taper=4, QPSK, CRC32, FEC none, payload 1275 Б, header 8 Б |
| Ядро | одно (affinity CPU0) |
Результаты
буфер: 143360 сэмплов, 8 кадров на проход
шум (поиск): 5.318–5.324 Msps (186 проходов; кадров 0 — норма)
сигнал+кадры: 2.338–2.339 Msps (82 прохода; 656/656 CRC ok — валидно)
Разброс между тремя запусками < 0.1% — замер чистый.
Выводы (проектные решения)
| Sample rate | Запас (кадры) | Вердикт |
|---|---|---|
| 3.84 MSPS | 0.61× | ЗАПРЕЩЁН: не realtime, отказ под нагрузкой |
| 1.92 MSPS | 1.22× | рабочий: второе ядро свободно под IIO/RS/IO |
| 1.00 MSPS | 2.34× | резерв, если 1.92 окажется впритык в реальном тракте |
- Полезная скорость при 1.92 MSPS: ~1.3–1.5 Мбит/с (≈160–190 КБ/с) после RS(255,223), преамбул и служебки.
- Поиск на шуме (5.32 Msps) не лимитирует ни один из режимов.
- RS-декод в замер не входит: его стоимость мала относительно синхронизатора и планируется на второе ядро.
Неисследованные резервы (если понадобится > 1.92 MSPS)
- M=32/CP=8 — дешевле FFT и эквалайзер; цена: выше доля пилотов, чувствительнее к CFO. Прогнать бенчмарк с этими параметрами.
- Двухпоточный конвейер — IIO-refill + int16→float на ядре 1, синхронизатор на ядре 0: ~+10–15% и убирает джиттер refill.
- Сравнение NEON vs VFP — пересборка без
-mfpu=neon(с-mfpu=vfpv3) даёт цифру вклада векторизации для отчёта.
Как воспроизвести
make bench
scripts/deploy.sh bench <ip_платы>
ssh root@<ip_платы> '/tmp/ofdm_bench 1.92'
# критерий приёмки: "сигнал+кадры" ≥ 1.2× цели на одном ядре