Лабораторная 11.46 — Сообщение из консоли одной Zynq в консоль другой¶
Идея¶
Это не ещё одна лаборатория по BER. Это пользовательский финал основной образовательной линии курса:
board A console
↓
PS A
↓ AXI-Lite mailbox
PL A: packet source → существующий QPSK TX
↓
AD936x A
↓ RF / cable
AD936x B
↓
PL B: существующий QPSK RX → packet recovery
↓ AXI-Lite mailbox
PS B
↓
board B console
Студент вводит строку на первой плате и видит ту же строку на второй.
Почему эта лаборатория важнее ещё одного DSP-блока¶
К этому моменту курс уже показывает mapper, RRC, синхронизацию, CFO/timing, BER и двухплатные измерения. Но без пользовательских данных всё это легко воспринимать как набор отдельных экспериментов.
Lab 11.46 связывает их в одну систему:
- PS отвечает за пользовательский интерфейс и packet-level software;
- PL отвечает за детерминированный bit/symbol/sample datapath;
- AXI: реальная граница между ними;
- AD936x переносит уже не тестовый паттерн как самоцель, а понятное сообщение.
Предпосылки¶
Перед этой лабораторной должны быть понятны:
- Zynq: где заканчивается PS и начинается PL;
- Lab 5.12: PS↔PL message mailbox;
- существующий двухплатный QPSK тракт и результат Lab 11.45.
Что переиспользуем¶
Не строим новый PHY. Переиспользуем доказанный тракт Block 11:
QPSK mapper
→ RRC TX
→ AD936x
→ RX matched filter / timing / carrier recovery
→ frame synchronization
→ recovered bits
Новая работа находится до mapper и после recovered bits:
PS message bytes → packet serializer → [existing modem] → packet decoder → PS message bytes
Ограничение первой версии¶
Существующий рабочий кадр использует 256 payload bits = 32 bytes. Поэтому первая radio-версия передаёт один фиксированный 32-байтный payload пакета; сообщение пользователя в нём до 27 bytes (см. Packet payload v1).
Это намеренное ограничение baseline, а не недостаток mailbox:
- PS/PL mailbox хранит до 64 bytes;
- v1 radio bridge использует один существующий 32-byte QPSK payload;
- fragmentation/multiple frames: отдельное расширение после первого PASS.
Hello from board A помещается в один кадр.
Packet payload v1¶
Внутри существующих 32 payload bytes используем простой формат:
byte 0 : payload length N, 0…27
bytes 1..2 : sequence, uint16 little-endian
bytes 3..29 : UTF-8/application bytes, zero padded
bytes 30..31: CRC-16/CCITT over bytes 0..29
То есть максимальное пользовательское сообщение v1: 27 bytes. Этого достаточно для учебного demo и оставляет packet metadata/CRC внутри уже существующего payload размера.
Численные соглашения зафиксированы явно:
| Параметр | Соглашение packet-v1 |
|---|---|
| Вариант CRC | CRC-16/CCITT-FALSE |
| Полином | 0x1021 |
| Начальное значение | 0xffff |
refin / refout |
false / false |
| Финальный XOR | 0x0000 |
| Стандартная проверка | CRC("123456789") = 0x29b1 |
| Порядок байтов sequence и CRC | little-endian |
| Байтовые линии 256-bit bus | packet byte n отображается в payload[8*n +: 8] |
| Сериализация битов | сначала младший бит каждого байта |
Mailbox уже упаковывает байты little-endian в 32-битные слова, поэтому на границе PS/PL не требуется дополнительный word swap.
В первой реализации на плате packet assembly и окончательное решение по CRC можно оставить в PS. Синтезируемый codec служит исполняемой перекрёстной проверкой и позволяет собрать автономный RTL loopback из mailbox fields; при AXI-интеграции нужно выбрать одну границу и не кодировать уже собранный packet повторно. PL детерминированно передаёт итоговые 32 payload bytes как bits/symbols/samples.
Это специально подчёркивает архитектурное разделение, а не переносит всю систему в FPGA.
Этап 1: software contract без RF¶
Убедиться, что студент понимает mailbox:
python tools/zynq_message_console.py --mock demo "Hello Zynq" --sequence 17
Это не RF simulation. Здесь проверяется только PS-visible interface.
Этап 2: одна плата, hardware PS↔PL echo¶
Собрать mailbox AXI-Lite IP и доказать:
Linux PS → AXI-Lite → PL echo → AXI-Lite → Linux PS
Ожидаемый результат:
zynq$ sudo python3 tools/zynq_message_console.py --base <mailbox_addr> send "Hello PL" --sequence 1
TX sequence=1 bytes=8 payload="Hello PL"
и чтение того же payload из RX mailbox.
Физический адрес берётся из Vivado Address Editor конкретной сборки.
Этап 3: цифровой modem loopback¶
Заменить PL echo на packet serializer + существующий QPSK digital loopback:
PS → mailbox → packet bytes → QPSK TX → digital loopback → QPSK RX → mailbox → PS
Здесь RF ещё нет, но уже проверяется правильность packet framing поверх реального modem datapath.
В репозитории теперь есть исполняемый baseline этапа 3:
qpsk_packet_v1.py: независимый Python reference для packet/CRC;qpsk_packet_v1_codec.v: синтезируемый ready/valid packet codec;qpsk_packet_frame_bridge.v: добавляет существующую 24-битную преамбулу и собирает нормализованные 256 payload bits после существующего frame sync;qpsk_packet_digital_loopback.v: переиспользует существующие mapper, RRC TX, matched filter, timing sampler, hard decision и quadrant-resolving frame sync;tb_qpsk_packet_digital_loopback.sv: передаёт"Hello from board A", sequence17и требует тот же payload сCRC=OK.
Запуск сфокусированной регрессии:
python tools/run_block5_hdl_smoke.py --no-generate \
--test tb_qpsk_packet_v1_codec \
--test tb_qpsk_packet_digital_loopback
Корректная формулировка результата:
packet digital loopback PASS
Все новые packet-блоки используют один clock, синхронный active-high reset и
ready/valid transfer. Producer удерживает payload стабильным при valid=1 и
ready=0; этот baseline не добавляет CDC.
Этот результат не доказывает AXI/Vivado integration, FPGA synthesis/timing, RF transport или двухплатный link.
Этап 4: две платы¶
Начать с безопасного кабельного соединения и требуемого затухания, затем при необходимости перейти к OTA.
Board A:
board-a$ sudo python3 tools/zynq_message_console.py --base <addr> send \
"Hello from board A" --sequence 17
TX sequence=17 bytes=18 payload="Hello from board A"
Board B:
board-b$ sudo python3 tools/zynq_message_console.py --base <addr> receive --wait 10
RX sequence=17 bytes=18 crc=OK payload="Hello from board A"
Это и есть основной acceptance criterion.
Как это прошло на стенде курса (14.10.2026)¶
Первое сообщение между двумя платами прошло по проводному радиотракту более коротким путём, чем описанный выше. Использовано то, что уже работает на железе:
- Плата A через
iio_writedevпередаёт кадр Лабораторной 11.45, в котором 256 бит полезной нагрузки и есть пакет packet-v1. - Плата B принимает его модемом в ПЛИС. ПК читает принятые биты из регистра декодированных бит
моста (
gp_ctrl[16]) и проверяет CRC.
python blocks/block_11_integrated_sdr_project/python/two_board_message.py send "Hello from board A" --sequence 17 --whiten
TX sequence=17 bytes=18 payload="Hello from board A"
RX sequence=17 bytes=18 crc=OK payload="Hello from board A"
Десять сообщений по 20 приёмов: доставлено без искажений 189 из 200, PER 5,5 %. Все 11 неудач либо не дали захвата кадра, либо были пойманы CRC; ни одно искажённое сообщение не принято (отчёт).
Два урока по дороге:
- Дифференциальный кадр нельзя просто повторять, если суммарное приращение его фазы не равно целому обороту: иначе на каждом стыке кадров возникает скачок фазы, который ломает следующую преамбулу.
- Пакет, дополненный нулями, превращается в длинную серию одного и того же символа. Скремблирование полезной нагрузки PN9 (на обоих концах) это исправило: 24 из 24 вместо 17 из 24.
С консолей самих плат, без Python на платах: sw/zmsg.c, собранная arm-linux-gnueabihf-gcc -static
из Vitis, делает то же самое на обоих концах.
board-a$ ./zmsg send "Hello from board A" --seq 17
TX sequence=17 bytes=18 payload="Hello from board A"
board-b$ ./zmsg receive
RX sequence=17 bytes=18 crc=OK payload="Hello from board A"
board-a$ ./zmsg stop
Десять сообщений по 20 приёмов с консолей: доставлено 199 из 200, одна ошибка CRC, ни одного
неверного сообщения не принято (перемерено 15.10.2026). Первый замер, 200 из 200, сделан версией
zmsg, которая иногда теряла импульс старта и тогда заново читала предыдущий кадр. При передатчике,
повторяющем одно сообщение, это может только превратить пропущенный приём в засчитанный, поэтому
после исправления число проверено заново (см. Лабораторную 11.48).
Плата A здесь повторяет сообщение циклически, и каждый приём на плате B ловит одну из многих копий.
Чтобы передать сообщение один раз, как это делает настоящее радио, нужен приёмник, который его
ждёт: это Лабораторная 11.48, zmsg send --once и zmsg receive --listen.
Что на этом этапе ещё открыто: mailbox PS/PL, передающий пакет модему. Mailbox проходит собственную проверку эхом на плате A (Лабораторная 5.12), но сообщение выше идёт от консоли к модему без него.
Что фиксировать в отчёте¶
- bitstream / build identity обеих плат;
- mailbox physical base address;
- RF-частоту, частоту дискретизации, настройки аттенюации/усиления;
- исходную строку и UTF-8 byte length;
- sequence;
- RX CRC result;
- число повторов;
- packet success rate;
- при ошибке: raw 32-byte payload или hex dump, а не только «не работает».
Минимальная кампания¶
После первого успешного сообщения выполнить не менее 100 коротких передач одного payload и сохранить:
packets sent
packets received
CRC OK
CRC failed / timeout
PER
BER из существующего modem evidence остаётся полезен, но здесь главным пользовательским показателем становится PER / successful message delivery.
Что не нужно делать до первого PASS¶
Не добавлять одновременно:
- DMA;
- interrupts;
- variable-length streaming protocol;
- fragmentation;
- encryption;
- OFDM;
- LoRa;
- Ethernet bridge.
Сначала должна пройти самая простая наблюдаемая цепочка:
text → PS → PL → RF → PL → PS → text
Критерий завершения¶
Лабораторная завершена только когда есть hardware evidence с двух плат:
- команда передачи на Board A;
- принятая строка на Board B;
sequenceсовпадает;CRC=OK;- задокументированы RF-настройки;
- короткая повторная кампания даёт измеренный PER.
До этого любые mock/RTL/digital-loopback результаты считаются промежуточными доказательствами, а не готовой двухплатной радиолинией сообщений.