Перейти к содержанию

Лабораторная 11.50 — Две платы переписываются через свои mailbox

Идея

Лабораторная 11.49 отправляла сообщения в одну сторону: плата A передавала, mailbox платы B слушал. Для переписки нужны оба направления сразу. Каждая плата всё время слушает, а когда кто-то набирает строку, на мгновение перестаёт слушать, отправляет строку и снова слушает. Программа короткая, потому что самое трудное уже делает mailbox. Ценность лабораторной в другом: в первом разговоре дошло 7 строк из 10 в каждую сторону, и все причины существовали ещё в прежних лабораторных, просто не проявлялись. Каждую прятало что-то, что односторонняя схема делала по случайности.

Как устроен zmbox chat

старт:        один раз настроить радио (LO, усиления, источник ЦАП); RX_LISTEN
цикл:         STATUS.RX_VALID?  -> вывести "<< текст", если RX_META говорит CRC_OK; RX_ACK
              строка в stdin?   -> RX_STOP; подождать 140 мс
                                   записать TX_SEQUENCE, TX_LENGTH, TX_DATA
                                   TX_START, повторять, пока STATUS.TX_BUSY не покажет, что его приняли
                                   дождаться RX_VALID: это наша же пачка, услышанная через плату; выбросить
                                   вывести ">> текст"; RX_LISTEN

Три подробности следуют из контракта mailbox из Лабораторной 11.49:

  • TX_START не принимается, пока mailbox слушает, а RX_STOP не обрывает уже начатую попытку прослушивания (до 136 мс). Программа ждёт 140 мс, дольше любой попытки, а затем повторяет TX_START, пока его не примут. Почему она ждёт, а не просто повторяет, объясняет последняя находка ниже. Смотрит она на TX_BUSY, а не на TX_DONE: TX_DONE остаётся поднятым от предыдущего сообщения.
  • Передающая плата слышит себя. TX_START передаёт и принимает в одной попытке, поэтому собственная пачка возвращается через утечку между TX и RX на плате. Эту копию чат выбрасывает.
  • Имя едет в полезной нагрузке. Packet-v1 несёт не больше 27 байт, так что «A: » плюс 24 символа это всё, что помещается в строку; длинные строки обрезаются.

Что нужно

  • Две платы с образом issue70-listen-window из репозитория артефактов (sha256 f89db5d1...). Чат работает и на issue69-listen-silent, на нём сняты результаты ниже; прежние образы с mailbox годятся для сообщений в одну сторону, но не для переписки (почему, объясняет третья находка ниже).
  • Два кабельных пути, по одному на направление. На стенде курса: плата A TX1 -> перестраиваемый аттенюатор (30 дБ) -> сплиттер -> плата B RX1, и плата B TX1 -> 30 дБ -> плата A RX1. 915 МГц, усиление RX 71 дБ на обеих платах.
  • /root/zmbox на обеих платах, собранный из blocks/block_11_integrated_sdr_project/sw/zmbox.c. Обе платы теряют /root при перезагрузке, в том числе при той, что загружает новый образ.

Без оборудования

python tools/run_block5_hdl_smoke.py --test tb_message_modem_listen

Две копии обёртки моста, T и R: T отправляет два сообщения, R слушает и должен доставить оба. После третьей находки тестбенч ещё и считает отсчёты, которые R выдаёт в свой поток ЦАП, пока слушает. Их должно быть ноль; до исправления было 2764.

Запуск

Запустите чат на обеих платах и печатайте на любой из них. Вывод ниже записан на стенде курса, строки подавал скрипт, поэтому самого набранного текста в нём нет; в терминале вы увидите и то, что набираете.

board-a$ ./zmbox chat --name A --tx-gain -30
chat as A: type a line (at most 27 bytes with the name) and press Enter; Ctrl-C ends
>> A: hello, B here?
<< B: yes, hearing you, A
>> A: loud and clear
<< B: what carrier?
>> A: 915 MHz, conducted
<< B: nice. signing off
>> A: bye
board-b$ ./zmbox chat --name B --tx-gain -50
chat as B: type a line (at most 27 bytes with the name) and press Enter; Ctrl-C ends
<< A: hello, B here?
>> B: yes, hearing you, A
<< A: loud and clear
>> B: what carrier?
<< A: 915 MHz, conducted
>> B: nice. signing off
<< A: bye

>> это строка, которую плата отправила, << строка, которую она приняла. Строка, которую набрали, но которая не дошла, на другой стороне просто не появляется: mailbox выбрасывает кадры с неверной CRC, поэтому потерянная строка никогда не превращается в неправильную.

Тот же обмен можно запустить по расписанию с компьютера и посчитать:

cd blocks/block_11_integrated_sdr_project/python
python two_board_chat.py --rounds 100 --tx-gain-b -50 --json-out chat.json

В каждом раунде плата A набирает a<k>, а через две секунды плата B набирает b<k>. Скрипт печатает для каждого направления, сколько строк дошло, сколько пришло с текстом, который никто не набирал, и сколько пришло дважды.

Что нашёл первый разговор

Сообщение вдвое короче

До переписки плата A ещё отправляла через zmsg send --once (Лабораторная 11.48). Теперь такие сообщения не доходили ни до кого, 0 из 20. Другая плата записала одно через iio_readdev: около 880 отсчётов вместо примерно 1766. Циклический поток zmsg повторялся каждые 608 отсчётов, то есть каждые полкадра. ЦАП играл каждый второй отсчёт.

Очевидным подозреваемым была только что сделанная правка мультиплексора FIFO ЦАП (issue #67, ниже). Симуляция настоящих блоков ADI с обеими версиями мультиплексора писала в FIFO одинаково, так что подозреваемый, похоже, был ни при чём, и окончательно это решил старый образ. Сразу после загрузки он отдавал поток правильно; после одного запуска zmbox тоже вдвое сжимал его.

zmbox ставил всем четырём каналам ЦАП источник DMA. В axi_ad9361 сигнал, который включает канал в распаковщике, это ровно «у этого канала источник DMA». С четырьмя включёнными каналами распаковщик читал двухканальный буфер zmsg как четырёхканальный и отдавал TX1 каждый второй отсчёт. Теперь zmbox ставит DMA только TX1 (I0, Q0), а TX2 переводит в ноль.

Урок: когда новый образ ведёт себя странно, сначала прогоните старый образ с той же последовательностью команд. Здесь виновата оказалась последовательность команд, а не образ.

Сильный сигнал прямо перед каждым сообщением

Затем плата A отправляла через zmbox send, по вызову на сообщение. Плата B приняла 6 из 40, а те же сообщения через zmsg --once дошли 40 из 40. zmbox при каждом вызове заново записывал частоту дискретизации и LO. Запись перестраивает AD9361, даже когда значение не меняется, и следующая за ней калибровка TX выдавала в эфир около 30 мс сигнала на 41 дБ выше шума прямо перед пачкой. Слушающий приёмник принимал это за пакет.

Та же ошибка была в zmsg и исчезла в Лабораторной 11.48. Исправление (писать настройку, только если она отличается) так и не попало в zmbox. С ним: 40 из 40.

Слушающая плата, которая передаёт (issue #69)

С обоими исправлениями переписка всё ещё теряла около трети строк, и ни одна из потерянных не дошла до проверки CRC: приёмник не захватывал кадр вовсе. Плата B записала эфир платы A, пока набирали шесть строк, и нашла семнадцать пачек. Каждые 138 мс слушающая плата выдавала пачку в 1344 отсчёта. Это 168 символов, кадр ПЗУ из лабораторных по BER, без вводной части.

Попытка прослушивания запускает ядро модема тем же фронтом, что и передача, и передатчик QPSK каждый раз проигрывал свой кадр. Этот кадр сталкивается с сообщением другой платы. А ещё через утечку он попадает в собственный приёмник платы и занимает ту самую попытку, которой плата слушает.

В Лабораторной 11.49 этого не было видно: у слушающей платы был выключен TX LO, и кадры не покидали микросхему. Исправление держит поток к ЦАП молчащим во время попытки прослушивания. Теперь слушающая плата ничего не выдаёт в эфир. С обоими исправлениями zmbox и TX −30 дБ на обеих платах переписка выросла с 20 из 30 строк в каждую сторону до 30 и 25 из 30.

Строки, потерянные на краю окна прослушивания (issue #70)

На образе #69 чат всё ещё терял примерно строку из десяти, опять без единой ошибки CRC, хотя каждая часть чата по отдельности почти ничего не теряла. Разбор по частям показал, что значение имеет только одно: отправляет ли zmbox chat. Плата B, отправляя из чата, достучалась до платы, которая только слушала, 361 раз из 400; та же плата через zmbox send достучалась 99 или 100 раз из 100. Разница была не в пачках. Записанные на плате A, пачки чата были той же длины (1864 отсчёта) и несли те же символы: их фазовые шаги от символа к символу совпадали с пачками zmbox send на 0,94-0,99. А потери шли сериями, в одном прогоне подряд строки 65, 66 и 67, то есть действовало что-то медленно меняющееся.

Разница была в моменте отправки. Чат перестаёт слушать и повторяет TX_START, пока mailbox его не примет, а принимает он, когда заканчивается уже начатая попытка прослушивания: каждая пачка чата уходила в конце 136-мс окна прослушивания отправителя. Объяснение, которое сходится с цифрами: окно приёмника перезапускается, когда приходит пакет, поэтому после обмена окна обеих плат начинаются почти одновременно, и следующая пачка из чата приходит к концу окна приёмника, где пакет не помещается и теряется. Сообщения zmbox send уходят в момент, который задаёт программа, а не окно. Окна приёмника напрямую не наблюдались.

Проверка была дешёвой: после RX_STOP подождать 140 мс, дольше любой попытки, и только потом стартовать. Отправка из чата выросла с 93 и 94 из 100 до 100 из 100, дважды. Ожидание 68 мс почти не помогло (96 и 95), вероятно, потому что у начатой попытки часто оставалось больше 68 мс, и пачка всё равно уходила в её конце. Теперь zmbox chat ждёт 140 мс; строка уходит на 0,14 с позже, чем могла бы, и в чате этого никто не заметит.

Исправлен и сам приёмник. Раньше попытка прослушивания заканчивалась, когда семплер выдал 65535 символов, что бы в этот момент ни приходило. Теперь окно продлевается, пока идёт пакет. Симуляция, которая отправляет сообщение за 200 и за 40 символов до конца окна, со старым приёмником теряла оба, с новым доставляет оба (tb_message_modem_listen_edge). На платах с образом issue70-listen-window и вовсе без ожидания (ZMBOX_TX_DELAY_MS=0) чат доставил 100 из 100 строк в обе стороны, дважды; образ #69 в таком же прогоне дал 92 и 84. Ожидание 140 мс в zmbox chat остаётся: оно ничего не стоит и защищает платы, на которых ещё стоит прежний образ.

И проверка, ради которой затевался чат (issue #67)

Мультиплексор FIFO ЦАП в курсовом образе писал в FIFO только пока шла пачка. Без вендорского потока DMA ЦАП между пачками держал последний отсчёт: несущая на частоте LO. С Лабораторной 11.47 это прикрывал циклический буфер нулей. Исправление пишет в FIFO по собственному запросу чтения FIFO, а когда отправлять нечего, пишет нули. Плата B отправляла пачки вообще без потока DMA (ZMBOX_NO_ZERO_STREAM=1), а плата A измеряла паузы. Со старым образом они были на 40,3 дБ выше шума, как сама пачка, с новым на 9,8 дБ, столько же, сколько с потоком нулей. Остаток это утечка TX LO, одна линия в 0,3 кГц от LO.

Результаты

Обе платы на issue69-listen-silent, zmbox chat с ожиданием 140 мс, плата A на TX −30 дБ:

TX платы B раундов A -> B B -> A
−30 дБ 60 59 53
−40 дБ 60 60 60
−50 дБ 100 100 100
−50 дБ 100 99 99

Ни одна строка не пришла искажённой или дважды, и ни один пакет не провалил CRC. По дороге, на том же образе, но без ожидания, чат доставлял 90 и 85 строк из 100 при −50 дБ, а при более громкой плате B от B к A ещё меньше. На стенде курса путь B -> A идёт без сплиттера, и на 71 дБ приёмник платы A перегружается, когда плата B громкая: когда плата A только слушала, сообщения платы B доходили 35 из 40 при −30 дБ и 40 из 40 при −50 дБ. Отсюда значение по умолчанию −50 дБ для платы B в скрипте.

Задания

  1. Зачем zmbox chat повторяет TX_START и почему проверяет TX_BUSY, а не TX_DONE?
  2. Отправляющая плата получает собственное сообщение обратно. Откуда берётся эта копия и почему её можно спокойно выбросить?
  3. Первые три неисправности выше существовали во время Лабораторной 11.49, которая доставила 498 из 500. Для каждой скажите, что в односторонней схеме её прятало.
  4. В строке чата после имени помещается не больше 24 символов. Откуда это ограничение и что понадобилось бы для длинного сообщения?
  5. Строки, отправленные из zmbox chat, терялись примерно вдесятеро чаще, чем те же строки через zmbox send, хотя пачки в эфире были одинаковые. Почему это исправляет ожидание 140 мс перед TX_START и что вместо этого пришлось бы сделать исправлению в приёмнике?
Ответы
  1. Mailbox не принимает TX_START, пока слушает, а уже начатая попытка прослушивания доходит до конца (до 136 мс) даже после RX_STOP. Поэтому первый TX_START может быть отвергнут, и программа пробует снова, пока mailbox его не примет (после последней находки она сначала ждёт 140 мс, так что первая попытка обычно проходит). TX_DONE остаётся поднятым после предыдущего сообщения, по нему не отличить новый старт от старого; TX_BUSY поднимается, только если этот старт приняли.
  2. TX_START передаёт и принимает в одной попытке, и приёмник платы слышит собственную пачку через утечку между TX и RX на плате. Эта копия ничего не говорит о том, приняла ли строку другая плата, поэтому для чата в ней нет информации.
  3. Сообщение вдвое короче: в Лабораторной 11.49 на одной плате никогда не запускали zmsg после zmbox, поэтому источники ЦАП не оставались включёнными на четыре канала. Выброс калибровки: плата A отправляла через zmsg, который уже писал настройки, только если они менялись. Кадры слушающей платы: плата B слушала с выключенным TX LO, и кадры не покидали микросхему.
  4. В packet-v1 27 байт полезной нагрузки (256 отбеленных бит на кадр), и три из них чат тратит на «A: ». Для длинного сообщения нужен либо более длинный кадр, либо несколько пакетов с номером и сборкой на приёмной стороне, а ещё решение, что делать, если один из них потерялся.
  5. Без ожидания пачка уходит, когда кончается попытка прослушивания отправителя, а окно приёмника началось примерно тогда же, поэтому пачка приходит к концу окна приёмника и не помещается. После ожидания дольше любой попытки момент отправки задаёт набранная строка, и он попадает в случайную точку окна приёмника, где пакет почти всегда помещается. Исправление в приёмнике не заканчивало бы попытку прослушивания, пока приходит пакет: если окно кончилось, а детектор пакетов всё ещё видит пакет, продлить окно до конца кадра или начать с этим пакетом следующую попытку.

Подтверждения

Что лабораторная показывает и чего не показывает

Она показывает, как две платы обмениваются набранными сообщениями в обе стороны через контракт регистров Лабораторной 5.12, с канальным уровнем в ПЛИС и без единого искажённого сообщения. Надёжной связи она не показывает: нет ни подтверждений, ни повторной передачи, и строка из ста всё ещё может пропасть. Все цифры получены на одной паре плат, одной несущей, по кабелю.

Лабораторная 11.51 добавляет в чат подтверждения и повторы, чтобы потерянная строка больше не терялась молча.