Реализация 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защищает от «зависания» в неописанном состоянии.- Соблюдайте правило присваиваний: такт →
<=, комбинаторика →=.