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

Лабораторная 5.7 — BPSK-повышающий дискретизатор символов 8x

Цель

Вставить явный мост между частотами между BPSK-маппером символов и RRC TX FIR, работающим на частоте отсчётов.

Новый блок принимает один отсчёт Q1.15 на символьной частоте и выдаёт поток 8x с вставленными нулями (zero-stuffing), нужный формирующему фильтру:

BPSK-маппер -> 8x upsampler / zero-stuffer -> RRC TX FIR -> будущий ЦАП / RF-тракт

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

Файл Назначение
blocks/block_05_fpga_hdl_flow/rtl/bpsk_upsampler_8x.v мост от символьной частоты к частоте отсчётов с SPS = 8
blocks/block_05_fpga_hdl_flow/python/generate_bpsk_upsampler_8x_vectors.py генерирует детерминированные входные и ожидаемые выходные векторы из общего пакета Блока 11
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.v самопроверяющийся Verilog-testbench
blocks/block_05_fpga_hdl_flow/tb/bpsk_upsampler_8x_input_vectors.txt сгенерированный поток символов Q1.15
blocks/block_05_fpga_hdl_flow/tb/bpsk_upsampler_8x_expected_vectors.txt сгенерированный поток отсчётов с вставленными нулями

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

python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_upsampler_8x_vectors.py

iverilog -g2012 \
  -o blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.out \
  blocks/block_05_fpga_hdl_flow/rtl/bpsk_upsampler_8x.v \
  blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.v

vvp blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.out

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

PASS: bpsk_upsampler_8x test completed without errors

Почему этот блок важен

Маппер символов и TX FIR по природе работают на разных эффективных частотах:

  • выход маппера: один комплексный символ за символьный период;
  • вход TX FIR: один комплексный отсчёт за каждый такт FPGA в домене частоты отсчётов.

Без явного блока вставки нулей маппер невозможно подключить к формирующему фильтру в детерминированной RTL-цепочке.

Контракт интерфейса

Сигнал Смысл
in_valid источник выставляет один комплексный символ Q1.15
in_ready upsampler может принять следующий символ
out_valid доступен один выходной отсчёт на частоте отсчётов
out_i, out_q первый отсчёт: символ, следующие SPS-1 отсчётов: нули

Блок держит out_valid высоким на всё 8-отсчётное расширение каждого принятого символа.

Общие входные данные

Python-генератор использует:

Общий файл Роль
tx_symbols_q15.txt точная последовательность символов из общего пакета BPSK
config.json задаёт samples_per_symbol = 8

Чего ожидать

PASS: bpsk_upsampler_8x test completed without errors (281 symbols, 2248 samples)

2248 = 281 x 8. Блок вставляет нули: символ выходит на фазе 0, следующие 7 выходных отсчётов это нули, и in_ready опущен на эти 7 тактов. RRC-фильтр из Lab 5.6 превращает эти импульсы с нулями в сформированный сигнал.

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

python tools/run_block5_hdl_smoke.py --test tb_bpsk_upsampler_8x

Упражнения

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

  1. Укоротите счётчик на один: if (phase == SPS - 2). Testbench сообщает 786 ошибок. Первая: out=(32767,0) expected=(0,0) idx=7 (следующий символ пришёл на отсчёт раньше), последняя: out_valid=0 expected=1 idx=2247 (поток закончился раньше). Ошибка на единицу в счётчике проявляется как периодическое смещение, а не как случайная ошибка.
  2. Почему вставка нулей, а не удержание (повтор символа 8 раз)? Опишите, что удержание делает со спектром до RRC-фильтра.
  3. При вставке нулей средняя мощность на выходной отсчёт падает в 8 раз. 65 коэффициентов TX в rtl/bpsk_rrc_tx_fir_taps.mem в сумме дают около 2.85 (близко к корню из 8), наибольший коэффициент: 0.387. Объясните, откуда такое масштабирование и какой запас оно оставляет до полной шкалы Q1.15.
Ответы (сверены с RTL и стендом)
  1. Наблюдаемый вывод приведён в упражнении: счётчик, ошибающийся на единицу, даёт периодическое рассогласование.
  2. Удержание символа на 8 отсчётов свёртывает последовательность импульсов с 8-отсчётным прямоугольником: спектр умножается на sinc, который проседает по полосе и лишь частично подавляет образы. RRC-фильтр после него формировал бы уже прямоугольно отфильтрованный импульс, поэтому импульс TX перестал бы быть корнем из приподнятого косинуса, согласованный фильтр RX перестал бы ему соответствовать, и появилась бы межсимвольная интерференция. Вставка нулей оставляет образы во весь рост, но целиком отдаёт форму импульса RRC-фильтру, который их и убирает.
  3. Коэффициенты имеют единичную энергию (Σ h² = 1,000), поэтому каждый символ несёт ту же энергию, что один отсчёт полной шкалы; их сумма 2,85 ≈ √8: усиление по постоянному току, возвращающее множитель 8, потерянный при вставке нулей. Каждый выходной отсчёт это сумма по одной полифазной ветви, не больше Σ|h[p + 8m]| = 0,55 амплитуды символа: около 5,2 дБ запаса до полной шкалы Q1.15 (8,2 дБ над самым большим отводом 0,387).

Лабораторная 5.7b: повышение частоты и TX-фильтр как один полифазный фильтр

Нули, которые вставляет этот блок, дальше по тракту не бесплатны: bpsk_rrc_tx_fir их умножает. Для выходной фазы p (0..7) ненулевой вход встречают только отводы p, p+8, ..., p+64, поэтому

y[8n + p] = sum over m of h[p + 8m] * s[n - m],   m = 0..8

rtl/bpsk_rrc_tx_polyphase.v вычисляет именно это: 9 умножителей на канал с коэффициентами, меняющимися с фазой, регистровое дерево сумматоров, те же округление и насыщение. У него порты пары (вход символов с in_ready, выход отсчётов), и он проверяется потактово против bpsk_upsampler_8x, за которым стоит bpsk_rrc_tx_fir:

iverilog -g2012 -o /tmp/tx_pp.vvp \
  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_tx_polyphase.v \
  blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rrc_tx_polyphase_equivalence.v
vvp /tmp/tx_pp.vvp
PASS: bpsk_rrc_tx_polyphase equals bpsk_upsampler_8x + bpsk_rrc_tx_fir clock for clock (600 symbols, 4800 samples, 0 saturated)

Vivado 2021.1 на xc7z020clg400-2 на 100 МГц (отчёт):

Реализация LUT FF DSP48E1 WNS
bpsk_upsampler_8x + bpsk_rrc_tx_fir 169 2303 96 +3,038 нс (фильтр)
bpsk_rrc_tx_polyphase 404 541 14 +3,233 нс

Тот же выход, седьмая часть DSP-блоков и четверть триггеров; лишние LUT: мультиплексоры коэффициентов. Аппаратный TX-тракт сохраняет исходную пару, так что на плате ничего не меняется.

  1. В проекте 18 умножений (по 9 на канал), а DSP-блоков 14. Синтезируйте его (python tools/generate_block5_bpsk_vivado_reports.py --top bpsk_rrc_tx_polyphase пишет отчёты) и прочитайте «DSP: Preliminary Mapping Report» в журнале синтеза: большинство блоков получает вход B шириной всего 2-3 бита. Куда делась остальная часть каждого коэффициента и чего это стоит в LUT?
  2. Почему согласованный фильтр приёмника в лабораторной 5.8 не может использовать тот же приём и какая структура на приёмной стороне могла бы (подумайте о децимации после согласованного фильтра)?

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

  • Объяснено, почему граница «маппер: FIR» является многочастотным интерфейсом.
  • Показаны тайминги in_ready и out_valid минимум для двух символов.
  • Указано, что ожидаемый выход: один отсчёт символа и семь нулей.
  • Показано, как этот блок питает bpsk_rrc_tx_fir.v на следующем шаге интеграции.

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

BPSK-upsampler 8x превращает один отсчёт Q1.15 на символьной частоте в восемь выходных отсчётов на частоте отсчётов.
Это явный мост по времени между маппером символов и RRC TX FIR.
С этим блоком TX-тракт HDL получает детерминированный входной поток на частоте отсчётов для формирования импульса.