Метастабильность и переход между тактовыми доменами

Разбираем коварную проблему асинхронных сигналов и стандартный приём борьбы с ней.

Метастабильность — состояние триггера, когда его вход изменился слишком близко к фронту такта (нарушив setup/hold), и выход на некоторое время «зависает» между 0 и 1, прежде чем случайно скатиться к одному из значений.

Пока вся схема тактируется одним сигналом, триггеры срабатывают согласованно, и setup/hold соблюдаются по построению. Проблемы начинаются, когда сигнал приходит из другого тактового домена или вообще извне (кнопка, данные с другого чипа) — он асинхронен к нашему такту и может изменить вход триггера в запретном окне. Результат — метастабильность, одна из самых коварных проблем цифровой техники, потому что она проявляется редко и плавающе.

Что такое метастабильность

Когда вход триггера меняется ровно в окне setup/hold, триггер не успевает однозначно решить, 0 это или 1. Его выход повисает на промежуточном уровне (метастабильное состояние) и через случайное время скатывается к 0 или 1 — но к какому именно, предсказать нельзя. Если этот «дрожащий» сигнал тут же используется логикой, разные её части могут увидеть его по-разному — и схема рассыпается:

Хорошо (синхронно):  d стабилен -> [FF] -> чистый 0 или 1

Плохо (асинхронно):  d дёрнулся у фронта -> [FF] -> ~~~~~ (зависло)
                                                  |
                                  непредсказуемо -> 0 или 1
                     ^ это метастабильность

Тактовые домены и CDC

Большие проекты часто имеют несколько тактовых частот — например, 100 МГц для ядра и 25 МГц для интерфейса. Передача сигнала из одного домена в другой называется CDC (Clock Domain Crossing). Без защиты любой такой переход — риск метастабильности. Поэтому действует жёсткое правило: любой сигнал, приходящий из другого домена, нельзя использовать напрямую — его сначала синхронизируют.

Синхронизатор из двух триггеров

Стандартное лекарство — цепочка из двух триггеров (two-flop synchronizer). Первый триггер может уйти в метастабильность, но ему даётся целый такт, чтобы «успокоиться»; второй триггер захватывает уже устоявшееся значение:

// Синхронизатор: переносим async_in в домен clk через 2 триггера
reg sync_ff1, sync_ff2;

always @(posedge clk) begin
    sync_ff1 <= async_in;   // может стать метастабильным...
    sync_ff2 <= sync_ff1;   // ...но за такт устаканится; sync_ff2 уже чистый
end
// использовать в логике только sync_ff2, не async_in и не sync_ff1!

Это не убирает метастабильность полностью (математически вероятность ненулевая), а снижает её до пренебрежимо малой — настолько, что среднее время между сбоями измеряется тысячами лет. Для многобитных шин одного синхронизатора мало (биты могут «разъехаться»); там применяют другие приёмы — серую кодировку, FIFO-переходы, квитирование.

Как работает под капотом

Оценим, насколько двухтриггерный синхронизатор снижает частоту сбоев. Метрика — MTBF (среднее время между отказами); добавление триггера умножает его в огромное число раз:

import math

# Упрощённая модель MTBF метастабильности
f_clk = 100e6        # тактовая частота, Гц
f_data = 10e6        # частота смены асинхронных данных, Гц
tau = 0.2e-9         # постоянная разрешения триггера, с
T0 = 1e-9            # параметр окна метастабильности, с

def mtbf(t_resolve):
    # t_resolve — время, данное триггеру на разрешение (тактов на «успокоение»)
    return math.exp(t_resolve / tau) / (T0 * f_clk * f_data)

for n_ff, t_res in [(1, 2e-9), (2, 12e-9)]:
    years = mtbf(t_res) / (3600 * 24 * 365)
    print(f"{n_ff} триггер(а): MTBF ~ {years:.2e} лет")

Вывод:

1 триггер(а): MTBF ~ 6.98e-10 лет
2 триггер(а): MTBF ~ 3.62e+12 лет

Разница колоссальна — больше двадцати порядков: у одного триггера среднее время между сбоями исчезающе мало, а добавление второго отодвигает сбой на триллионы лет. Дополнительный такт «на разрешение» входит в экспоненту, поэтому второй триггер так драматически улучшает надёжность. Вот почему two-flop synchronizer — обязательный приём при любом CDC.

Частые ошибки

  • Использовать асинхронный сигнал напрямую. Кнопку, сигнал из другого домена или внешние данные нельзя подавать в логику без синхронизатора.
  • Синхронизировать многобитную шину поразрядно. Биты могут устаканиться в разные такты и «разъехаться»; для шин нужны FIFO или серый код.
  • Считать метастабильность «теоретической». Без синхронизатора она проявляется как редкие, плавающие, невоспроизводимые баги — кошмар отладки.

Итог

  • Метастабильность возникает, когда вход триггера меняется в окне setup/hold (асинхронный сигнал).
  • CDC — переход сигнала между тактовыми доменами; всегда требует синхронизации.
  • Two-flop synchronizer даёт первому триггеру такт «успокоиться», резко повышая MTBF.
  • Многобитные шины через домены передают FIFO или серым кодом, а не поразрядно.
Проверьте себя
1. Что такое метастабильность триггера?
AПерегрев микросхемы
BСостояние, когда вход изменился у фронта (нарушив setup/hold) и выход на время зависает между 0 и 1, потом случайно скатываясь
CСлишком высокая частота
DОшибка синтеза
2. Как защищают сигнал, приходящий из другого тактового домена (CDC)?
AИспользуют напрямую — это безопасно
BПропускают через синхронизатор из двух триггеров, давая первому такт на разрешение метастабильности
CУвеличивают тактовую частоту
DПодключают через assign
3. Почему многобитную шину нельзя просто пропустить через поразрядные синхронизаторы при переходе между доменами?
AЭто слишком медленно
BОтдельные биты могут устаканиться в разные такты и 'разъехаться', дав неверное значение
CСинхронизаторы не работают с шинами
DЭто удваивает частоту