Menu

Hằng số trong Verilog: nhị phân, hex, thập phân và độ rộng

Cách Verilog viết hằng số: có độ rộng và không có độ rộng, các cơ số 'b 'h 'd 'o, số có dấu, dấu gạch dưới để dễ đọc, và những cái bẫy người mới hay gặp.

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.

Cấu tạo của một hằng có độ rộng

Một hằng số (literal) trong Verilog có tối đa ba phần:

8'b1010_1100
│  │ │
│  │ └─ digits in the chosen base
│  └─── base specifier: b (binary), h (hex), d (decimal), o (octal)
└────── width in bits (decimal)

Đọc từ trái sang phải: "một giá trị 8 bit, viết ở hệ nhị phân, với các chữ số 10101100". Đó là 172 ở hệ thập phân, 0xAC ở hệ hex, cùng một dãy bit dù bạn viết theo cách nào (một công cụ đổi cơ số hiển thị cả ba cạnh nhau). Độ rộng cho trình biên dịch biết chính xác cần cấp bao nhiêu bit; cơ số cho nó biết cách hiểu các chữ số.

Dấu gạch dưới là dấu phân cách để nhìn cho dễ. 32'b1010_1100_0011_0101 giống hệt 32'b1010110000110101. Hãy dùng chúng.

Bốn cơ số

Hex (h) là chuẩn cho mọi giá trị rộng hơn 4 bit. Nhị phân (b) là chuẩn khi bạn muốn đọc trực tiếp từng mẫu bit. Thập phân (d) dành cho các số đếm có nghĩa với con người. Bát phân (o) có tồn tại nhưng bạn gần như sẽ không bao giờ gặp.

Chữ số hex không phân biệt hoa thường: 8'hAB và 8'hab là như nhau.

Chuyện gì xảy ra khi giá trị không vừa?

Nếu các chữ số không lấp đầy độ rộng đã khai báo, Verilog thêm số 0 vào bên trái:

8'b101       // equivalent to 8'b00000101  (= 5)
8'hF         // equivalent to 8'h0F        (= 15)

Nếu các chữ số vượt quá độ rộng đã khai báo, Verilog cắt bỏ các bit cao kèm cảnh báo:

4'hFF        // truncates to 4'hF, simulator warns
4'b10000     // truncates to 4'b0000

Cảnh báo đó là bạn của bạn. Đừng tắt nó đi.

Có độ rộng và không có độ rộng

Một hằng không có độ rộng:

'd10    // unsized, takes default size (≥32 bits)
10      // also unsized - no base means decimal

Hằng không có độ rộng mặc định là 32 bit trong phần lớn công cụ. Đây là nguồn gốc của cả một nhóm bug:

reg [7:0] count;
if (count == -1) ...      // -1 is unsized 32-bit; comparison gets weird

Cách sửa là viết 8'hFF thay cho -1, hoặc 8'd255, hoặc {8{1'b1}}. Luôn ghi độ rộng cho hằng khi dùng trong các biểu thức phụ thuộc độ rộng.

Nơi duy nhất hằng không có độ rộng là ổn là các biến đếm kiểu integer trong vòng lặp for của testbench, nơi integer đủ rộng để không có gì bất ngờ xảy ra.

Hằng có dấu

Mặc định, hằng có độ rộng là không dấu:

8'd255   // unsigned 255
8'b1111_1111   // unsigned 255 - same bit pattern, just written differently

Nếu muốn hằng được hiểu là số có dấu, hãy thêm s sau dấu nháy đơn:

8'sd10           // signed 10
8'sb1111_1111    // signed -1 (two's complement)

Cờ s có ý nghĩa với:

  • Các toán tử <<< và >>> (dịch số học): chúng mở rộng dấu cho toán hạng có dấu.
  • Các toán tử so sánh trên biểu thức trộn lẫn có dấu.
  • Các phép ép kiểu $signed/$unsigned.

Với phần lớn phép tính số học thông thường trên tín hiệu bus, bạn sẽ không cần s.

Hằng số có X và Z

Một chữ số trong hằng có thể là x (không xác định) hoặc z (trở kháng cao):

8'bx là cách viết tắt của "cả 8 bit đều không xác định": giá trị này được lan ra cho đủ độ rộng đã khai báo. Chữ số x thường gặp trong nhánh default của máy trạng thái và khi khởi tạo. Chúng ta sẽ nói về ý nghĩa của chúng trong bài Giá trị X và Z.

Hằng có độ rộng trong thực tế

Khi bắt đầu viết module thật, bạn sẽ thấy hằng có độ rộng ở khắp nơi:

// Bus widths
input  wire [31:0] addr;
wire [31:0] zero = 32'h0;
wire [31:0] all_ones = 32'hFFFF_FFFF;

// State encodings
localparam IDLE = 3'd0;
localparam BUSY = 3'd1;
localparam DONE = 3'd2;

// Masks
wire is_msb_set = data & 32'h8000_0000;

// Comparisons
if (counter == 8'd255) ...

// Reset values
always @(posedge clk) begin
    if (reset) data_out <= 8'd0;
    else       data_out <= data_in;
end

Quy luật rất nhất quán: viết độ rộng, viết cơ số, viết chữ số. Mọi hằng bạn dùng đều có cùng một dạng.

Chạy thử

Giờ bạn đã có mọi thứ cần thiết để viết bất kỳ hằng số nào một module Verilog có thể cần. Mảnh ghép cuối cùng của phần kiểu dữ liệu là chuyện gì xảy ra khi một tín hiệu không phải 0 hay 1 rõ ràng: các giá trị x và z.

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

8'b1010 trong Verilog nghĩa là gì?

Đó là một hằng nhị phân có độ rộng: giá trị 8 bit có biểu diễn nhị phân là 00001010. Số đứng trước dấu nháy đơn là độ rộng tính bằng bit; chữ cái sau dấu nháy là cơ số (b nhị phân, h hex, d thập phân, o bát phân); các chữ số phía sau là giá trị. Nếu các chữ số không lấp đầy độ rộng, hằng sẽ được thêm số 0 ở bên trái.

Số có độ rộng và không có độ rộng trong Verilog khác nhau thế nào?

Một hằng có độ rộng như 8'd10 rộng đúng 8 bit. Một hằng không có độ rộng như 'd10 hoặc chỉ 10 mặc định là 32 bit, thường là quá nhiều. Trộn hằng không có độ rộng vào các biểu thức phụ thuộc độ rộng sẽ gây ra bug khó thấy: hãy ưu tiên hằng có độ rộng ở mọi nơi, trừ code kiểm thử viết tạm.

Viết số hex trong Verilog như thế nào?

Dùng ký hiệu cơ số 'h: 8'hFF là giá trị 8 bit bằng 255. Chữ số hex không phân biệt hoa thường; 8'hff và 8'hFF là như nhau. Bạn có thể nhóm chữ số bằng dấu gạch dưới cho dễ đọc: 32'hDEAD_BEEF. Trình phân tích cú pháp bỏ qua các dấu gạch dưới.

Dấu gạch dưới trong số Verilog hoạt động thế nào?

Dấu gạch dưới là dấu phân cách để nhìn cho dễ, trình phân tích cú pháp sẽ bỏ qua chúng. 32'b1010_1100_0011_0101 có giá trị y hệt 32'b1010110000110101 nhưng dễ đọc hơn nhiều. Ký tự đầu tiên sau tiền tố cơ số không được là dấu gạch dưới, nhưng bạn có thể đặt chúng ở bất kỳ đâu khác trong dãy chữ số.

Viết số có dấu trong Verilog như thế nào?

Thêm s sau dấu nháy đơn: 8'sd10 là số thập phân 10 có dấu 8 bit, còn 8'sb1111_1111 là -1 có dấu. Mặc định, hằng có độ rộng là không dấu; bổ ngữ s đảo lại điều đó. Phần lớn phép tính số học dùng toán hạng không dấu: chỉ dùng số có dấu khi bạn thật sự cần giá trị âm được truyền qua <, >>> và các toán tử tương tự.

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