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

Лабораторная 5.8 — Согласованный фильтр BPSK RX и восстановление бит

Цель

Превратить детерминированный синтетический захват Блока 11 в воспроизводимую приёмную RTL-цепочку:

скорректированный захват -> согласованный RRC-фильтр -> символьный тайминг с фиксированной фазой -> жёсткое решение -> восстановленные биты

Это первый исполняемый якорь RX в HDL-маршруте, ориентированный на BER.

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

Файл Назначение
blocks/block_05_fpga_hdl_flow/rtl/bpsk_rrc_rx_fir.v обёртка RX вокруг общего 65-коэффициентного RRC FIR
blocks/block_05_fpga_hdl_flow/rtl/bpsk_symbol_timing_sampler.v выбирает один символьный отсчёт каждые 8 отсчётов после известного начального смещения
blocks/block_05_fpga_hdl_flow/rtl/bpsk_hard_decision.v отображает знак выбранного символа в бит 0/1
blocks/block_05_fpga_hdl_flow/python/generate_bpsk_rx_bit_recovery_vectors.py генерирует векторы скорректированного захвата и ожидаемые восстановленные биты
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rx_bit_recovery.v самопроверяющийся testbench восстановления RX

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

python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_rx_bit_recovery_vectors.py

iverilog -g2012 \
  -o blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rx_bit_recovery.out \
  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/tb/tb_bpsk_rx_bit_recovery.v

vvp blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rx_bit_recovery.out

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

PASS: bpsk_rx_bit_recovery completed without errors

Общие входные данные из Блока 11

Общий файл Роль
end_to_end_bpsk_reference_v1.ci16 детерминированный синтетический захваченный пакет
sample_plan.json известное начало выборки согласованного фильтра и samples_per_symbol
tx_bits.txt ожидаемая последовательность бит для сравнения BER
rrc_taps_q15.txt коэффициенты согласованного фильтра

Python-генератор применяет ту же коррекцию CFO/фазы, что и эталон MATLAB, добавляет хвост очистки FIR и выдаёт детерминированный входной поток Q1.15 для RTL-testbench.

Тракт данных

flowchart LR
    CAP[Скорректированный захват Q1.15] --> MF[Согласованный фильтр RRC]
    MF --> TS[Выборка символов с известной фазой]
    TS --> HD[Жёсткое решение]
    HD --> BITS[Восстановленные биты]
    BITS --> CMP[Сравнение с tx_bits.txt]

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

Передающей цепочки недостаточно, когда курс переходит к BER. Маршруту модема, ориентированному на BER, нужны:

  1. детерминированная согласованная фильтрация;
  2. воспроизводимый выбор символов;
  3. явные решения по битам;
  4. побитовое сравнение с известным исходным кадром.

Эта работа даёт все четыре пункта в одной исполняемой HDL-проверке.

Зафиксированные допущения

Первая работа по восстановлению RX намеренно использует известные опорные значения вместо слепого синхронизатора:

  • CFO и фаза корректируются офлайн до старта RTL-testbench;
  • символьный тайминг использует известный matched_filter_sample_start;
  • порог жёстких решений BPSK зафиксирован на нуле.

Благодаря этому цепочка достаточно проста, чтобы проверить её раньше, чем следующие блоки введут петли восстановления тайминга и несущей.

Чего ожидать

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

Контракт записан в tb/bpsk_rx_bit_recovery_meta.txt: start_offset = 450 отсчётов, sps = 8, 281 бит кадра, из них первые 25 это преамбула, поэтому оцениваются 256 бит полезной нагрузки.

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

python tools/run_block5_hdl_smoke.py --test tb_bpsk_rx_bit_recovery

Упражнения

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

  1. Инвертируйте полярность жёсткого решения в bpsk_hard_decision.v: out_bit <= (in_i >= 0);. Ошибочен каждый бит: FAIL: bpsk_rx_bit_recovery completed with total/payload errors = 281 / 256. Запомните эту мутацию: в Lab 5.10 есть верхний уровень, который её переживает.
  2. Сэмплер берёт один отсчёт на символ с фиксированным смещением. Измените start_offset в tb/bpsk_rx_bit_recovery_meta.txt и перезапустите с --no-generate (чтобы файл не перегенерировался). Наблюдается: 451 (на отсчёт позже) по-прежнему проходит; 454 даёт total/payload errors = 68 / 64, 446 это 72 / 64. При сдвиге на полсимвола ошибочна примерно четверть бит. Объясните оба результата по глазковой диаграмме BPSK после RRC-фильтра: насколько широка открытая часть глаза?
  3. Почему согласованный фильтр может использовать те же коэффициенты, что и TX-фильтр? Что изменится, если канал добавит поворот фазы (сравните с Lab 8.2)?
Ответы (сверены с RTL и стендом)
  1. Наблюдаемый вывод приведён в упражнении: когда инверсию нечем обнаружить, неверен каждый бит.
  2. После RRC-фильтров TX и RX импульс: приподнятый косинус; глазковая диаграмма открыта около центра символа и закрывается по мере сдвига момента выборки к границе символа. На один отсчёт (1/8 символа) в сторону глаз ещё широко открыт, поэтому 451 проходит. Номинальный сдвиг это 450, поэтому 454 и 446 отстоят на четыре отсчёта, то есть на полсимвола: там соседние символы дают почти столько же, сколько нужный, для некоторых битовых шаблонов они его перевешивают, и ошибается около четверти бит. Развёртка показывает открытую часть глаза: от 448 до 452 (±2 отсчёта) проходят, 447 и 453 уже дают 12 и 16 ошибок. Та же развёртка на плате Zynq (петля внутри ПЛИС через цепочку блока 11, reports/hardware/bpsk-qpsk-sampling-phase-silicon-20261014.md) даёт 0 ошибок в центре и при сдвиге на ±1 и ±2 отсчёта, 13 и 17 ошибок при −3 и +3 и 67 при сдвиге на полсимвола: плата повторяет смоделированный глаз с точностью до одного бита.
  3. Согласованный фильтр это обращённый во времени сопряжённый переданный импульс. RRC-импульс вещественный и симметричный, поэтому сам себе согласованный фильтр, а RRC TX вместе с RRC RX дают приподнятый косинус без межсимвольной интерференции в центрах символов. Поворот фазы проходит через линейный фильтр без изменений и переносит энергию из I в Q; жёсткое решение только по I деградирует и при 90 градусах отказывает, поэтому приёмнику нужно восстановление фазы (Лабораторная 8.2) до решения.

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

  • Указано, что согласованный фильтр RX использует те же rrc_taps_q15.txt, что и TX FIR.
  • Показано настроенное начальное смещение из sample_plan.json.
  • Объяснено, почему в этой работе фиксированный тайминг, а не синхронизатор.
  • Приложены лог успешного восстановления бит и полное/payload число ошибок.
  • Указано, что дальше: управление кадровым пакетом или настоящие блоки синхронизации.

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

Цепочка RX HDL переиспользовала общие коэффициенты RRC и восстановила детерминированный кадр BPSK без битовых ошибок.
Выбор символов использовал известное начальное смещение согласованного фильтра и один отсчёт каждые восемь тактов.
Это даёт первый исполняемый HDL-якорь BER перед переходом к кадровой интеграции Zynq TX/RX.

Расширение восстановления тайминга (Lab 5.8b): что дальше

Децимация с фиксированной фазой выше предполагает ровно 8 отсчётов на символ. На реальном тракте AD9361 это предположение может отличаться на доли процента, поэтому момент выборки дрейфует по 281-символьному пакету, и BER упирается в ~40 % даже в когерентном loopback (без сдвига несущей). Поэтому в курс добавлена готовая петля восстановления символьного тайминга Гарднера, которая отслеживает дрейф:

  • убывающий NCO по модулю 1 с 2 стробами на символ, линейный интерполятор (mu ≈ nco<<2), sign-Gardner детектор ошибки тайминга e = sgn(y_mid)·sgn(y_on[k]−y_on[k−1]) (не зависит от амплитуды) и ПИ-фильтр петли со степенями двойки в коэффициентах (k1 = 1/256, k2 = 1/4096) в петле нет умножителей.

Эталонные модели и RTL, побитово согласованные друг с другом:

Артефакт Путь
Модели float + fixed-point на Python (+ демо) python/bpsk_timing_recovery_model.py
Модели float + fixed-point на MATLAB matlab/bpsk_timing_recovery_model.m
Скрипт сборки Simulink (блок HDL-Coder MATLAB Function) simulink/bpsk_timing_recovery_build_simulink.m
Синтезируемый RTL rtl/bpsk_symbol_timing_recovery.v
Генератор векторов python/generate_bpsk_timing_recovery_vectors.py
Побитовая HDL-проверка tb/tb_bpsk_symbol_timing_recovery.v
Проверка BER всей цепочки (TR против фиксированной фазы) tb/tb_bpsk_zynq_ber_timing_recovery.v
Однотактная и конвейерная петля, символ за символом tb/tb_bpsk_symbol_timing_recovery_equivalence.v

bpsk_rx_bit_recovery_chain выбирает между ними параметром TIMING_RECOVERY (0: фиксированный сэмплер этой работы, 1: петля Гарднера). Runtime-мост блока 11 оставляет 0: на его коротком loopback-пакете без пропусков петля ошибается и работает хуже фиксированной фазы, поэтому петля предназначена для потоков с настоящим дрейфом. Запуск python/bpsk_timing_recovery_model.py печатает таблицу BER float / fixed-point / фиксированная фаза на пакете с дрейфом: обе модели восстановления тайминга достигают BER 0 там, где децимация с фиксированной фазой не справляется.

Реализация петли на 100 МГц

Петля это рекурсия длиной в один отсчёт: на стробе умножение интерполятора, ошибка синхронизации и фильтр петли должны выдать следующий шаг NCO раньше, чем он понадобится следующему отсчёту. В таком виде (PIPELINED = 0) Vivado размещает и разводит её примерно на 52 МГц на кристалле курса на 9,4 нс медленнее такта 100 МГц (отчёт). Режим по умолчанию PIPELINED = 1 укладывается в 100 МГц (+0,335 нс) с теми же выходными символами за счёт двух идей, применимых к большинству петель обратной связи в приёмнике:

  • Использовать запас, который уже есть у алгоритма. Следующий строб наступает не раньше чем через три отсчёта (3 * W_MAX <= 1.0 при 8 отсчётах на символ), поэтому символ и обновление петли можно сформировать через два такта после строба. Отсчёты в промежутке сдвинули NCO старым шагом; обновление переписывает NCO как «NCO после строба - k * новый шаг», где k: число отсчётов, пришедших после строба.
  • Упреждение. У знаковой ошибки Гарднера только три значения, поэтому три возможных результата фильтра петли вычисляются и регистрируются на стробе, а ошибка лишь выбирает один из них.

Цена: площадь (659 триггеров вместо 201). tb/tb_bpsk_symbol_timing_recovery_equivalence.v прогоняет обе формы на одном пакете с дрейфом и случайными пустыми тактами и требует совпадения каждого выходного символа по I и Q:

iverilog -g2012 -o /tmp/tr_eq.vvp \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_symbol_timing_recovery.v \
  blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_symbol_timing_recovery_equivalence.v
vvp /tmp/tr_eq.vvp
PASS: bpsk_symbol_timing_recovery PIPELINED=1 equals PIPELINED=0 on all 281 symbols (I and Q values, 854 idle input clocks)

Упражнение: в rtl/bpsk_symbol_timing_recovery.v заставьте коррекцию NCO не учитывать отсчёт, пришедший в такт после строба (wire [1:0] k_steps = {1'b0, consume};). Тест эквивалентности тогда падает с symbol counts reference=281 pipelined=270, expected 281: петля теряет синхронизацию и пропускает символы, а тест, который смотрит только на решённые биты вышедших символов, этого бы не заметил. Какое k использует сломанный код, в каких тактах оно неверно и почему тест падает и при -Ptb_bpsk_symbol_timing_recovery_equivalence.GAPS=0 (отсчёт каждый такт)?