Сброс: синхронный и асинхронный

Учимся приводить схему в известное начальное состояние — и разбираем два вида сброса.

Сброс (reset) — сигнал, переводящий триггеры схемы в заранее заданное начальное состояние; без него после включения питания триггеры содержат непредсказуемые значения.

Когда FPGA только включилась, значения триггеров не определены — там «мусор». Чтобы схема стартовала из известного состояния (счётчики с нуля, автоматы — в исходном состоянии), нужен сброс. Это не мелочь: без корректного сброса схема может вести себя непредсказуемо при каждом включении. Сброс бывает двух видов — синхронный и асинхронный, и выбор между ними влияет и на поведение, и на надёжность.

Синхронный сброс

Синхронный сброс срабатывает только по фронту такта: сигнал rst проверяется внутри тактируемого блока, как обычное условие. Список чувствительности содержит только clk:

always @(posedge clk) begin
    if (rst)
        q <= 0;        // сброс произойдёт на ближайшем фронте такта
    else
        q <= d;
end

Плюс: сброс — обычная синхронная логика, легко анализируется по таймингу, не создаёт особых путей. Минус: чтобы сброс сработал, должен идти такт; если такт остановлен, сброс «не дойдёт».

Асинхронный сброс

Асинхронный сброс срабатывает немедленно, не дожидаясь такта. Для этого его добавляют в список чувствительности блока — обычно по фронту:

always @(posedge clk or posedge rst) begin
    if (rst)
        q <= 0;        // сработает сразу при rst=1, даже без фронта такта
    else
        q <= d;
end

Плюс: схема сбрасывается мгновенно, даже если такт не идёт, — удобно при подаче питания. Минус: снятие асинхронного сброса в неудачный момент (близко к фронту такта) может вызвать метастабильность, поэтому на практике сброс снимают синхронно — это называют «асинхронная установка, синхронное снятие».

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

Главное различие — когда сброс вступает в силу. Промоделируем оба вида: подадим импульс сброса между фронтами и сравним момент, когда выход обнулится:

# Сетка времени; rst активен в моменты t=3 и t=4 (между фронтами)
# фронты такта — в чётные моменты времени t=0,2,4,6,8
q_sync = 5
q_async = 5
print(" t | такт? | rst | q_синхр | q_асинхр")
print("---+-------+-----+---------+---------")
for t in range(9):
    edge = (t % 2 == 0)               # фронт такта на чётных t
    rst = 1 if t in (3, 4) else 0
    if rst:
        q_async = 0                   # асинхронный реагирует сразу
    if edge:
        q_sync = 0 if rst else q_sync + 1   # синхронный — только по фронту
    print(f" {t} |   {'V' if edge else '.'}   |  {rst}  |    {q_sync}    |    {q_async}")

Вывод:

 t | такт? | rst | q_синхр | q_асинхр
---+-------+-----+---------+---------
 0 |   V   |  0  |    6    |    5
 1 |   .   |  0  |    6    |    5
 2 |   V   |  0  |    7    |    5
 3 |   .   |  1  |    7    |    0
 4 |   V   |  1  |    0    |    0
 5 |   .   |  0  |    0    |    0
 6 |   V   |  0  |    1    |    0
 7 |   .   |  0  |    1    |    0
 8 |   V   |  0  |    2    |    0

Видно ключевое: асинхронный q обнулился уже в момент t=3 (как только пришёл rst), а синхронный дождался ближайшего фронта в t=4. Это и есть практическая разница: асинхронный сброс быстрее, но требует осторожного снятия.

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

  • Совсем забыть про сброс. Без него счётчики и автоматы стартуют из «мусора», и поведение непредсказуемо.
  • Асинхронно снимать асинхронный сброс. Снятие сброса близко к фронту такта вызывает метастабильность; снимайте синхронно.
  • Смешивать стили сброса в проекте. Выберите один подход (чаще синхронный для FPGA) и придерживайтесь его — это упрощает анализ тайминга.

Итог

  • Сброс приводит триггеры в известное начальное состояние; без него — «мусор» после включения.
  • Синхронный сброс срабатывает по фронту такта; легче для анализа тайминга.
  • Асинхронный сброс срабатывает мгновенно; снимать его лучше синхронно.
  • В FPGA-проектах чаще предпочитают синхронный сброс или схему «асинхронная установка, синхронное снятие».
Проверьте себя
1. Когда срабатывает синхронный сброс?
AНемедленно, как только rst=1
BТолько на ближайшем фронте тактового сигнала
CПри выключении питания
DНикогда
2. Как объявляют асинхронный сброс в always-блоке?
Aalways @(posedge clk)
Balways @(posedge clk or posedge rst)
Calways @(*)
Dassign rst = 0
3. Почему асинхронный сброс рекомендуют снимать синхронно?
AЧтобы сэкономить LUT
BСнятие сброса близко к фронту такта может вызвать метастабильность
CИначе сброс не сработает
DЭто требование синтаксиса