Лабораторная 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, нужны:
- детерминированная согласованная фильтрация;
- воспроизводимый выбор символов;
- явные решения по битам;
- побитовое сравнение с известным исходным кадром.
Эта работа даёт все четыре пункта в одной исполняемой 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.
- Инвертируйте полярность жёсткого решения в
bpsk_hard_decision.v:out_bit <= (in_i >= 0);. Ошибочен каждый бит:FAIL: bpsk_rx_bit_recovery completed with total/payload errors = 281 / 256. Запомните эту мутацию: в Lab 5.10 есть верхний уровень, который её переживает. - Сэмплер берёт один отсчёт на символ с фиксированным смещением. Измените
start_offsetвtb/bpsk_rx_bit_recovery_meta.txtи перезапустите с--no-generate(чтобы файл не перегенерировался). Наблюдается: 451 (на отсчёт позже) по-прежнему проходит; 454 даётtotal/payload errors = 68 / 64, 446 это72 / 64. При сдвиге на полсимвола ошибочна примерно четверть бит. Объясните оба результата по глазковой диаграмме BPSK после RRC-фильтра: насколько широка открытая часть глаза? - Почему согласованный фильтр может использовать те же коэффициенты, что и TX-фильтр? Что изменится, если канал добавит поворот фазы (сравните с Lab 8.2)?
Ответы (сверены с RTL и стендом)
- Наблюдаемый вывод приведён в упражнении: когда инверсию нечем обнаружить, неверен каждый бит.
- После 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 при сдвиге на полсимвола: плата повторяет смоделированный глаз с точностью до одного бита. - Согласованный фильтр это обращённый во времени сопряжённый переданный импульс. 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 (отсчёт каждый такт)?