Logic hai trạng thái và bốn trạng thái
Trong phần mềm, một bit là 0 hoặc 1. Trong Verilog, một bit có thể mang một trong bốn giá trị:
0: dây được kéo xuống mức thấp.1: dây được kéo lên mức cao.x: giá trị của dây không xác định. Trình mô phỏng không biết được.z: dây ở trạng thái trở kháng cao. Không có gì điều khiển nó.
Mô hình bốn trạng thái này tồn tại vì phần cứng cũng gặp đúng vấn đề đó. Một sợi dây thật có thể được nối xuống thấp, nối lên cao, không xác định (bị hai nguồn kéo ngược nhau), hoặc thả nổi (không có nguồn điều khiển nào). Trình mô phỏng phải mô hình hóa cả bốn thì mới hữu ích.
x xuất hiện như thế nào
Chạy đoạn này và xem đầu ra:
a được khai báo nhưng chưa bao giờ được ghi, nên nó nằm ở x. Cộng x + 5 cho ra x: mọi phép tính số học với giá trị không xác định đều cho kết quả không xác định. Đầu ra toàn là x, đúng bằng độ rộng của a.
Những nguồn x thường gặp trong thiết kế của bạn:
- Một
regđược khai báo nhưng không bao giờ reset (phần lớn Verilog tổng hợp được dùng một tín hiệu reset tường minh để xóa chúng). - Một câu lệnh
casekhông códefault, gặp giá trị đầu vào không khớp nhánh nào. - Một
wiremất nguồn điều khiển duy nhất sau khi refactor. - Một chuỗi
if/elsemà một nhánh không gán tín hiệu mà nhánh kia có gán (sinh latch mangxnếu không được phủ hết). - Đọc vượt quá cuối một vector hoặc mảng.
Lan truyền x: chỉ một chút x cũng làm hỏng tất cả
Điều tàn nhẫn ở x là nó lan truyền. Chỉ một bit x trong một toán hạng là cả kết quả thành x:
Để ý rằng 0 & x là 0 (AND với 0 luôn ra 0) và 1 | x là 1 (OR với 1 luôn ra 1). Trình mô phỏng bi quan theo từng bit nhưng vẫn tôn trọng các đồng nhất thức. Phép số học và phép so sánh thì không rộng lượng như vậy.
Đây là lý do một thanh ghi chưa khởi tạo duy nhất có thể khiến cả bus đầu ra thành xxxx. Lần ngược từ bất kỳ x nào, bạn sẽ tìm thấy nguồn gốc.
z xuất hiện như thế nào
z là giá trị của một dây không có ai điều khiển:
Hai mẫu trong đoạn code trên:
floatingchỉ được khai báo và không bao giờ được điều khiển. Nó mặc định làz.data_outlà một đầu ra ba trạng thái có chủ đích. Khienableở mức thấp, đầu ra chủ động nhả vềz. Đó là cách một bộ điều khiển bus "buông tay" để bộ khác tiếp quản.
Trên logic nội bộ, z gần như luôn sai. Trên chân hai chiều hoặc bus dùng chung, z lại hoàn toàn đúng.
So sánh bằng == và ===
Toán tử so sánh bằng thông thường == trả về x khi một trong hai toán hạng có bit x hoặc z:
=== (và bạn đồng hành !==) so sánh nghiêm ngặt từng bit, kể cả x và z. Dùng nó mỗi khi cần kiểm tra có phải x/z hay không trong testbench. === không tổng hợp được, nhưng bên trong khối initial của testbench thì điều đó không quan trọng.
Hàm hệ thống $isunknown(expr) là cách gọn nhất để hỏi "biểu thức này có bit x hoặc z nào không?": trả về 1 nếu có, 0 nếu không.
Dùng x làm giá trị "không quan tâm" có chủ đích
Một mẫu gây tranh cãi nhưng hợp lệ: 'x trong nhánh default của một máy trạng thái báo cho công cụ tổng hợp rằng "trạng thái này không thể tới được, cứ tối ưu thoải mái":
case (state)
IDLE: next_state = go ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = 'x; // unreachable
endcase
Công cụ tổng hợp có thể dùng x để gộp trạng thái và giảm số cổng logic. Khi mô phỏng, nếu lập luận của bạn sai và nhánh default thực sự được chạy tới, bạn sẽ thấy x lan ra từ next_state và bug lộ ra ngay lập tức.
Chỉ dùng cách này khi bạn đã suy nghĩ kỹ xem nhánh default có thật sự không thể tới được hay không. Nếu chưa, hãy đặt default về một trạng thái an toàn.
Quy trình gỡ lỗi thường dùng
Bạn đang nhìn một dạng sóng đầy x. Quy trình như sau:
- Tìm
xsớm nhất. Lần ngược dạng sóng theo thời gian. Tín hiệu thànhxsớm nhất là tín hiệu gần nguồn gốc nhất. - Tìm nguồn điều khiển của nó. Mở mã nguồn. Cái gì gán cho tín hiệu này? Một
assign? Một khốialways? - Kiểm tra đầu vào của nguồn điều khiển. Nếu vế phải có bất kỳ
xnào, sự lan truyền đang làm đúng việc của nó: bug nằm ở phía trước. - Nếu đầu vào sạch mà nguồn điều khiển vẫn tạo ra
x, nguồn điều khiển đó chưa đầy đủ. Mộtcasethiếu default, mộtifthiếuelse, một thanh ghi thiếu reset.
Hầu hết các bug "bão x" quy về một trong ba nguyên nhân: thiếu reset, thiếu default, hoặc một module con chưa được nối.
Tiếp theo
Giờ bạn đã có trọn bộ câu chuyện về kiểu dữ liệu: wire/reg, vector, tham số, hằng số dạng số và mô hình logic bốn trạng thái. Chương tiếp theo bắt đầu dùng tất cả những thứ đó để xây dựng biểu thức: đủ loại toán tử, kể cả các toán tử mức bit vốn chẳng có ý nghĩa gì trong phần mềm.
Câu hỏi thường gặp
X trong Verilog nghĩa là gì?
x là giá trị không xác định. Một tín hiệu mang giá trị x có thể là 0 hoặc 1, trình mô phỏng không biết được. Nó xuất hiện khi tín hiệu chưa được điều khiển, khi hai nguồn điều khiển tranh chấp, khi một thanh ghi được đọc trước khi reset, hoặc ở bất cứ đâu mà hành vi không xác định lẽ ra sẽ lan truyền âm thầm. Hãy coi x là dấu hiệu của bug; nó gần như không bao giờ là thứ bạn muốn.
Z trong Verilog nghĩa là gì?
z là giá trị trở kháng cao: dây hoàn toàn không được điều khiển. Đây là trạng thái hợp lệ của các đầu ra ba trạng thái (bus dữ liệu, chân hai chiều), nhưng trên tín hiệu nội bộ thì z thường là lỗi, nghĩa là 'chỗ này chưa nối gì cả'. Công cụ tổng hợp từ chối hầu hết các mẫu z nằm ngoài tín hiệu enable của đầu ra ba trạng thái tường minh.
Vì sao đầu ra Verilog của tôi là xxxx?
Gần như luôn là vì có tín hiệu không được thứ gì điều khiển, hoặc vì một phép toán đã lan truyền x từ tín hiệu khác. Hãy lần ngược lại: tín hiệu nào là x, cái gì cấp cho nó, nguồn điều khiển có đang hoạt động không? Thủ phạm thường gặp là câu lệnh case thiếu nhánh default, thanh ghi không được reset, và các wire mất nguồn điều khiển sau khi refactor.
Làm sao kiểm tra x hoặc z trong Verilog?
Dùng toán tử ===, so sánh chính xác từng bit kể cả x và z. a === 1'bx đúng khi a thực sự là x. Toán tử == thông thường trả về x khi một trong hai toán hạng có bit x, nghĩa là a == 1'bx không bao giờ cho kết quả bạn muốn. Ngoài ra còn có $isunknown(a), trả về một giá trị boolean gọn gàng.
Có thể gán x làm giá trị mặc định trong Verilog không?
Có, và đây là một kỹ thuật có chủ đích trong câu lệnh case: default: out = 'x; nói với công cụ tổng hợp 'tôi cam đoan trường hợp này không bao giờ xảy ra, cứ tối ưu thoải mái'. Cái giá phải trả là nếu nó thật sự xảy ra khi mô phỏng, x sẽ lan truyền và bạn thấy được bug. Chỉ dùng khi bạn đã chứng minh nhánh default không thể tới được, đừng dùng nó để khỏi phải viết đủ các trường hợp.