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

Лабораторная 11.31 — Стресс-развёртка оценщика грубого CFO

Цель

Lab 11.30 проверила оценщик qpsk_coarse_cfo.v на живом двухплатном канале, но две платы AD9361 оказались лишь в ~0,3 ppm друг от друга, то есть межплатный сдвиг несущей составлял около −290 Гц. Это внутри нескольких сотен герц захвата петли Костаса, поэтому главная причина существования оценщика (захват десятков кГц, недоступных петле Костаса) на кремнии по-настоящему не проверялась. Успешный прогон на −290 Гц не говорит, что оценка верна на −40 кГц.

Эта работа вводит контролируемый сдвиг. Плата A передаёт тот же циклический QPSK на carrier + Δ, пока плата B принимает на carrier, так что восстановленный сигнал несёт известную Δ (плюс собственные −290 Гц). Развёртка Δ в пределах ±70 кГц показывает на реальном сигнале сразу три свойства:

  1. оценщик 4-й степени отслеживает введённый CFO во всём однозначном диапазоне ±60 кГц;
  2. fixed-point арифметика RTL согласуется с эталоном float при каждом сдвиге, а не только около нуля;
  3. за пределами ±60 кГц оценка сворачивается (алиасируется) на 120 кГц, ровно как предсказывает 4·ω ∈ (−π, π]: граница неоднозначности проекта, продемонстрированная, а не заявленная.

Почему ±60 кГц

Оценщик возводит каждый символ в 4-ю степень, чтобы убрать модуляцию QPSK (четыре точки созвездия отображаются в одну), оставляя тон на 4·ω, где ω: приращение фазы на символ от CFO. Затем он измеряет угол этого тона. Угол однозначен только в пределах одного оборота, поэтому 4·ω должно оставаться внутри (−π, π]:

|ω| < π/4  на символ   →   |f| < Rsym/8 = 480 кСимв/с / 8 = 60 кГц

Ниже 60 кГц оценка равна истинному CFO. Выше 4·ω заворачивается, и сообщаемая частота сворачивается на 2·60 = 120 кГц это алиас, а не ошибка. Работа прогоняет оценщик прямо через эту границу, чтобы свёртка была видна в данных.

Установка

плата A  ──  циклический QPSK из ЦАП через DMA, TX_LO = carrier + Δ
   │
   │   TX1 ──▶ аттенюатор 30 дБ ──▶ RX1     (закрытый кабель SMA, база 915 МГц)
   ▼
плата B  ──  RX_LO = carrier (фиксированный) ──▶ захват сырых IQ ──▶ SSH ──▶ оценщик на хосте

CFO вводится исключительно расстройкой передающего LO платы A (форма сигнала, частота дискретизации и приёмный LO платы B не меняются) так что восстановленный сдвиг равен Δ плюс собственная разность плат, и ничего больше. Работа дословно переиспользует обвязку передатчика и приёмника Lab 11.30 (reset_tx_dma, отсоединённая потоковая передача, считывание источника ЦАП, один непрерывный захват); ловушки, от которых они защищают, см. в той работе.

Метод

Для каждой Δ: приглушить обе платы, перестроить TX_LO_frequency платы A на carrier + Δ, запустить циклический QPSK, захватить на плате B и прогнать те же оценщики float и fixed-point из coarse_cfo_ref.py по восстановленным символам (согласованный фильтр → лучшая целая под-символьная фаза → оценка 4-й степенью по последовательным окнам из 256 символов). Строка dTX=0 измеряется первой, и её результат: собственный сдвиг, относительно которого отсчитываются все ожидаемые значения.

Столбец fixed-point применяет усечение RTL SQ_SHIFT=11 к символам, масштабированным до величины согласованного фильтра ~2000 LSB, наблюдаемой на реальных захватах, поэтому f-vs-fx это разрыв между эталоном float и целочисленной арифметикой поставляемого HDL, а не между двумя float.

Результаты

Измерено 2026-07-18, база 915 МГц, TX платы A −30 дБ на аттенюатор 30 дБ, RX платы B 50 дБ. Собственный сдвиг при dTX=0: −238 Гц. Он дрейфует от прогона к прогону с температурой и допуском опоры (−194, −238 и −288 Гц на трёх холодных загрузках) и именно поэтому оценщик существует; таблица индексирована введённой Δ.

dTX измеренный CFO СКО ожидаемый (свёрнутый) ошибка EVM float-против-fixed примечание
+0,0 кГц −238 Гц 18 −238 Гц +0 Гц 4,7 % 0,15 Гц собственный
+5,0 кГц +4 605 Гц 187 +4 762 Гц −157 Гц 20 % 0,78 Гц
+10,0 кГц +9 368 Гц 42 +9 762 Гц −394 Гц 36 % 1,39 Гц
+20,0 кГц +19 218 Гц 416 +19 762 Гц −545 Гц 43 % 2,76 Гц
+30,0 кГц +29 371 Гц 1341 +29 762 Гц −392 Гц 49 % 4,53 Гц
+40,0 кГц +39 222 Гц 113 +39 762 Гц −540 Гц 40 % 2,39 Гц
+50,0 кГц +49 565 Гц 333 +49 762 Гц −197 Гц 29 % 1,80 Гц
−10,0 кГц −9 897 Гц 93 −10 238 Гц +341 Гц 37 % 1,20 Гц
−30,0 кГц −29 563 Гц 307 −30 238 Гц +675 Гц 42 % 2,91 Гц
−50,0 кГц −49 995 Гц 616 −50 238 Гц +243 Гц 42 % 1,55 Гц
+55,0 кГц +54 623 Гц 324 +54 762 Гц −139 Гц 29 % 1,38 Гц
+60,0 кГц +59 708 Гц 67 +59 762 Гц −54 Гц 9,6 % 0,25 Гц граница
+70,0 кГц −50 106 Гц 259 −50 238 Гц +132 Гц 24 % 1,07 Гц алиас → −50,2 кГц
−70,0 кГц +49 496 Гц 136 +49 762 Гц −266 Гц 26 % 1,45 Гц алиас → +49,8 кГц

Как читать таблицу:

  • Отслеживание. От −50 кГц до +55 кГц измеренный CFO следует за введённым сдвигом с ошибкой в несколько сотен герц это внутри захвата петли Костаса. Это ровно то разделение труда, на котором построен двухступенчатый приёмник: грубая ступень убирает основную часть, петля закрывает остаток.
  • Свёртка. При +70 кГц оценка не показывает +70 кГц, и это не ошибка: она показывает −50,2 кГц, точно 70 − 120. −70 кГц сворачивается в +49,8 кГц так же. Пограничная строка при +60 кГц это шарнир: 4·ω лежит чуть ниже π, EVM опускается до однозначных процентов, потому что развёрнутое созвездие почти выровнено, а чуть дальше оно заворачивается. Прямо на сингулярности окна пакета делятся между +60 и −60 кГц (одной и той же точкой на окружности 4·ω) поэтому оценки по окнам объединяются круговым средним (в области 4·ω), а не простым средним, которое попало бы в бессмысленную середину. Вдали от границы они согласуются до долей герца; от него зависит только эта строка.
  • Float против fixed. Целочисленная арифметика RTL остаётся в пределах ~4,5 Гц от эталона float во всём диапазоне, оценку ограничивает не усечение fixed-point, а конечное окно измерения (обратите внимание, как СКО следует за EVM: оба хуже всего около ±30 кГц, где сырое созвездие вращается быстрее всего внутри окна).

Значения EVM в середине развёртки высоки по построению: EVM здесь измеряется по сырым восстановленным символам до того, как петля Костаса убирает остаток, поэтому большой введённый CFO размазывает созвездие. Это свидетельство наличия реального сдвига, а не показатель качества канала: именно нижележащая петля превращает их в декодируемые символы (и превращала, при BER 0, в Lab 11.30).

Самотест (без радио)

python blocks/block_11_integrated_sdr_project/python/lab_11_31_coarse_cfo_stress_sweep.py --self-test

Применяет каждую Δ к сгенерированной форме сигнала программно (×e^{j2πΔn/fs}) и запускает то же восстановление. Без радио собственный сдвиг нулевой, поэтому оценщик должен отслеживать каждую Δ с точностью до нескольких сотен герц и чисто сворачиваться на границе; сама ошибка тоже сворачивается в ±60 кГц, чтобы сингулярность ±60 кГц (где оценка законно может принять любой знак) не читалась как промах на 120 кГц. Запускайте его перед любой аппаратной сессией, если вы меняли анализ на стороне хоста.

Запуск на железе

python blocks/block_11_integrated_sdr_project/python/lab_11_31_coarse_cfo_stress_sweep.py \
    --host-a 192.168.40.1 --host-b 192.168.20.1 --carrier 915e6

Обе платы должны быть прошиты стендовым образом (README-bench.txt для каждой платы на её SD-карте). Развёртка идёт без присмотра и сама за собой убирает: между каждой Δ она перестраивает, передаёт, захватывает и приглушает плату A, а при выходе (в том числе после сбоя) принудительно выставляет на обеих платах −89,75 дБ. Результаты записываются в coarse_cfo_stress_sweep.json рядом с напечатанной таблицей.

RF-безопасность

Это кондуктивная работа: TX1 платы A попадает на RX1 платы B через кабель и аттенюатор 30 дБ, и ничего не излучается. TX −30 дБ по умолчанию безопасен за этим аттенюатором. По воздуху предел это −50 дБ: не повышайте мощность, чтобы «увидеть сигнал», и не запускайте эту развёртку на антенне. Обе платы приглушаются при выходе; стоковый rc.user платы B при каждой загрузке заново включает передатчик 71 МГц, который общая обвязка глушит первым делом.

Что дальше

Оценщик проверен от начала до конца на кремнии во всём рабочем диапазоне, что было необходимым условием для подключения его к приёмнику в fabric. qpsk_rx_bit_recovery_chain может инстанцировать его перед петлёй Костаса за битом coarse_cfo_en, qpsk_zynq_ber_top выводит этот бит наружу, а мост gpreg управляет им через gp_ctrl[13]: так что сдвиг можно убирать в fabric во время работы одной записью в регистр. Набор проверок Блока 05 покрывает весь путь от моста вниз, и оценщик декодирует введённый сдвиг 25 кГц до BER 0.

Тайминг теперь закрывается. Наивная интеграция сильно не укладывалась в быстрый (8 нс) такт divide-select (−13,9 нс): умножитель-накопитель 4-й степени и LUT-плюс-умножитель разворота занимают каждый ~11–14 нс каскадных DSP, а синтез/phys-opt скрывали промежуточные регистры, до которых не мог дотянуться multicycle по имени. Обе цепи теперь конвейеризованы: каждый этап: одно умножение, регистрируемое по in_valid, со счётчиком заполнения, пропускающим переходные процессы заполнения конвейера, и линией задержки глубиной GUARD, сохраняющей выравнивание пакета, а multicycle накопления/разворота доходят до входов данных DSP. При COARSE_ENABLE=1 (задаётся в мосте) оценщик входит в поставляемый bitstream и больше не ограничивает тайминг: рабочий такт 16 нс укладывается с запасом +7,997 нс, а единственный остаток это уже существовавший путь согласованного фильтра −0,003 нс на такте 8 нс (присущий запас divide-select проекта; базовая линия coarse-out сама лежит на −0,035 нс). cfo_omega не изменился, поскольку постоянный CFO даёт 4·ω независимо от положения окна. Параметр COARSE_ENABLE остаётся (по умолчанию 0), так что работы по loopback/OTA по-прежнему могут исключать оценщик при компиляции. Остаётся шаг это двухплатный прогон с gp_ctrl[13]=1: приёмник захватывает реальный межплатный CFO в fabric, а не на хосте.