"Toán tử" trong Verilog nghĩa là gì
Toán tử nhận các biểu thức mang tín hiệu và tạo ra biểu thức mang tín hiệu mới. a + b là phần cứng: công cụ tổng hợp biến nó thành một bộ cộng. a == b là phần cứng: nó tạo ra một bộ so sánh. Không có gì ở đây là "code được chạy"; tất cả đều là "mạch mà toán tử mô tả".
Danh sách toán tử đủ ngắn để học thuộc. Bài này nói về các toán tử số học, so sánh, logic và điều kiện. Bài Bitwise và rút gọn nói về các toán tử mức bit không có bản tương đương trực tiếp trong phần mềm.
Số học: + - * / %
Ba điều cần biết:
- Phép chia là phép chia nguyên.
7/2là3, không phải3.5. Verilog không có kiểu số thực dùng được ở đây (córeal, nhưng nó chỉ dùng khi mô phỏng). - Phép chia và chia lấy dư rất tốn phần cứng.
/4thì ổn (công cụ tổng hợp biến nó thành phép dịch phải), nhưng/nvớinthay đổi lúc chạy sẽ tạo ra một bộ chia nhiều chu kỳ mà bạn gần như không bao giờ muốn có. - Tràn số sẽ quay vòng. Cộng hai giá trị không dấu N bit mà kết quả vượt quá N bit thì kết quả bị cắt còn N bit. Nếu muốn giữ lại bit nhớ, hãy khai báo kết quả rộng hơn một bit:
wire [7:0] a, b;
wire [8:0] sum = a + b; // 9-bit sum captures carry out
So sánh: == != < > <= >=
Cả sáu toán tử so sánh đều trả về kết quả 1 bit: 1 nếu đúng, 0 nếu sai, x nếu một trong hai toán hạng có bit x hoặc z. Để xử lý trường hợp x/z, hãy dùng === và !== (các toán tử so sánh case), được giải thích trong bài Giá trị X và Z.
<= ở đây là toán tử so sánh (nhỏ hơn hoặc bằng). Cùng các ký tự đó cũng có nghĩa là phép gán non-blocking trong khối thủ tục (q <= d). Ngữ cảnh giúp phân biệt: bên trong một biểu thức thì đó là phép so sánh; ở vị trí bên trái của giá trị đang được gán thì đó là phép gán. Sự trùng lặp này dễ gây nhầm lẫn nhưng không mơ hồ với trình phân tích cú pháp.
Logic: && || !
Toán tử logic coi các toán hạng là boolean (mọi giá trị khác 0 là đúng, 0 là sai) và cho kết quả 1 bit:
Điểm khác biệt quan trọng cần ghi nhớ: && logic không phải là & bitwise.
4'b1100 && 4'b0011 // 1 (both are non-zero, logical AND is true)
4'b1100 & 4'b0011 // 4'b0000 (no bit positions are 1 in both)
Tương tự với || và |. Dùng toán tử logic trong điều kiện if và trong vế điều kiện của ?:; dùng bitwise khi bạn muốn từng vị trí bit được AND/OR độc lập.
&& và || có tính ngắn mạch theo kiểu C: vế bên phải không được tính nếu kết quả đã được quyết định bởi vế bên trái. Điều này quan trọng với testbench kiểm tra con trỏ hay gọi hàm; nó hiếm khi quan trọng với RTL tổng hợp được, nơi mọi thứ vốn diễn ra song song.
Toán tử điều kiện: ?:
Toán tử ba ngôi là người bạn tốt nhất của bạn khi xây mux và các biểu thức điều kiện:
Mẫu cond ? a : b trong phần cứng là một bộ chọn kênh 2 sang 1 với tín hiệu chọn 1 bit. Nối chuỗi chúng thì ổn với lựa chọn 3 hoặc 4 đường, nhưng nhiều hơn thế thì câu lệnh case (xem bài Câu lệnh case) dễ đọc hơn hẳn.
Toán tử ?: cũng là cách chuẩn để cho thanh ghi một giá trị mặc định mà thỉnh thoảng bạn mới ghi đè:
next_value = update ? new_data : next_value;
Quy tắc độ rộng: cái bẫy cho người mới
Quy tắc độ rộng của Verilog cho các toán tử số học nổi tiếng là gây bất ngờ. Tóm tắt:
- Độ rộng của kết quả là độ rộng lớn nhất trong các đầu vào.
- Nếu kết quả được gán cho thứ gì đó rộng hơn, nó được thêm số 0 ở đầu (hoặc mở rộng dấu nếu là số có dấu).
- Độ rộng của các phép tính trung gian cũng là độ rộng kết quả, nghĩa là tràn số xảy ra ngay ở bước trung gian.
Phép nhân đầu tiên bị tràn vì cả hai toán hạng đều rộng 8 bit, nên kết quả cũng rộng 8 bit, và 40000 không vừa. Phép thứ hai đúng vì chúng ta đã chủ động mở rộng a trước khi nhân.
Các kỹ thuật để sửa:
- Dùng toán tử ghép nối
{8'b0, a}để thêm số 0 ở đầu. - Dùng
$unsigned(a)hoặc$signed(a)để tác động đến cách hiểu giá trị. - Ép các biểu thức trung gian lên độ rộng lớn hơn khi không chắc chắn.
Độ ưu tiên toán tử
Bạn sẽ không học thuộc cả bảng, nhưng sẽ dựa vào vài quy tắc:
*,/,%ưu tiên cao hơn+,-.- So sánh ưu tiên cao hơn logic (
&&,||). - Toán tử điều kiện
?:có độ ưu tiên rất thấp: trong biểu thức lớn, thường phải đặt ngoặc quanh các nhánh của nó. - Khi không chắc, cứ thêm ngoặc. Trình mô phỏng không tính phí theo số ký tự.
out = (mode == 2'd0) ? a + b : a - b; // clearer with parens
Tiếp theo là gì
Bạn đã đi qua mọi toán tử trông giống anh em của chúng bên phần mềm. Bài tiếp theo, Bitwise và rút gọn, nói về các toán tử không có bản tương đương trực tiếp trong phần mềm: AND/OR/XOR bitwise trên từng bit, cùng các dạng rút gọn gộp cả một vector thành một bit duy nhất.
Câu hỏi thường gặp
Verilog hỗ trợ những toán tử nào?
Verilog có bốn nhóm toán tử lớn: số học (+, -, *, /, %), so sánh (==, !=, <, >, <=, >=), logic (&&, ||, !) và bitwise/rút gọn (&, |, ^, ~, ~&, ~|, ~^). Ngoài ra còn có các toán tử dịch bit (<<, >>, <<<, >>>), ghép nối {}, nhân bản {N{...}} và toán tử điều kiện ?:.
&& và & trong Verilog khác nhau thế nào?
&& là AND logic: nó coi mỗi toán hạng là một boolean (bằng 0 hoặc khác 0) và trả về kết quả 1 bit. & là AND bitwise: nó ghép cặp các bit theo từng vị trí. 4'b1100 && 4'b0011 là 1 (cả hai đều khác 0); 4'b1100 & 4'b0011 là 4'b0000. Dùng && trong điều kiện, dùng & cho các phép toán ở mức bit.
Phép chia trong Verilog hoạt động thế nào?
Đó là phép chia nguyên: phần thập phân bị bỏ đi. 7 / 2 là 3, không phải 3.5. Toán tử % cho phần dư: 7 % 2 là 1. Chia cho 0 cho ra x (không xác định) khi mô phỏng. Về mặt kỹ thuật cả hai toán tử đều tổng hợp được, nhưng chúng tạo ra phần cứng rất lớn và chậm trên FPGA và ASIC: hãy tránh dùng chúng trong các đường tổng hợp được, trừ khi số chia là lũy thừa của 2.
Toán tử điều kiện trong Verilog là gì?
?: là toán tử ba ngôi hay toán tử điều kiện, lấy từ C. cond ? a : b cho ra a nếu cond đúng (khác 0) và b nếu ngược lại. Đây là cấu trúc được dùng nhiều nhất cho logic dạng bộ chọn kênh (multiplexer): out = sel ? in1 : in0 tạo ra một mux 2 sang 1. Chuỗi nhiều ?: tạo ra mux rộng hơn, dù case thường rõ ràng hơn khi có từ ba đầu vào trở lên.