Mở đầu: Khi mạch của bạn "kẹt" ở một trạng thái không ai định nghĩa
Bạn thiết kế 1 FSM 3 trạng thái, mã hoá bằng 2 bit — nhưng 2 bit lại biểu diễn được tới
4 tổ hợp. Trạng thái thứ 4 đó không nằm trong sơ đồ thiết kế, không ai viết code xử lý
nó. Bình thường, mạch không bao giờ tự nhảy vào đó — cho đến một ngày nhiễu điện, hạt vũ trụ (cosmic ray),
hay điều kiện khởi động bất thường đẩy flip-flop vào đúng tổ hợp "ma" này. Không có nhánh
case nào khớp, không có lối thoát được định nghĩa — mạch kẹt vĩnh viễn ở
trạng thái không tồn tại trên sơ đồ, và bạn sẽ debug hàng giờ vì mọi thứ "trông đúng" trên giấy.
Bài này dạy cách mô hình hoá hành vi có nhiều "chế độ" khác nhau bằng máy trạng thái hữu hạn (FSM): phân biệt Moore và Mealy, dùng typedef enum thay vì số ma thuật (magic number) của
Verilog cũ, đánh đổi giữa 2 cách mã hoá trạng thái (binary vs one-hot), và quan trọng nhất — cách đảm bảo
mọi trạng thái, kể cả trạng thái không định nghĩa, đều có lối thoát. Cuối bài, bạn tự tay
dựng 1 FSM đèn giao thông và 1 bộ phát hiện chuỗi bit — cả hai đều chạy trên chính engine VeriLite của
trang này.
always_ff vừa học. Tài nguyên ngoài: HDLBits, ChipVerify.
1. Moore vs Mealy — 2 triết lý thiết kế FSM
Mọi FSM đều có 2 phần: trạng thái hiện tại (lưu trong flip-flop) và đầu ra (output). Điểm khác biệt giữa 2 mô hình kinh điển nằm ở việc đầu ra phụ thuộc vào gì:
| Mô hình | Output phụ thuộc vào | Đặc điểm |
|---|---|---|
| Moore | Chỉ trạng thái hiện tại | Output ổn định trong cả chu kỳ clock, dễ debug, nhưng có thể trễ 1 chu kỳ so với input |
| Mealy | Trạng thái hiện tại + input hiện tại | Phản hồi nhanh hơn (cùng chu kỳ với input đổi), nhưng dễ sinh glitch nếu input không ổn định |
Demo đèn giao thông trong bài này là Moore (đèn nào sáng chỉ phụ thuộc trạng thái, không phụ thuộc input nào khác). Bộ phát hiện chuỗi bit ở Mục 5 cũng được thiết kế theo kiểu Moore (có 1 trạng thái "FOUND" riêng) để dễ trực quan hoá trên sơ đồ trạng thái — cách thiết kế Mealy tương đương sẽ được so sánh ngắn gọn trong phần đó.
2. typedef enum — thay thế magic number của Verilog cũ
Verilog cũ thường mã hoá trạng thái bằng hằng số nguyên trần (magic number):
parameter RED=0, GREEN=1, YELLOW=2;. Đọc code sau này, bạn phải nhớ "2 nghĩa là gì" — dễ nhầm
lẫn, dễ gõ sai số. SystemVerilog dùng typedef enum để đặt tên trực tiếp cho từng trạng thái:
// Verilog cũ: magic number, dễ nhầm lẫn
parameter RED = 0, GREEN = 1, YELLOW = 2;
reg [1:0] state;
// ... case (state) 0: ... 1: ... — đọc lại sau 6 tháng, "0" là gì nhỉ?
// SystemVerilog: typedef enum, tên trạng thái RÕ RÀNG
typedef enum {RED, GREEN, YELLOW} state_t;
state_t state;
// ... case (state) RED: ... GREEN: ... — đọc là hiểu ngay
Ngoài việc dễ đọc, typedef enum còn giúp công cụ bắt lỗi gõ nhầm tên ngay
lúc biên dịch (vì mỗi tên trạng thái là 1 định danh có kiểu riêng), điều mà magic number kiểu số nguyên
trần không làm được.
3. Mã hoá trạng thái: Binary vs One-hot
typedef enum không tự quyết định trạng thái được lưu trữ vật lý ra sao — đó là việc của
cách mã hoá (state encoding), và có 2 lựa chọn phổ biến:
| Kiểu mã hoá | Số bit cho N trạng thái | Ưu điểm | Nhược điểm |
|---|---|---|---|
| Binary | ⌈log₂N⌉ bit (vd 3 trạng thái → 2 bit) |
Ít flip-flop nhất — tiết kiệm diện tích | Logic giải mã (decode) phức tạp hơn, có thể chậm hơn ở tần số cao |
| One-hot | N bit (mỗi trạng thái 1 bit riêng, chỉ 1 bit "1" tại 1 thời điểm) | Giải mã cực đơn giản (chỉ cần đọc đúng 1 bit) — nhanh hơn ở FPGA (nhiều flip-flop có sẵn) | Tốn nhiều flip-flop hơn hẳn khi N lớn |
Ví dụ 8 trạng thái: binary chỉ cần 3 bit (3 flip-flop), one-hot cần 8 bit (8 flip-flop) — gấp hơn 2 lần.
Đổi lại, mỗi flip-flop trong one-hot tương ứng trực tiếp 1 trạng thái, nên logic
if (state == S3) chỉ cần đọc đúng 1 dây thay vì so sánh cả bus nhị phân. Trên FPGA (dư
flip-flop nhưng logic
LUT giới
hạn), one-hot thường được ưu tiên; trên ASIC (diện tích đắt đỏ), binary phổ biến hơn. VeriLite (engine
trang này) luôn dùng binary ngầm định (qua typedef enum) — đủ để dạy khái niệm, việc chọn
encoding tường minh sẽ gặp lại ở công cụ tổng hợp thật (Bài 10).
// Binary: 3 trạng thái chỉ cần 2 bit — tool tự chọn khi dùng typedef enum mặc định
typedef enum logic [1:0] {RED, GREEN, YELLOW} state_binary_t;
// RED=2'b00, GREEN=2'b01, YELLOW=2'b10 (2'b11 là tổ hợp KHÔNG dùng đến — xem Mục 4)
// One-hot: 3 trạng thái cần đủ 3 bit, mỗi bit ứng 1 trạng thái
typedef enum logic [2:0] {
RED_OH = 3'b001,
GREEN_OH = 3'b010,
YELLOW_OH = 3'b100
} state_onehot_t;
// Đọc "đang ở GREEN?" chỉ cần kiểm tra bit số 1, không cần so sánh cả bus
4. Cạm bẫy: Trạng thái treo (illegal/unreachable state)
Đây chính là kịch bản mở đầu bài: nếu số bit mã hoá nhiều hơn số trạng thái thực sự cần (rất phổ biến với binary encoding không tròn lũy thừa 2, hoặc bất kỳ one-hot nào bị nhiễu thành tổ hợp không hợp lệ), sẽ tồn tại những tổ hợp bit không được định nghĩa trong sơ đồ thiết kế.
default — không có lối thoát khỏi trạng thái lạ
❌ Sai — thiếu default, trạng thái lạ không có lối thoát:
always_comb begin
case (state)
RED: next_state = GREEN;
GREEN: next_state = YELLOW;
YELLOW: next_state = RED;
// KHÔNG có default! Nếu state rơi vào tổ hợp thứ 4 (2'b11),
// next_state không được gán — synthesis sẽ tạo ra latch (Bài 2)
// hoặc giữ nguyên trạng thái lạ mãi mãi tuỳ công cụ.
endcase
end
✅ Đúng — default đưa mọi trạng thái lạ về trạng thái an toàn:
always_comb begin
case (state)
RED: next_state = GREEN;
GREEN: next_state = YELLOW;
YELLOW: next_state = RED;
default: next_state = RED; // Bất kỳ tổ hợp lạ nào cũng thoát về RED
endcase
end
Đây chính xác là cùng một cạm bẫy latch inference đã học ở Bài 2, chỉ khác ngữ cảnh: ở Bài 2 là thiếu nhánh cho input hợp lệ, ở đây là thiếu nhánh cho trạng thái không hợp lệ — nhưng hậu quả giống hệt nhau: 1 phần tử nhớ không định nghĩa xuất hiện ngoài ý muốn.
5. Demo tương tác: FSM đèn giao thông
FSM Moore đơn giản nhất: 3 trạng thái, không phụ thuộc input nào ngoài clock/reset, tự động chuyển vòng RED → GREEN → YELLOW → RED. Bấm Step Clock để tự chứng kiến sơ đồ trạng thái "nhảy" đồng bộ với waveform.
Bấm Step Clock nhiều lần — trạng thái hiện hành luôn tô sáng lime trên sơ đồ, đúng chu trình RED → GREEN → YELLOW → RED.
module fsm_traffic (
input logic clk, rst,
output logic red, yellow, green
);
typedef enum {RED, GREEN, YELLOW} state_t;
state_t state, next_state;
// Process 1: thanh ghi trạng thái (tuần tự)
always_ff @(posedge clk) begin
if (rst)
state <= RED;
else
state <= next_state;
end
// Process 2: logic trạng thái kế tiếp (tổ hợp)
always_comb begin
case (state)
RED: next_state = GREEN;
GREEN: next_state = YELLOW;
YELLOW: next_state = RED;
default: next_state = RED; // Bắt buộc — tránh trạng thái treo
endcase
end
// Process 3: logic output (tổ hợp, chỉ phụ thuộc state — đúng kiểu Moore)
assign red = (state == RED);
assign yellow = (state == YELLOW);
assign green = (state == GREEN);
endmodule
always_ff), logic trạng thái kế tiếp (always_comb
+ case), và logic output (assign). Đây là
template 3-process phổ biến trong công nghiệp vì dễ đọc, dễ debug từng phần riêng biệt
— khác với template 2-process (gộp next-state và output logic chung 1 khối always_comb),
nhanh viết hơn nhưng khó tách bạch khi mạch phức tạp.
6. Demo tương tác: Bộ phát hiện chuỗi bit "1011"
Một FSM thực dụng hơn: nhận 1 bit mỗi chu kỳ clock, phát hiện khi 4 bit gần nhất tạo thành chuỗi
1011 (cho phép chồng lấn — overlap). Đây là bài toán kinh điển để luyện tư duy vẽ sơ đồ trạng
thái trước khi viết code.
Bấm bit_in để chọn 0/1 cho bit tiếp theo, rồi Step Clock. Thử nhập
tuần tự 1, 0, 1, 1 — trạng thái sẽ đi IDLE → S1 → S10 → S101 → FOUND, và
detected=1 đúng lúc đến FOUND.
module seq_detector_1011 (
input logic clk, rst, bit_in,
output logic detected
);
typedef enum {IDLE, S1, S10, S101, FOUND} state_t;
state_t state, next_state;
always_ff @(posedge clk) begin
if (rst)
state <= IDLE;
else
state <= next_state;
end
always_comb begin
case (state)
IDLE: next_state = bit_in ? S1 : IDLE;
S1: next_state = bit_in ? S1 : S10;
S10: next_state = bit_in ? S101 : IDLE;
S101: next_state = bit_in ? FOUND : S10;
FOUND: next_state = bit_in ? S1 : S10; // cho phép chồng lấn (overlap)
default: next_state = IDLE;
endcase
end
assign detected = (state == FOUND);
endmodule
FOUND không phải "điểm dừng" — ngay chu kỳ tiếp theo, FSM phải tiếp tục theo dõi
bit mới để phát hiện chồng lấn (vd chuỗi 1011011 chứa 2 lần khớp 1011, bắt đầu
ở vị trí 0 và vị trí 3). Vì vậy từ FOUND, nếu bit tiếp theo là 1 thì coi như vừa thấy 1 bit
"1" mới (chuyển về S1), nếu là 0 thì tương đương đã thấy "10" (chuyển về S10) — logic giống hệt từ
IDLE/S1, chỉ khác điểm xuất phát. Đây là lý do phiên bản Mealy của cùng bài toán này thường gọn hơn
(không cần trạng thái FOUND riêng, chỉ cần gắn output "detected" trực tiếp lên cạnh chuyển từ S101 khi
bit_in=1) — đánh đổi giữa số trạng thái và độ phức tạp output logic.
Tóm lược
Bài này trang bị công cụ mô hình hoá hành vi nhiều "chế độ" bằng FSM:
- ✅ Moore: output chỉ phụ thuộc trạng thái (ổn định, dễ debug); Mealy: output phụ thuộc cả input (phản hồi nhanh hơn).
- ✅
typedef enumthay magic number — dễ đọc, công cụ bắt lỗi gõ nhầm tên trạng thái. - ✅ Mã hoá binary (ít flip-flop) vs one-hot (giải mã đơn giản, nhanh trên FPGA) — đánh đổi diện tích/tốc độ.
-
✅ Luôn viết
defaulttrong case trạng thái kế tiếp — tránh trạng thái treo không lối thoát, cùng họ lỗi với latch inference (Bài 2).
Bài tiếp theo chuyển sang kiểm chứng thiết kế bằng testbench — cách chứng minh mạch bạn vừa xây thực sự đúng, không chỉ "trông có vẻ đúng".
Trắc nghiệm ôn tập
Câu 1
FSM kiểu Moore có đặc điểm gì khác biệt so với Mealy?
Câu 2
Lợi ích chính của typedef enum so với magic number (Verilog cũ) là gì?
Câu 3
Mã hoá one-hot cho N trạng thái cần bao nhiêu flip-flop?
Câu 4
Vì sao khối case tính trạng thái kế tiếp của FSM cần có default?
Tải file code thực hành minh họa bài học
File SystemVerilog gồm FSM đèn giao thông và bộ phát hiện chuỗi 1011, kèm testbench kiểm tra chu trình trạng thái và các lần phát hiện chuỗi. Chạy được trên Verilator hoặc dán vào EDA Playground:
Tải về fsm_examples.sv
Bình luận