PTP сервер дома. Часть 7
Семён сохраняет полезное_)Всем привет! Расписал подробнее как пока вижу архитектуру что будет жить на FPGA
1. Clock & Reset Manager (clock_reset.v)
Что делает:
Создаёт стабильный глобальный тактовый сигнал и синхронизированный reset для всей FPGA.
Входы:
osc_in — пин OCXO 25 МГц
ext_reset_n — внешний сигнал сброса (или от power-on)
Выходы:
clk_25m — основной глобальный clk (25 МГц)
rst_n_sync — 3-stage синхронизированный reset
clk_1ghz — быстрый clk для TDC (через отдельный PLL)
Реализация:
Gowin PLL IP (2 инстанса):
PLL1: 25 МГц → 25 МГц (low jitter для всей логики)
PLL2: 25 МГц → 800–1600 МГц (для TDC IOLogic, рекомендуется 1000 МГц)
Global Clock Buffer (GCLK) на выходах
Reset synchronizer (3 FF)
Ресурсы: ~15 LUT + 2 PLL
Связь с регистрами: нет (базовый модуль)
2. I2C Slave + Regfile (i2c_slave_regfile.v)
Что делает:
Полноценный I²C Slave (адрес 0x58) + большой регистровый файл 128 байт (расширенный вариант, который ты просил).
Входы:
scl, sda (пины)
Все статусы от остальных модулей
Выходы:
sda_out (open-drain)
Все 128 регистров (0x00–0x7F)
Реализация:
Gowin IP Core I2C_SLAVE (IPUG504) + собственный регистровый файл на массиве reg [7:0] regs[0:127]
Автоматический little-endian для 16/32/64-бит
Автосброс бита START_TDC_CAL после запуска
Поддержка repeated start и interrupt
Ресурсы: ~700–900 LUT
Связь: все регистры (STATUS, CONTROL, PHASE_ERROR_PS, DEVICE_DNA, TEMPERATURE_C и т.д.)
3. PPS Validator with TDC (pps_validator_tdc.v) — два инстанса (GNSS + DS)
Что делает:
Синхронизирует сырой PPS, измеряет период с точностью ~25–40 пс, проверяет допуск и считает джиттер.
Входы:
pps_raw (прямой пин от GNSS или DS3231)
tdc_lsb_ps_cal (из самокалибровки)
Выходы:
pps_ok
period_ps (32 бит)
jitter_ps (16 бит)
tdc_raw (32 бит)
Реализация:
Gowin_TDC IP (один на канал)
Double FF синхронизатор
Логика расчёта периода и moving-average jitter (за 8 периодов)
Ресурсы: ~350 LUT + 1 TDC IP на один канал
Регистры: GNSS_PERIOD_PS / DS_PERIOD_PS, JITTER_GNSS_PS / JITTER_DS_PS, GNSS_TDC_RAW / DS_TDC_RAW
4. Reference Selector FSM (ref_selector_fsm.v)
Что делает:
Принимает окончательное решение, какой PPS подавать на AD9545 (GNSS > DS3231 > Holdover).
Входы:
gnss_valid_mcu, ds_valid_mcu (из регистров 0x02/0x03)
gnss_pps_ok, ds_pps_ok
force_* биты из CONTROL
phase_error_ps (для hitless)
Выходы:
selected (2 бит: 00=GNSS, 01=DS, 10=Holdover)
holdover_flag
ref_switch_count (счётчик)
Реализация:
5-состояний FSM (с hysteresis 5–10 с)
Таймер потери сигнала
Приоритет force от MCU
Ресурсы: ~450 LUT
Регистры: SELECTED_GNSS / SELECTED_DS / HOLDOVER_ACTIVE / REF_SWITCH_COUNT
5. PPS Mux + Glitch-Free Programmable Delay (pps_mux_glitchfree.v)
Что делает:
Выбирает нужный PPS и добавляет точную задержку (0–~3.2 нс с шагом ~12.5 пс) без глитчей.
Входы:
selected (из FSM)
delay_steps (из регистра 0x20)
pps_gnss_sync, pps_ds_sync
Выход:
pps_out_to_ad9545 (пин REFA AD9545)
Реализация:
2:1 mux
ODDR (для чистого фронта)
IODELAY primitive (динамическое обновление только когда PPS=0)
Защита от глитча при смене delay
Ресурсы: ~120 LUT + ODDR + IODELAY
Регистр: DELAY_STEPS
6. Phase Monitor with TDC (phase_monitor_tdc.v)
Что делает:
Измеряет фазовую ошибку между выбранным PPS и feedback PPS из AD9545 с точностью десятков пикосекунд.
Входы:
pps_selected (уже с задержкой из модуля 5)
feedback_pps (пин из AD9545)
Выходы:
phase_error_ps (signed 32 бит)
phase_alarm
Реализация:
Два Gowin_TDC (для selected и feedback)
Расчёт разницы + alarm
Ресурсы: ~400 LUT + 2 TDC IP
Регистры: PHASE_ERROR_PS, PHASE_ALARM, FEEDBACK_TDC_RAW
7. Self_Calibration_TDC (self_cal_tdc.v)
Что делает:
Автоматически калибрует реальное значение LSB TDC (пс на счёт) с помощью эталонного 1 Гц от делителя.
Входы:
start_cal (бит из CONTROL 0x01)
Выходы:
lsb_ps_calibrated
cal_done, cal_error
Реализация:
Делитель clk_25m → 1 Гц
Отдельный TDC + накопитель за 60 секунд
Формула: lsb_ps = 1_000_000_000 / (среднее_количество_тактов)
Ресурсы: ~300 LUT + 1 TDC IP
Регистры: TDC_LSB_PS_CAL, CAL_STATUS, START_TDC_CAL
8. ADC & Temperature Reader (adc_reader.v)
Что делает:
Читает встроенный температурный сенсор и сенсоры напряжения Gowin.
Входы:
start_adc_read (бит из CONTROL)
Выходы:
temperature_c_x10
vcc_core_mv, vcc_io_mv
Реализация:
Gowin ADC IP (каналы: Temperature + Voltage Sensors)
Обновление каждые 200 мс
Ресурсы: ~150 LUT + 1 ADC IP
Регистры: TEMPERATURE_C, INTERNAL_VCC_CORE_mV, INTERNAL_VCCIO_mV, ADC_STATUS
9. Status / LED / IRQ Aggregator (status_led_irq.v)
Что делает:
Собирает все статусы, управляет LED и генерирует прерывание на MCU.
Выходы:
led_gnss, led_ds, led_lock, led_holdover
irq_out (на пин MCU)
Реализация:
Простая логика OR для IRQ (смена selected, phase_alarm, cal_done и т.д.)
Ресурсы: ~80 LUT
10. Top Module (gm_ptp_fpga_top.v)
Что делает:
Инстанцирует и соединяет все 9 модулей + Gowin primitives (PLL, ADC, ODDR, IODELAY, TDC).
Особенности:
Все сигналы синхронизированы на clk_25m
.sdc файл с false_path для PPS и set_clock_groups
DEVICE_DNA читается один раз при старте
Ресурсы всего проекта: ~4.5–5.5k LUT + 4–5 PLL/TDC/ADC IP (GW5A-LV25MG121 загружен на ~20–25%)
А как fpga будет принимать решение что лучше?
FPGA принимает решение о выборе источника PPS по довольно простой, но надёжной иерархической логике с несколькими уровнями защиты от ложных переключений.
Вот как это устроено на практике (в модуле ref_selector_fsm.v):
Иерархия приоритетов (от самого высокого к низкому)
Force-команды от MCU (самый высокий приоритет — override всего остального)
Если в регистре CONTROL (0x01) установлен бит FORCE_GNSS → всегда GNSS
Если FORCE_DS → всегда DS3231
Если FORCE_HOLDOVER → всегда holdover (синтезированный PPS или просто ничего не подаём на AD9545)
→ Это позволяет оператору / софту принудительно зафиксировать источник при отладке, тестировании или известной неисправности.
GNSS как основной источник (приоритет по умолчанию)
Условия, при которых выбирается GNSS:
GNSS_VALID_MCU = 1 (MCU уверен, что приёмник имеет хороший фикс)
И
GNSS_PPS_OK = 1 (TDC-валидатор подтверждает, что период 0.999–1.001 с и джиттер не слишком большой)
DS3231 как резервный источник
Выбирается только если GNSS недоступен:
GNSS_VALID_MCU = 0 ИЛИ GNSS_PPS_OK = 0 более N секунд (обычно 10–30 с)
И
DS_VALID_MCU = 1
И
DS_PPS_OK = 1
Holdover (последний уровень)
Если оба источника плохие:
GNSS_VALID_MCU = 0 или GNSS_PPS_OK = 0
И
DS_VALID_MCU = 0 или DS_PPS_OK = 0
→ переходим в holdover
Дополнительные защитные механизмы (чтобы не дёргаться)
Hysteresis / таймер потери
Источник считается потерянным только после 10–30 секунд непрерывно плохого сигнала
Это защита от кратковременных сбоев GNSS.
Debounce включения
Новый источник включается только после 3–5 секунд стабильности
Это предотвращение «прыжков» туда-сюда.
Phase continuity check
Переключение разрешено только если phase_error_ps < 50–100 нс в момент переключения
Джиттер-фильтр
Если JITTER_xxx_PS > порога (например 30–50 нс) → источник считается плохим
Sticky holdover
После входа в holdover выход возможен только по команде MCU или при возврате GNSS.
Не дёргаться обратно при кратком улучшении
Типичный сценарий переключения (пример)
Нормальная работа
GNSS_VALID_MCU = 1, GNSS_PPS_OK = 1, джиттер < 10 нс → GNSS
GNSS потерял фикс (MCU сбросил GNSS_VALID_MCU = 0)
→ FPGA ждёт 15 секунд (таймер), потом переключается на DS3231 (если DS_VALID_MCU = 1 и DS_PPS_OK = 1)
DS3231 тоже плохой (например, джиттер > 50 нс)
→ holdover (FPGA подаёт либо синтезированный PPS от OCXO, либо просто держит последний хороший фронт — зависит от реализации)
GNSS вернулся (GNSS_VALID_MCU = 1 + стабильный PPS)
→ FPGA ждёт ещё 5 секунд стабильности → возвращается на GNSS (если |phase_error| < 50 нс)
Также в дальнейшем планирую добавить
1. Weighted score (вес GNSS выше, чем DS3231)
Идея: вместо жёстких условий (GNSS или ничего) — вычисляем баллы качества для каждого источника и выбираем тот, у кого больше баллов.
Правила выбора:
если score_gnss > score_ds + hysteresis_margin (например 30–50) → GNSS
если score_ds > score_gnss + hysteresis_margin → DS3231
иначе → остаёмся на текущем источнике (или holdover, если оба < threshold)
Преимущества: плавные переходы, можно легко настраивать веса через I²C-регистры.
Реализация:
3–4 новых 16-битных регистра для весов/коэффициентов
~150–250 LUT
Обновление 1 раз в секунду
2. Allan deviation / ADEV-оценка качества источника за последние 100–1000 с
Реалистичный компромисс для FPGA:
Полный overlapping ADEV на τ = 1…1000 с требует хранения тысяч значений и вычислений O(n²) → нецелесообразно.
Предлагаемый упрощённый вариант:
Храним последние 64–256 значений fractional frequency deviation (y_k) и считаем ADEV только для фиксированных τ (например τ = 1 с, 4 с, 16 с, 64 с, 256 с).
Упрощённая формула (overlapping Allan variance для τ = n·1 с):
textσ_y²(τ = n) ≈ (1 / (2 · (M - 2n + 1))) · Σ [ (ȳ_{i+n} - ȳ_i)² ] для i = 1…(M-2n+1)
где ȳ_i — среднее y за n секунд (но поскольку PPS 1 Гц, чаще всего ȳ_i = y_i)
Ещё более простой и популярный вариант в железе — modified Allan deviation (mod ADEV) или просто RMS разностей за окно:
textadev_proxy(τ) = sqrt( 1/(M-n) · Σ_{i=1}^{M-n} (y_{i+n} - y_i)² )
где y_k = (period_k - 1_000_000_000) / 1_000_000_000 → fractional frequency deviation
Как хранить:
Кольцевой буфер на 256 элементов (BRAM или регистры)
Каждую секунду: y_new = (measured_period_ps - 1e12) / 1e12
Сдвигаем буфер, считаем adev_proxy для нескольких τ (1, 4, 16, 64, 256)
Использование в выборе:
Если adev_proxy_gnss(τ=64) < adev_proxy_ds(τ=64) × k → GNSS лучше
Порог обычно 1.5–3× (GNSS должен быть заметно лучше)
Ресурсы: 256 × 32 бит ≈ 1–2 кБ (BRAM или distributed RAM)
~400–800 LUT на вычисления (можно оптимизировать)
3. Автоматический возврат на GNSS только если джиттер < 5 нс и phase_error < 20 нс
Реализация — очень простая и полезная доработка:
В FSM добавляем состояние "prefer GNSS but wait for clean"
Почему это важно:
GNSS часто возвращается «грязным» (высокий джиттер, скачки фазы) после потери/восстановления сигнала.
Ждём, пока он действительно станет лучше DS3231
Ресурсы: + ~100–150 LUT + счётчик стабильности
4. Вход в holdover с синтезом PPS от OCXO (через счётчик 25 млн тактов)
Когда входим в holdover:
Запоминаем последний хороший period_ps (или усреднённый за 8–16 последних секунд)
Или просто используем номинал 1_000_000_000 ps
Улучшения:
Корректировать частоту по истории (например, средний period за последние 100 с)
Делать phase slew rate limit (не прыгать фазу резко при возврате GNSS)
Ресурсы: ~50–100 LUT + 25-битный счётчик