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

Лабораторная 5.10 — BPSK BER верхнего уровня, готовый для Zynq

Цель

Обернуть детерминированную кадровую BPSK-цепочку в управляющий верхний уровень, который намного ближе к будущему пути интеграции с Zynq:

start -> источник бит кадра -> кадровая цепочка TX -> внешняя граница отсчётов TX
                                                   -> внешняя граница отсчётов RX -> восстановление RX -> счётчик BER -> done

Работа оставляет источник BPSK-кадра и проверку BER внутри логики, обращённой к FPGA, и выводит наружу границу TX/RX в домене отсчётов для будущего подключения AD9363, FIFO или DMA.

Исполняемый HDL-пакет

Файл Назначение
blocks/block_05_fpga_hdl_flow/rtl/bpsk_frame_bit_source.v детерминированный источник кадра на ROM с семантикой start/busy/done
blocks/block_05_fpga_hdl_flow/rtl/bpsk_ber_counter.v сравнивает восстановленные биты с тем же ROM кадра и считает полный/payload BER
blocks/block_05_fpga_hdl_flow/rtl/bpsk_zynq_ber_top.v обёртка верхнего уровня вокруг источника, цепочек TX/RX и проверки BER
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.v самопроверяющийся testbench loopback верхнего уровня

Запуск из корня репозитория:

python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_framed_loopback_vectors.py
python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_rrc_tx_fir_vectors.py

iverilog -g2012 \
  -o blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.out \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_symbol_mapper.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_upsampler_8x.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_rrc_tx_fir.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_rrc_rx_fir.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_symbol_timing_sampler.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_hard_decision.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_framed_tx_chain.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_rx_bit_recovery_chain.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_frame_bit_source.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_ber_counter.v \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_zynq_ber_top.v \
  blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.v

vvp blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.out

Ожидаемый результат:

PASS: bpsk_zynq_ber_top completed without errors

Контракт верхнего уровня

Новый верхний уровень намеренно невелик, но ориентирован на железо:

Группа сигналов Роль
start, busy, done однократный запуск кадра и статус завершения
frame_bit_count, preamble_count, start_offset детерминированные управляющие входы, выведенные из общего пакета Блока 11
tx_valid, tx_i, tx_q поток отсчётов TX к будущему ЦАП, FIFO или тракту TX AD9363
rx_valid, rx_i, rx_q поток отсчётов RX от будущего АЦП, FIFO или тракта RX AD9363
received_bits, total_errors, payload_errors компактный интерфейс результата BER
lock_inverted 1, если счётчик захватился на инвертированной преамбуле (180 градусов) и перевернул нагрузку обратно; держится после done до следующего start

Почему этот этап важен

Предыдущий testbench loopback доказал, что DSP-цепочка работает. Эта работа добавляет системную структуру, необходимую, прежде чем проект платы сможет её использовать:

  1. детерминированный источник кадра вместо прямого цикла стимулов в testbench;
  2. счётчик BER, который позже можно отобразить в память или отдать в ПО;
  3. чистый шов в домене отсчётов, к которому подключатся AD9363 или AXI/DMA;
  4. явная семантика завершения верхнего уровня для автоматизированных аппаратных тестов.

Общий контракт кадра

Верхний уровень использует:

Общий артефакт Роль
blocks/block_05_fpga_hdl_flow/rtl/bpsk_frame_bits.mem детерминированные биты кадра для источника и для проверки BER
blocks/block_05_fpga_hdl_flow/tb/bpsk_framed_loopback_meta.txt начальное смещение, число бит, число символов преамбулы и допущения об очистке

Так биты источника, эталон BER и метаданные тайминга остаются синхронизированными между отдельным loopback и верхним уровнем, готовым для Zynq.

Стратегия testbench

Самопроверяющийся testbench соединяет:

выходы отсчётов TX верхнего уровня -> задержка 24 отсчёта, знак I инвертирован -> входы отсчётов RX верхнего уровня

Инверсия знака намеренная: из-за неё каждый прогон проходит через ветку инвертированного захвата счётчика BER.

Затем проверяет:

  • start запускает ровно один кадр;
  • busy становится активным на время прогона и сбрасывается в конце;
  • done устанавливается только после завершения всего пакета;
  • счётчики BER сообщают ноль полных и payload-ошибок;
  • lock_inverted = 1: счётчик захватился на инвертированной преамбуле, которую даёт инвертированная петля.

Первая интерпретация с точки зрения железа

Этот верхний уровень всё ещё синтетический, но теперь структурно совпадает с будущим трактом платы:

источник кадра / управление -> отсчёты TX -> AD9363 TX -> RF-тракт -> AD9363 RX -> отсчёты RX -> счётчики BER

Именно сюда на следующем шаге подключаются регистры AXI-Lite, DMA, управление BRAM или специфичная для платы логика тактов и сброса.

Чего ожидать

PASS: bpsk_zynq_ber_top completed without errors (281 bits, payload errors 0)

В отличие от Lab 5.8 и 5.9, верхний уровень содержит bpsk_ber_counter, который находит кадр корреляцией принятых бит с известной преамбулой, а не доверяет фиксированному смещению.

Раннер курса генерирует векторы, компилирует, запускает симуляцию и считает ошибкой любую строку FAIL или ненулевой код выхода симулятора:

python tools/run_block5_hdl_smoke.py --test tb_bpsk_zynq_ber_top

В Vivado 2021.1 этот топ размещается и разводится на xc7z020clg400-2 на 100 МГц с запасом +1,436 нс и занимает 676 LUT, 3085 триггеров и 192 из 220 блоков DSP48E1 (87 %) (отчёт): фильтр формирования TX и согласованный фильтр RX берут по 96 (лабораторная 5.6). Всё остальное, чему на том же кристалле нужны умножители, например петля несущей или второй канал, уже не поместится. Худший путь это разветвление, а не арифметика: покадровый перезапуск RX (rst || frame_start), идущий на 2560 входов сброса согласованного фильтра, 3 уровня логики и 88 % трассировки.

Упражнения

Каждое упражнение ниже: намеренная мутация RTL в одну строку. Внесите её, запустите testbench, прочитайте сообщения и верните файл (git checkout -- <file>). Приведённые выводы получены на Icarus Verilog 12.0.

  1. Примените ту же инверсию полярности, что в Lab 5.8 и 5.9 (out_bit <= (in_i >= 0); в bpsk_hard_decision.v). Те два testbench падают с 281 / 256 ошибками. Этот падает иначе: ERROR: lock_inverted=0 on the sign-inverted loopback, expected 1. Найдите причину в bpsk_ber_counter.v: он считает совпадения преамбулы и с прямым шаблоном (m_noninv), и с инвертированным (m_inv), а при инвертированном захвате переворачивает полезную нагрузку. Это разрешение неоднозначности на 180 градусов, и реальному приёмнику оно нужно, потому что петля несущей BPSK может захватиться на 0 или на 180 градусов. Что показали бы одни счётчики ошибок?
  2. Testbench инвертирует петлю намеренно. Зачем, и что PS должен делать с CONTROL_STATUS[3] (Лабораторная 5.11) на железе?
  3. Какова вероятность, что 8 случайных бит точно совпадут с 8-битной преамбулой? Свяжите её с LOCK_PREAMBLE_BITS и LOCK_ERR_TOL.
  4. Верните поиск кадра, которым счётчик пользовался до issue #65: в bpsk_ber_counter.v замените условие ((LOCK_ERR_TOL > 0) || (lock_preamble_limit_reg == WIN[INDEX_W-1:0])) на (LOCK_ERR_TOL > 0), чтобы точный захват шёл через последовательное сравнение, и запустите python tools/run_block5_hdl_smoke.py --test tb_bpsk_lock_leading_bits. Стенд ставит перед кадром от 0 до 12 лишних бит. Наблюдается: FAIL: tb_bpsk_lock_leading_bits - 27 of 52 runs failed, например FAIL 4 zero leading bits, inverted 0: received 281, total errors 140. Кадр принят целиком, и половина его бит неверна. Почему счётчик захватывает кадр не в той позиции, а не отказывается от захвата, и почему лишние нули вредят именно этой преамбуле (00000110...)?
Ответы (сверены с RTL и стендом)
  1. Мутация компенсирует инверсию петли, поэтому счётчик теперь захватывается на прямой преамбуле, насчитывает ноль полных и payload-ошибок и сбрасывает lock_inverted в 0. Одни счётчики ошибок показали бы чистый прогон: ошибку полярности они не видят, потому что счётчик в любом случае нормализует нагрузку. Её видно только по флагу. До issue #64 полярность оставалась во внутреннем регистре invert_bits, и эта мутация проходила незамеченной.
  2. Прямая петля проверяет только неинвертированный захват, и инвертированная ветка (m_inv, invert_bits, переворот нагрузки) в регрессии никогда бы не выполнялась. Инвертированная петля прогоняет её каждый раз, а проверка lock_inverted = 1 делает ожидаемую полярность частью контракта. На железе читайте CONTROL_STATUS[3] вместе с каждым результатом BER и записывайте в лог (в gpreg-мосте Блока 11 та же информация лежит в gp_status[6:5]). На линии с фиксированной фазой, например в цифровой петле или петле внутри ПЛИС, значение меняться не должно, и его изменение указывает на ошибку знака в тракте. За петлёй несущей допустимы оба значения, и по логу видно, как часто петля попадает в каждую точку захвата. У верхнего уровня QPSK есть такой же выход: двухбитный rotation_index (k·90 градусов).
  3. Точное 8-битное совпадение случайных бит имеет вероятность 1/256 ≈ 0,4 %, удвоенную примерно до 0,8 %, потому что принимается любая полярность. С LOCK_ERR_TOL = t окно допускает до t несовпадений: P = 2 · Σ_{k≤t} C(8,k) / 256, уже 7 % при t = 1. Длинные окна захвата и малый допуск держат ложные захваты редкими; в курсе по умолчанию 8 бит и LOCK_ERR_TOL = 0.
  4. Последовательный поиск сравнивает каждый новый бит со следующим ожидаемым битом преамбулы. При несовпадении он не возвращается назад: считает текущий бит новым первым битом преамбулы и идёт дальше. Это наивный поиск подстроки без функции отказа из алгоритма Кнута-Морриса-Пратта. Преамбула начинается с пяти нулей, поэтому лишние нули перед ней принимаются за её начало. Настоящая 1 приходит туда, где поиск ждёт 0, а истинное начало уже позади: эти биты поиск уже прошёл и больше на них не смотрит. Он продолжает сравнение с более позднего бита и в конце концов находит позицию, где следующие биты случайно проходят все его проверки. Дальше он считает кадр с неверным выравниванием, и примерно половина бит расходится с ROM (140 из 281). Скользящий коррелятор на каждом новом бите сравнивает всё последнее окно, поэтому истинное начало пропустить не может. На плате та же ошибка проявлялась только при некоторых смещениях старта сэмплера, когда перед кадром появляются лишние биты (отчёт с железа). В обычном порядке работы смещение перебирается и берётся лучшее, поэтому ошибка и оставалась незамеченной.

Чек-лист отчёта

  • Объяснено значение start, busy и done.
  • Показано, как задаются frame_bit_count, preamble_count и start_offset.
  • Показаны выведенные границы отсчётов TX и RX.
  • Приведены итоговые значения счётчиков BER.
  • Указан следующий шаг интеграции: регистры AXI-Lite, подключение DMA или соединение с AD9363.

Шаблон инженерного вывода

Детерминированная цепочка BPSK-модема теперь имеет верхний уровень, готовый для Zynq, с однократным интерфейсом запуска, явным статусом завершения и счётчиками BER.
Источник кадра и проверка BER используют один и тот же ROM с битовой последовательностью, поэтому тест остаётся детерминированным.
Это правильный якорь интеграции перед подключением проекта к управлению AXI, DMA или тракту отсчётов AD9363.