Сброс: синхронный и асинхронный
Учимся приводить схему в известное начальное состояние — и разбираем два вида сброса.
Сброс (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-проектах чаще предпочитают синхронный сброс или схему «асинхронная установка, синхронное снятие».