"Một dòng Verilog có thể sinh ra cả một mạch sai nếu bạn không hiểu bản chất phần cứng." Trong bài viết này, ta cùng mổ xẻ những lỗi kinh điển, khó debug nhưng rất phổ biến trong thiết kế Verilog – và cách phòng tránh để tiết kiệm hàng giờ gỡ bug!
I. Gán giá trị sai trong always block – wire hay reg?
❌ Lỗi:
always @(posedge clk) begin y = a + b; // Sai: y không phải reg
end
✅ Cách đúng:
reg [7:0] y; always @(posedge clk) begin y <= a + b; // Dùng <= cho logic tuần tự
end
💡 Ghi nhớ:
- Dùng reg khi gán trong always
- Dùng
<=
(non-blocking assignment) khi mô tả logic có clock
II. Quên default assignment trong block combinational
❌ Lỗi:
always @(*) begin if (sel == 1) y = a; // Không có else -> latch
end
✅ Cách đúng:
always @(*) begin y = 0; // Default if (sel == 1) y = a;
end
🧠 Vì sao?
Nếu không gán giá trị mặc định, trình tổng hợp sẽ hiểu bạn muốn giữ lại giá trị trước đó → Latch không mong muốn → khó timing closure, không đồng bộ hóa được.
III. Gán giá trị tuần tự bằng = (blocking) thay vì <= (non-blocking)
❌ Lỗi:
always @(posedge clk) begin a = b; b = a + 1; // Cả 2 gán cùng lúc → lỗi logic
end
✅ Cách đúng:
always @(posedge clk) begin a <= b; b <= a + 1;
end
🧠 Vì sao?
=
sẽ thực thi tuần tự như code C.- Nhưng phần cứng đồng bộ, mọi flip-flop cập nhật cùng lúc, nên phải dùng
<=
để mô tả đúng hành vi phần cứng.
IV. Clock domain crossing (CDC) mà không đồng bộ
❌ Lỗi:
// Tín hiệu từ clk_a qua clk_b
// Không xử lý gì → glitch, metastability
✅ Cách đúng:
reg sync1, sync2; always @(posedge clk_b) begin sync1 <= signal_from_clk_a; sync2 <= sync1;
end
💡 Mẹo:
- Sử dụng 2-3 stage synchronizer để tránh metastability khi tín hiệu đi qua vùng clock khác.
- Nếu truyền nhiều bit: dùng FIFO có CDC built-in (dual clock FIFO).
V. Quên reset hoặc reset không rõ ràng
❌ Lỗi:
always @(posedge clk) begin count <= count + 1; // Không reset → không xác định giá trị ban đầu
end
✅ Cách đúng:
always @(posedge clk or posedge rst) begin if (rst) count <= 0; else count <= count + 1;
end
🎯 Gợi ý:
- Luôn xác định giá trị khởi tạo rõ ràng cho các thanh ghi quan trọng.
- Với reset đồng bộ/asynchronous → tuân thủ hướng dẫn của công cụ tổng hợp.
VI. Dùng biến chưa khai báo hoặc không kết nối
❌ Lỗi:
assign y = x + z; // z chưa khai báo → silent fail hoặc gán mặc định
✅ Cách tránh:
- Luôn bật strict compile flag của trình tổng hợp (Vivado, ModelSim)
- Dùng lint tool như Verilator, iverilog -Wall để bắt lỗi sớm.
VII. Viết if, case không đầy đủ
❌ Lỗi:
always @(*) begin case (sel) 2'b00: y = a; 2'b01: y = b; // thiếu default → latch endcase
end
✅ Cách đúng:
always @(*) begin case (sel) 2'b00: y = a; 2'b01: y = b; default: y = 8'hxx; // Hoặc giá trị hợp lệ endcase
end
VIII. Tổng kết: Những bài học xương máu từ thực chiến Verilog
Thiết kế phần cứng số với Verilog đòi hỏi hiểu đúng bản chất phần cứng – vì chỉ một lỗi nhỏ như quên reset, thiếu default trong block tổ hợp hay dùng sai toán tử gán cũng có thể gây ra hậu quả nghiêm trọng: từ sinh latch, sai chức năng, đến lỗi metastability khó debug. Người mới thường mắc các lỗi như dùng sai kiểu reg/wire, không đồng bộ khi chuyển clock domain, hoặc bỏ sót nhánh trong case.
Cách tốt nhất để tránh các lỗi này là luyện tập mô phỏng kỹ, bật các cảnh báo tổng hợp, dùng công cụ lint như Verilator, và luôn viết code dựa trên mô hình mạch rõ ràng thay vì dịch từ tư duy phần mềm. Khi nắm được các nguyên tắc này, bạn sẽ viết Verilog chắc tay hơn và tự tin bước vào các dự án FPGA thực tế.
🎯 Bài tới: Thực chiến thiết kế FPGA – Bộ đếm, UART, và IP đơn giản
Bạn đã biết tránh sai, giờ là lúc viết những module thực tế – Bài 5 mình sẽ hướng dẫn các bạn thiết kế bộ đếm, UART truyền nhận cơ bản, và tái sử dụng IP hiệu quả.
Hẹn gặp lại!