Реализация FSM на Verilog

Записываем автомат на Verilog по проверенному шаблону из трёх блоков.

Канонический FSM на Verilog разделяют на три части: тактируемый регистр состояния, комбинационная логика переходов и комбинационная логика выходов — это делает автомат правильным и читаемым.

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

Кодирование состояний

Состояния — это абстрактные имена, но в железе их хранит регистр, значит, им нужны числовые коды. Их задают через parameter или localparam, чтобы код был читаемым:

localparam RED    = 2'b00;   // 2 бита хватает на 3-4 состояния
localparam GREEN  = 2'b01;
localparam YELLOW = 2'b10;

reg [1:0] state, next_state;   // текущее и следующее состояние

Двух бит достаточно для четырёх состояний. Иногда применяют one-hot кодирование (по одному биту на состояние) — оно быстрее в FPGA, но тратит больше триггеров. Для старта хватит обычного двоичного.

Три блока

Соберём светофор: RED → GREEN → YELLOW → RED. Вот канонический шаблон:

// Блок 1: регистр состояния (ТАКТИРУЕМЫЙ, неблокирующее <=)
always @(posedge clk or posedge rst) begin
    if (rst)
        state <= RED;
    else
        state <= next_state;
end

// Блок 2: логика переходов (КОМБИНАЦИОННАЯ, блокирующее =)
always @(*) begin
    case (state)
        RED:    next_state = GREEN;
        GREEN:  next_state = YELLOW;
        YELLOW: next_state = RED;
        default: next_state = RED;      // защита от «зависания»
    endcase
end

// Блок 3: логика выходов (КОМБИНАЦИОННАЯ, по состоянию — автомат Мура)
always @(*) begin
    case (state)
        RED:    lights = 3'b100;
        GREEN:  lights = 3'b001;
        YELLOW: lights = 3'b010;
        default: lights = 3'b100;
    endcase
end

Разделение строгое: Блок 1 — единственный тактируемый, он переносит next_state в state по фронту (<=). Блоки 2 и 3 — чистая комбинационная логика (=, always @(*)): они мгновенно вычисляют следующий шаг и выходы по текущему состоянию. default в case обязателен — он не даёт автомату «застрять» в неописанном состоянии.

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

Промоделируем светофор: проследим состояние и выходные лампы по тактам, чтобы увидеть цикличность RED→GREEN→YELLOW:

transitions = {"RED": "GREEN", "GREEN": "YELLOW", "YELLOW": "RED"}
outputs = {"RED": "100", "GREEN": "001", "YELLOW": "010"}

state = "RED"
print("такт | состояние | лампы (R G G)")
print("-----+-----------+-------------")
for tick in range(6):
    print(f"  {tick}  | {state:9} |    {outputs[state]}")
    state = transitions[state]    # переход на следующем фронте

Вывод:

такт | состояние | лампы (R G G)
-----+-----------+-------------
  0  | RED       |    100
  1  | GREEN     |    001
  2  | YELLOW    |    010
  3  | RED       |    100
  4  | GREEN     |    001
  5  | YELLOW    |    010

Автомат честно идёт по кругу, и на каждом состоянии горит своя лампа. В железе Блок 1 хранит состояние, а Блоки 2 и 3 постоянно «досчитывают» следующий шаг и текущие выходы.

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

  • Слить переходы и хранение в один тактируемый блок с =. Это работает, но запутывает и легко рождает ошибки; шаблон из трёх блоков надёжнее.
  • Забыть default в case. Без него автомат может попасть в неописанное состояние и «зависнуть», а синтез — вывести защёлку.
  • Использовать <= в комбинационных Блоках 2/3 или = в тактируемом Блоке 1. Нарушение правила «такт → <=, комбинаторика → =».

Итог

  • Канонический FSM — три блока: регистр состояния (такт), переходы (комбинаторика), выходы (комбинаторика).
  • Состояния кодируют через localparam; двух бит хватает на 3-4 состояния.
  • default в case защищает от «зависания» в неописанном состоянии.
  • Соблюдайте правило присваиваний: такт → <=, комбинаторика → =.
Проверьте себя
1. Из скольких блоков состоит канонический шаблон FSM на Verilog?
AИз одного тактируемого блока
BИз трёх: регистр состояния, логика переходов, логика выходов
CИз двух комбинационных блоков
DИз пяти
2. Зачем в case-блоке логики переходов нужен default?
AДля ускорения автомата
BЧтобы автомат не застрял в неописанном состоянии и не возникла защёлка
CЭто требование синтаксиса case
DЧтобы сэкономить триггеры
3. Какое присваивание используют в тактируемом блоке регистра состояния?
AБлокирующее =
BНеблокирующее <=
CОба сразу
Dassign