always-блоки, тактовый сигнал и posedge

Вводим время в схему: учимся описывать элементы, которые меняются по фронту тактового сигнала.

Триггер (flip-flop) — элемент памяти на один бит, который запоминает значение входа в момент фронта тактового сигнала и хранит его до следующего фронта.

Комбинационная логика не имеет памяти — она лишь функция входов. Но любая интересная схема (счётчик, автомат, процессор) должна помнить состояние и менять его во времени. Память во времени приносит последовательностная логика, а её сердце — триггер, синхронизированный тактовым сигналом (clock).

Тактовый сигнал — пульс схемы

Тактовый сигнал — это прямоугольная волна, которая ритмично переключается 0→1→0→1 миллионы раз в секунду. Каждый переход из 0 в 1 называют передним фронтом (posedge), из 1 в 0 — задним (negedge). Триггеры «защёлкивают» вход именно по фронту — это превращает непрерывное время в дискретные такты, по которым «шагает» вся схема:

clk:  __|‾‾|__|‾‾|__|‾‾|__|‾‾|__
        ^     ^     ^     ^
      posedge — в эти моменты триггеры захватывают вход

Синхронный дизайн (все триггеры от одного такта) — основа надёжной цифровой схемотехники: события происходят строго по фронтам, и поведение схемы предсказуемо.

always-блок и рождение триггера

Последовательностную логику описывают процедурным блоком always со списком чувствительности. Запись always @(posedge clk) означает: «выполни этот блок на каждом переднем фронте clk». Внутри присваивания создают триггеры:

module dff(
    input  wire clk,    // тактовый сигнал
    input  wire d,      // вход данных
    output reg  q       // выход (хранит бит) — обязательно reg
);
    always @(posedge clk) begin
        q <= d;          // по фронту clk защёлкиваем d в q
    end
endmodule

Это классический D-триггер. Читаем как описание железа: «есть триггер, который на каждом переднем фронте clk копирует d в q». Между фронтами q держит своё значение, что бы ни делал d. Обратите внимание: выход q объявлен reg, потому что ему присваивают внутри always. И присваивание здесь — неблокирующее <= (почему именно оно — тема следующего урока, для тактируемой логики это правильный выбор).

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

Промоделируем поведение D-триггера во времени на Python: вход d меняется произвольно, но q обновляется только в моменты фронтов такта. Это показывает дискретизацию времени:

# Эмулируем D-триггер: q обновляется только на фронте такта
d_sequence = [1, 0, 0, 1, 1, 0]   # значения d перед каждым фронтом
q = 0                              # начальное состояние триггера
print("фронт | d (до) | q (после фронта)")
print("------+--------+----------------")
for cycle, d in enumerate(d_sequence):
    q = d                          # защёлкиваем d в q ровно на фронте
    print(f"  {cycle}   |   {d}    |   {q}")

Вывод:

фронт | d (до) | q (после фронта)
------+--------+----------------
  0   |   1    |   1
  1   |   0    |   0
  2   |   0    |   0
  3   |   1    |   1
  4   |   1    |   1
  5   |   0    |   0

Значение q повторяет d, но «снимок» берётся лишь в моменты фронтов. В реальной схеме это и даёт память: между тактами выход стабилен, что позволяет строить конвейеры и автоматы.

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

  • Забыть объявить выход как reg. Сигнал, которому присваивают в always, должен быть reg, иначе ошибка.
  • Смешивать posedge разных тактов в одном блоке. Один always должен тактироваться от одного фронта одного сигнала — иначе получится нереализуемое или ненадёжное железо.
  • Управлять схемой не от такта, а от данных. В синхронном дизайне всё «шагает» от тактового сигнала; самодельные «такты» из данных приводят к глитчам.

Итог

  • Последовательностная логика добавляет память во времени через триггеры.
  • Тактовый сигнал дискретизирует время; триггеры срабатывают по фронту (posedge).
  • always @(posedge clk) создаёт тактируемое железо; выходы — reg.
  • Синхронный дизайн (один такт на всё) — основа надёжной схемы.
Проверьте себя
1. Что означает always @(posedge clk)?
AВыполнять блок непрерывно
BВыполнять блок на каждом переднем фронте тактового сигнала clk
CВыполнять блок один раз при старте
DВыполнять блок при изменении любого сигнала
2. Зачем нужен тактовый сигнал в синхронной схеме?
AЧтобы подавать питание
BЧтобы дискретизировать время: триггеры обновляются строго по фронтам, делая поведение предсказуемым
CЧтобы ускорить комбинационную логику
DЧтобы уменьшить число LUT
3. Почему выход триггера q объявляют как reg?
Areg делает его быстрее
BПотому что ему присваивают значение внутри always-блока, а такие сигналы должны быть reg
Creg экономит память
DЭто требование тактового сигнала