Menu

Toán tử bitwise và toán tử reduction trong Verilog

Các toán tử cấp bit trong Verilog: AND/OR/XOR theo từng bit, các dạng đảo, và toán tử reduction thu gọn cả một vector thành một bit duy nhất.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Hai vai trò cho cùng một ký hiệu

&, |, ^ và các dạng đảo của chúng xuất hiện dưới hai dạng khác nhau:

  • Dạng hai ngôi (hai toán hạng): a & b, phép toán theo từng bit giữa hai vector cùng độ rộng.
  • Dạng một ngôi (một toán hạng): &a, thu gọn (reduction) tất cả các bit của a thành một bit duy nhất.

Trình biên dịch phân biệt chúng bằng cách đếm số toán hạng. Theo cách gọi thông thường, dạng hai ngôi là "toán tử bitwise" và dạng một ngôi là "toán tử reduction".

Bitwise: từng bit một

Bộ đầy đủ:

Toán tửTênBit đầu ra ở vị trí N
a & bANDa[N] AND b[N]
a | bORa[N] OR b[N]
a ^ bXORa[N] XOR b[N]
a ~& bNANDNOT (a[N] AND b[N])
a ~| bNORNOT (a[N] OR b[N])
a ~^ bXNORNOT (a[N] XOR b[N])
~aNOTNOT a[N]

Lưu ý ~&, ~|, ~^ được viết với dấu ngã trước và toán tử sau. Mỗi cái là một token duy nhất, không có khoảng trắng ở giữa.

Khi hai toán hạng có độ rộng khác nhau, toán hạng hẹp hơn được mở rộng bằng số 0 ở bên trái cho khớp. Nếu muốn mở rộng theo bit dấu, hãy dùng toán hạng có dấu một cách tường minh với $signed().

Reduction: từ nhiều bit thành một

Dạng một ngôi của cùng các toán tử đó thu gọn một vector thành một bit duy nhất:

Ý nghĩa của từng toán tử:

  • &data trả về 1 nếu tất cả các bit của data đều là 1, ngược lại trả về 0. Hữu ích để kiểm tra "giá trị này có toàn bit 1 không?".
  • |data trả về 1 nếu có bit nào đó của data là 1, ngược lại trả về 0. Hữu ích để kiểm tra "giá trị này có khác 0 không?".
  • ^data trả về parity, tức XOR của tất cả các bit. Là 1 nếu số bit 1 là lẻ, 0 nếu chẵn.
  • ~&data, ~|data, ~^data là dạng đảo của các toán tử trên.

Bạn sẽ gặp chúng ở khắp nơi trong code thực tế:

wire empty       = ~|fifo_count;       // empty iff count is zero
wire all_ones    = &mask;              // all bits set
wire parity_bit  = ^data;              // parity of a byte
wire any_request = |request_vector;    // is anything requested?

Dạng reduction ngắn gọn và được tổng hợp thành các cây cổng logic dễ thấy: & thành một cổng AND nhiều đầu vào, | thành một cổng OR nhiều đầu vào, ^ thành một cây XOR (cũng chính là bộ tạo parity trong phần cứng).

Các mẫu thường gặp

Bật và xóa bit

wire [7:0] data;
wire [7:0] mask = 8'b0000_1000;

wire [7:0] set     = data | mask;     // forces bit 3 to 1
wire [7:0] cleared = data & ~mask;    // forces bit 3 to 0
wire [7:0] toggled = data ^ mask;     // flips bit 3
wire [7:0] tested  = data & mask;     // zero if bit 3 was 0, non-zero otherwise

Đây là những cách thao tác bit giống như bạn dùng trong C. Chúng được tổng hợp thành các phép toán chỉ cần một cổng.

Kiểm tra một giá trị có toàn bit 1 không

wire is_max = &counter;       // 1 if every bit of counter is 1

Một phép reduction AND với một cổng, so với cách viết counter == 8'hFF (cách này cũng dùng được; công cụ tổng hợp thường tạo ra phần cứng giống hệt nhau).

Tạo bit parity

Phát hiện có tín hiệu nào đang hoạt động

wire any_pending = |request_vector;

Nếu request_vector rộng (giả sử 64 nguồn yêu cầu), phép reduction OR thu gọn nó thành một tín hiệu duy nhất mà bạn có thể đưa vào bộ mã hóa ưu tiên hoặc bộ phân xử.

Toán tử dịch bit

Nhân tiện nói về toán tử cấp bit, đây là các phép dịch:

  • a << N dịch a sang trái N vị trí bit, điền số 0 vào bên phải.
  • a >> N dịch a sang phải N vị trí bit, điền số 0 vào bên trái.
  • a <<< N dịch trái số học (giống << với số không dấu).
  • a >>> N dịch phải số học: điền bằng bit dấu khi a là số có dấu.

Dịch một lượng hằng số không tốn gì trong phần cứng (chỉ là đi dây lại). Dịch theo một biến thay đổi lúc chạy sẽ tạo ra một barrel shifter, lớn hơn nhưng vẫn rẻ.

Tiếp theo là gì

Giờ bạn đã gặp mọi toán tử trả về một giá trị đơn. Tài liệu tiếp theo, Nối và nhân bản, nói về cú pháp {} và {N{...}} để ghép các mảnh thành vector rộng hơn, thứ bạn sẽ dùng liên tục khi kết nối các module có độ rộng khác nhau.

Câu hỏi thường gặp

Toán tử bitwise trong Verilog là gì?

Toán tử bitwise kết hợp hai vector cùng độ rộng theo từng vị trí. a & b AND bit 0 của a với bit 0 của b, bit 1 với bit 1, cứ thế tiếp tục, cho ra một vector có cùng độ rộng với đầu vào. Bộ đầy đủ gồm & (AND), | (OR), ^ (XOR), ~ (NOT), và các dạng đảo ~&, ~|, ~^ (NAND, NOR, XNOR).

Toán tử reduction trong Verilog là gì?

Toán tử reduction là dạng một ngôi của toán tử bitwise, thu gọn cả một vector thành một bit duy nhất. &data trả về 1 chỉ khi mọi bit của data đều là 1. |data trả về 1 nếu có bit nào đó là 1. ^data trả về XOR của tất cả các bit, tức là bit chẵn lẻ (parity). Dạng reduction không có toán hạng bên trái, chỉ có bên phải.

& và && trong Verilog khác nhau thế nào?

& là AND theo bit: nó ghép các bit theo từng vị trí và kết quả có cùng độ rộng với toán hạng. && là AND logic: nó coi mỗi toán hạng là một giá trị boolean (bằng 0 hay khác 0) và trả về kết quả 1 bit. 4'b1100 & 4'b0011 là 4'b0000; 4'b1100 && 4'b0011 là 1.

Làm sao tính parity trong Verilog?

Dùng toán tử reduction XOR: parity = ^data. Nó XOR tất cả các bit của data với nhau. Với vector 8 bit, đó là data[7] ^ data[6] ^ ... ^ data[0]. Kết quả là 1 nếu số bit bằng 1 là lẻ, 0 nếu chẵn. Đảo lại bằng ~^ cho ra parity chẵn.

~ trong Verilog làm gì?

~ là NOT theo bit: nó đảo mọi bit của toán hạng. ~4'b1100 là 4'b0011. Đừng nhầm với ! (NOT logic), toán tử thu gọn toán hạng thành một boolean 1 bit rồi đảo giá trị đó. !4'b1100 là 1'b0 (toán hạng khác 0, đảo giá trị đúng thành 0).

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu