- 1 1. Giới thiệu
- 2 2. Cú pháp cơ bản và các loại câu lệnh always
- 3 Khi viết như vậy, khi tín hiệu reset về “0”, q sẽ được reset ngay lập tức; ngược lại, d sẽ được chốt theo clock.
- 4 3. Các loại phép gán trong câu lệnh always
- 5 4. Lưu ý và các lỗi thường gặp khi sử dụng câu lệnh always
- 6 5. Các mở rộng của câu lệnh always trong SystemVerilog
- 7 6. FAQ: Các câu hỏi thường gặp về câu lệnh always
- 7.1 Q1. Nên dùng if hay case trong always?
- 7.2 Q2. Nếu bỏ danh sách nhạy cảm thì chuyện gì xảy ra?
- 7.3 Q3. Tại sao lại sinh ra latch ngoài ý muốn?
- 7.4 Q4. Có nên trộn = và <= không?
- 7.5 Q5. Khác nhau giữa always_ff và always @(posedge clk)?
- 7.6 Q6. Có thể điều khiển nhiều tín hiệu trong một always không?
- 7.7 Q7. Nếu dùng <= trong mạch tổ hợp thì sao?
- 8 7. Tổng kết
1. Giới thiệu
Vai trò của câu lệnh always
trong Verilog là gì?
Trong ngôn ngữ mô tả phần cứng “Verilog HDL”, vốn được sử dụng rộng rãi trong thiết kế mạch số, câu lệnh always
đóng vai trò vô cùng quan trọng. Khác với phần mềm, Verilog không mô tả “chạy như thế nào”, mà thay vào đó là định nghĩa “tín hiệu sẽ thay đổi thế nào trong những điều kiện cụ thể”. Trong đó, câu lệnh always
là cú pháp cơ bản để mô tả hành động xảy ra khi điều kiện nhất định được đáp ứng.
Tại sao cần câu lệnh always
?
Trong Verilog, có hai loại cách mô tả hoạt động của mạch:
- Mạch tổ hợp: Ngõ ra thay đổi ngay lập tức khi ngõ vào thay đổi
- Mạch tuần tự: Ngõ ra thay đổi dựa theo tín hiệu xung nhịp (clock) hoặc sự kiện thời gian
Chỉ với câu lệnh assign
thì không thể mô tả các nhánh điều kiện phức tạp hoặc việc lưu trữ trạng thái. Đây chính là lúc cần đến always
.
Ví dụ, để mô tả logic có nhiều điều kiện hoặc hoạt động lưu trữ bằng flip-flop, ta cần dùng always
kết hợp với cấu trúc điều khiển (if
, case
).
Các mẫu câu lệnh always
thường dùng
Câu lệnh always
có một số cách sử dụng phổ biến, tùy thuộc vào loại mạch cần thiết kế:
always @(*)
→ Dùng cho mạch tổ hợpalways @(posedge clk)
→ Mạch tuần tự đồng bộ với cạnh lên của clockalways @(posedge clk or negedge rst)
→ Mạch tuần tự có reset bất đồng bộ
Do đó, hiểu rõ câu lệnh always
, vốn là cú pháp trung tâm của Verilog, là bước đầu tiên không thể thiếu với mọi kỹ sư thiết kế phần cứng.
Mục tiêu của bài viết
Bài viết này sẽ giải thích toàn diện về câu lệnh always
trong Verilog, từ cú pháp cơ bản, cách sử dụng nâng cao, các lỗi thường gặp, cho đến mở rộng trong SystemVerilog.
- Biết cách viết đúng câu lệnh
always
- Hiểu nguyên nhân lỗi trong quá trình tổng hợp logic
- Nắm rõ sự khác biệt giữa
=
và<=
- Tránh các lỗi phổ biến của người mới bắt đầu
Bài viết nhằm trở thành tài liệu hữu ích, dễ hiểu và thực tế cho cả người mới và người đã có kinh nghiệm.
2. Cú pháp cơ bản và các loại câu lệnh always
Cú pháp cơ bản của always
Câu lệnh always
trong Verilog được sử dụng để thực thi lặp lại dựa trên một điều kiện nhất định (danh sách nhạy cảm – sensitivity list). Cú pháp cơ bản như sau:
always @(danh_sách_nhạy_cảm)
begin
// Các xử lý cần thực hiện
end
Điểm quan trọng trong cú pháp này là phần “danh sách nhạy cảm” (sensitivity list). Đây là nơi định nghĩa “tín hiệu nào thay đổi thì khối lệnh này sẽ được kích hoạt”.
Sử dụng always @(*)
cho mạch tổ hợp
Trong mạch tổ hợp, ngõ ra phải thay đổi ngay lập tức mỗi khi ngõ vào thay đổi. Khi đó, ta dùng @(*)
trong danh sách nhạy cảm.
always @(*) begin
if (a == 1'b1)
y = b;
else
y = c;
end
Với cách viết này, khi một trong các tín hiệu a
, b
, c
thay đổi, khối always
sẽ được thực thi và ngõ ra y
sẽ được tính toán lại.
Lợi ích của việc dùng @(*)
- Tự động thêm tất cả tín hiệu ngõ vào vào danh sách nhạy cảm
- Tránh lỗi không đồng nhất giữa mô phỏng và tổng hợp do thiếu tín hiệu
Sử dụng always @(posedge clk)
cho mạch tuần tự
Trong mạch tuần tự, trạng thái thay đổi đồng bộ với tín hiệu clock. Khi đó ta sử dụng posedge clk
trong danh sách nhạy cảm.
always @(posedge clk) begin
q <= d;
end
Ở đây, tại cạnh lên của clock (posedge
), giá trị d
sẽ được chốt vào q
. Ký hiệu <=
là phép gán non-blocking, thường dùng trong mạch tuần tự.
posedge
và negedge
posedge
: Hoạt động tại cạnh lênnegedge
: Hoạt động tại cạnh xuống
Lựa chọn cạnh phù hợp tùy vào mục đích thiết kế.
always @(posedge clk or negedge rst)
với reset bất đồng bộ
Trong nhiều mạch phức tạp, ta cần thêm chức năng reset. Mô tả reset bất đồng bộ thường được viết như sau:
always @(posedge clk or negedge rst) begin
if (!rst)
q <= 1'b0;
else
q <= d;
end
Khi viết như vậy, khi tín hiệu reset về “0”, q
sẽ được reset ngay lập tức; ngược lại, d
sẽ được chốt theo clock.
Phân biệt giữa mạch tổ hợp và mạch tuần tự
Loại mạch | Câu lệnh always sử dụng | Đặc điểm |
---|---|---|
Mạch tổ hợp | always @(*) | Ngõ ra thay đổi ngay lập tức theo ngõ vào |
Mạch tuần tự | always @(posedge clk) | Hoạt động đồng bộ với clock |
3. Các loại phép gán trong câu lệnh always
Trong Verilog có 2 cách gán khác nhau
Bên trong câu lệnh always
của Verilog, có 2 toán tử gán khác nhau:
=
: gán blocking (blocking assignment)<=
: gán non-blocking (non-blocking assignment)
Nếu không hiểu rõ sự khác biệt này mà viết code, sẽ dễ dẫn đến hoạt động không mong muốn hoặc sự khác biệt giữa kết quả mô phỏng và kết quả tổng hợp. Đây là điểm cực kỳ quan trọng.
Gán blocking (=
) là gì?
Gán blocking có nghĩa là một câu lệnh được thực hiện xong thì mới đến câu tiếp theo. Nó gần giống với cách điều khiển trong phần mềm truyền thống.
always @(*) begin
a = b;
c = a;
end
Trong ví dụ này, a = b
được thực hiện trước, sau đó c = a
được thực hiện với giá trị mới của a
. Thứ tự gán có ảnh hưởng trực tiếp đến logic, vì vậy cần chú ý đến thứ tự.
Ứng dụng chính
- Trong mạch tổ hợp (dùng trong
if
,case
) - Khi không cần lưu trạng thái
Gán non-blocking (<=
) là gì?
Gán non-blocking có nghĩa là tất cả các câu lệnh được đánh giá đồng thời và kết quả sẽ được cập nhật cùng lúc. Cách này thể hiện rõ tính song song của phần cứng.
always @(posedge clk) begin
a <= b;
c <= a;
end
Trong trường hợp này, cả a <= b
và c <= a
đều được đánh giá đồng thời và cập nhật sau cạnh clock. Do đó, c
sẽ nhận giá trị cũ của a
trong chu kỳ clock trước.
Ứng dụng chính
- Mạch tuần tự (register, flip-flop)
- Khi cần duy trì và lan truyền chính xác nhiều trạng thái
Bảng so sánh gán blocking và non-blocking
Đặc điểm | Blocking (= ) | Non-blocking (<= ) |
---|---|---|
Thứ tự thực thi | Thực hiện tuần tự từ trên xuống | Đánh giá đồng thời, cập nhật cùng lúc |
Ứng dụng chính | Mạch tổ hợp | Mạch tuần tự |
Thời điểm cập nhật | Cập nhật ngay | Cập nhật sau cạnh clock |
Lỗi thường gặp | Tạo latch ngoài ý muốn | Giá trị không được cập nhật đúng |
Điều gì xảy ra nếu trộn lẫn?
Không nên trộn =
và <=
trong cùng một khối hoặc cùng một tín hiệu. Ví dụ dưới đây có thể chạy trong mô phỏng, nhưng sẽ gây lỗi sau khi tổng hợp phần cứng:
always @(posedge clk) begin
a = b;
a <= c;
end
Ở đây, a
được gán 2 lần với hai cách khác nhau, khiến cho giá trị cuối cùng không xác định rõ.
Nguyên tắc sử dụng
- Dùng
=
trong mạch tổ hợp (always @(*)
) - Dùng
<=
trong mạch tuần tự (always @(posedge clk)
)
Chỉ cần tuân thủ nguyên tắc này, bạn đã tránh được phần lớn lỗi phổ biến.

4. Lưu ý và các lỗi thường gặp khi sử dụng câu lệnh always
Lỗi khi viết danh sách nhạy cảm (sensitivity list)
Nếu không khai báo đúng tín hiệu nhạy cảm sẽ dễ gây bug
Trong Verilog, danh sách nhạy cảm (@(...)
) trong câu lệnh always
phải chỉ rõ tín hiệu nào thay đổi sẽ kích hoạt khối lệnh. Ví dụ sau chỉ ghi một phần tín hiệu:
always @(a) begin
if (b)
y = 1'b1;
else
y = 1'b0;
end
Trong đoạn code trên, sự thay đổi của b
sẽ không được phát hiện. Do đó, khi b
thay đổi, ngõ ra y
không cập nhật → gây lỗi.
Giải pháp: sử dụng @(*)
Để tránh quên hoặc thiếu tín hiệu trong danh sách nhạy cảm, nên viết:
always @(*) begin
if (b)
y = 1'b1;
else
y = 1'b0;
end
@(*)
sẽ tự động đưa tất cả tín hiệu được tham chiếu trong khối lệnh vào danh sách nhạy cảm, giúp code an toàn và dễ bảo trì.
Tạo latch ngoài ý muốn
Bỏ sót nhánh trong if
hoặc case
sẽ tạo latch
Nếu không gán giá trị cho biến trong tất cả các nhánh, công cụ tổng hợp sẽ chèn latch để giữ giá trị cũ. Ví dụ:
always @(*) begin
if (enable)
y = d; // khi enable=0, y giữ nguyên giá trị cũ
end
Code này trông đúng, nhưng khi enable
= 0 thì y
không được gán → latch sẽ được sinh ra tự động.
Giải pháp: gán giá trị ở mọi trường hợp
always @(*) begin
if (enable)
y = d;
else
y = 1'b0; // luôn gán giá trị cho y
end
Bằng cách luôn gán giá trị cho biến, ta tránh được latch không mong muốn.
Cấu trúc điều kiện quá phức tạp
Khi dùng if
hoặc case
quá phức tạp, dễ bỏ sót một số trường hợp → dẫn đến hành vi không xác định hoặc thiếu logic.
Lỗi phổ biến: quên default
trong case
always @(*) begin
case(sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
// thiếu xử lý cho 2'b11
endcase
end
Ở đây, nếu sel = 2'b11
, ngõ ra y
sẽ không xác định.
Giải pháp: thêm nhánh default
always @(*) begin
case(sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
default: y = 1'b0; // giá trị an toàn
endcase
end
Nhờ có default
, ngõ ra luôn có giá trị xác định → tăng độ an toàn của thiết kế.
Lưu ý khi điều khiển nhiều tín hiệu cùng lúc
Khi một khối always
điều khiển nhiều tín hiệu, thứ tự gán hoặc thiếu gán có thể gây quan hệ phụ thuộc không mong muốn. Với mạch phức tạp, nên tách thành nhiều khối always
để rõ ràng và dễ bảo trì.
Tổng hợp các lỗi thường gặp
Vấn đề | Nguyên nhân | Giải pháp |
---|---|---|
Ngõ ra không cập nhật | Danh sách nhạy cảm thiếu tín hiệu | Dùng @(*) |
Sinh ra latch | Bỏ sót gán trong một số trường hợp | Dùng else hoặc default |
Hành vi không xác định | case không bao quát hết | Luôn có default |
Điều khiển quá phức tạp | Nhiều tín hiệu trong một khối | Tách nhỏ thành nhiều khối always |
5. Các mở rộng của câu lệnh always
trong SystemVerilog
always_comb
: chuyên dùng cho mạch tổ hợp
Tổng quan
always_comb
hoạt động gần giống với always @(*)
trong Verilog truyền thống, nhưng nó chỉ rõ ràng rằng đây là logic tổ hợp.
always_comb begin
y = a & b;
end
Lợi ích chính
- Tự động tạo danh sách nhạy cảm
- Công cụ sẽ cảnh báo khi có latch ngoài ý muốn
- Ngăn xung đột với biến cùng tên đã định nghĩa trước đó
Ví dụ (so sánh với Verilog)
// Verilog
always @(*) begin
y = a | b;
end
// SystemVerilog
always_comb begin
y = a | b;
end
always_ff
: chuyên dùng cho mạch tuần tự (flip-flop)
Tổng quan
always_ff
được dùng để mô tả mạch tuần tự điều khiển bằng clock, bắt buộc phải có điều kiện kích hoạt như posedge clk
hoặc negedge rst
.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
Lợi ích chính
- Chỉ cho phép gán non-blocking (
<=
),=
sẽ báo lỗi - Công cụ kiểm tra chính xác danh sách nhạy cảm
- Dễ nhận biết đây là mạch tuần tự → tăng tính bảo trì
always_latch
: chuyên dùng cho mạch latch
Tổng quan
always_latch
được dùng khi muốn mô tả latch một cách rõ ràng. Tuy nhiên, do latch thường không mong muốn, nên chỉ dùng khi thật sự cần.
always_latch begin
if (enable)
q = d;
end
Lưu ý
- Nếu bỏ nhánh gán trong điều kiện, latch sẽ được tạo ra rõ ràng
- Hạn chế sử dụng, chỉ dùng khi thiết kế yêu cầu
So sánh các cú pháp trong SystemVerilog
Cú pháp | Ứng dụng | Tương ứng trong Verilog | Đặc điểm |
---|---|---|---|
always_comb | Mạch tổ hợp | always @(*) | Tự động danh sách nhạy cảm, cảnh báo latch |
always_ff | Flip-flop | always @(posedge clk) | Đồng bộ clock, tránh lỗi gán |
always_latch | Latch | always @(*) (khi thiếu nhánh) | Chỉ rõ latch, dễ phát hiện lỗi |
Xu hướng hiện nay: sử dụng SystemVerilog
Trong các dự án hiện đại, cú pháp của SystemVerilog thường được khuyến nghị vì tăng tính an toàn và dễ đọc. Nhờ các công cụ hỗ trợ phân tích, always_ff
và always_comb
giúp phát hiện lỗi “viết nhưng không chạy” ngay từ sớm.
Đặc biệt trong các dự án lớn hoặc làm việc nhóm, cú pháp rõ ràng giúp dễ hiểu ý đồ thiết kế, tăng hiệu quả review code và bảo trì.
6. FAQ: Các câu hỏi thường gặp về câu lệnh always
Phần này giải đáp những thắc mắc phổ biến khi sử dụng câu lệnh always
trong Verilog và SystemVerilog. Nội dung được trình bày ngắn gọn, dễ hiểu, bao gồm cả vấn đề thường gặp trong thực tế thiết kế.
Q1. Nên dùng if
hay case
trong always
?
Đáp: Hãy chọn dựa trên số lượng và độ phức tạp của điều kiện:
- 2–3 điều kiện đơn giản →
if
dễ đọc hơn - Nhiều trạng thái rõ ràng →
case
giúp code sáng sủa, giảm lỗi
Đặc biệt, case
khuyến khích liệt kê đầy đủ các nhánh → hạn chế lỗi bỏ sót.
Q2. Nếu bỏ danh sách nhạy cảm thì chuyện gì xảy ra?
Đáp: Khi danh sách nhạy cảm thiếu tín hiệu, một số thay đổi sẽ không kích hoạt khối always
. Kết quả: ngõ ra không cập nhật.
Điều này gây khác biệt giữa mô phỏng và phần cứng thực tế. Cách khắc phục: dùng @(*)
hoặc always_comb
trong SystemVerilog.
Q3. Tại sao lại sinh ra latch ngoài ý muốn?
Đáp: Khi nhánh điều kiện (if
, case
) không gán giá trị cho biến trong mọi trường hợp, công cụ tổng hợp sẽ tự thêm latch để giữ giá trị cũ.
Ví dụ sai:
always @(*) begin
if (en)
y = d; // khi en=0, y giữ nguyên
end
Giải pháp:
always @(*) begin
if (en)
y = d;
else
y = 1'b0;
end
Q4. Có nên trộn =
và <=
không?
Đáp: Không. Nguyên tắc:
- Mạch tổ hợp → dùng
=
(blocking) - Mạch tuần tự → dùng
<=
(non-blocking)
Nếu trộn, mô phỏng có thể chạy nhưng phần cứng thực sẽ hoạt động sai.
Q5. Khác nhau giữa always_ff
và always @(posedge clk)
?
Đáp: Chúng gần giống nhau về hoạt động, nhưng always_ff
an toàn hơn:
Tiêu chí | always @(posedge clk) | always_ff |
---|---|---|
Danh sách nhạy cảm | Lập trình viên tự viết | Công cụ tự kiểm tra |
Lỗi gán | = vẫn có thể chạy | Lỗi biên dịch nếu dùng sai |
Độ rõ ràng | Ý đồ thiết kế không rõ ràng | Xác định rõ: mạch tuần tự |
Q6. Có thể điều khiển nhiều tín hiệu trong một always
không?
Đáp: Có thể, nhưng nếu quá nhiều tín hiệu sẽ khó bảo trì và dễ mắc lỗi. Khi đó, hãy tách thành nhiều khối always
nhỏ hơn.
Khi nên tách:
- Mỗi ngõ ra hoạt động độc lập
- Khi có cả logic đồng bộ và bất đồng bộ
Q7. Nếu dùng <=
trong mạch tổ hợp thì sao?
Đáp: Vẫn có thể mô phỏng, nhưng khi tổng hợp có thể sinh ra mạch sai mong đợi. Nguyên tắc: mạch tổ hợp dùng blocking (=
).
7. Tổng kết
Câu lệnh always
– nền tảng quan trọng trong thiết kế Verilog
Trong thiết kế phần cứng bằng Verilog, câu lệnh always
là công cụ mạnh mẽ để mô tả cả mạch tổ hợp lẫn mạch tuần tự. Nó không chỉ mở rộng khả năng thiết kế mà còn giúp mô tả rõ ràng luồng điều khiển và thời gian hoạt động. Vì vậy, đây là kiến thức bắt buộc với mọi kỹ sư thiết kế, từ người mới đến chuyên gia.
Bài viết đã tập trung giải thích các điểm chính sau:
Tóm tắt nội dung
- Khác biệt và cách dùng
always @(*)
vàalways @(posedge clk)
- Sự khác nhau giữa gán blocking (
=
) và non-blocking (<=
) - Cách viết danh sách nhạy cảm và tránh tạo latch ngoài ý muốn
- Mở rộng trong SystemVerilog:
always_comb
,always_ff
,always_latch
- Trả lời các câu hỏi thường gặp (FAQ) để giải quyết vấn đề thực tế
Độ chính xác quyết định chất lượng
Trong mô tả phần cứng, mạch sẽ được tạo đúng theo code bạn viết. Do đó, chỉ một lỗi nhỏ trong mô tả có thể dẫn tới lỗi vật lý trên chip. Với always
, cần đặc biệt chú ý đến:
- Độ chính xác trong cú pháp
- Cách sử dụng phép gán (
=
và<=
) - Việc bao quát tất cả trường hợp trong điều kiện
Bước tiếp theo: thiết kế nâng cao
Khi đã nắm vững always
, bạn có thể tiến xa hơn với:
- Thiết kế FSM (Finite State Machine)
- Cấu trúc pipeline hoặc xử lý streaming
- Tạo IP core và triển khai trên FPGA
Hơn nữa, việc học thêm SystemVerilog và VHDL sẽ giúp bạn trở thành kỹ sư có thể làm việc trong nhiều môi trường thiết kế khác nhau.
Tư duy của một kỹ sư thiết kế
Thiết kế mạch không chỉ dừng lại ở “chạy được” mà còn phải đảm bảo chính xác, dễ mở rộng và dễ bảo trì.
Hy vọng rằng bài viết này không chỉ giúp bạn hiểu cú pháp always
mà còn truyền tải tư duy thiết kế an toàn và bền vững để áp dụng trong các dự án thực tế.