Một dây hay nhiều dây
Đến giờ mọi tín hiệu chúng ta thấy đều rộng 1 bit. Thiết kế thật gần như không bao giờ như vậy: địa chỉ là 16 hoặc 32 bit, bus dữ liệu là 8 hoặc 64 bit, điểm ảnh RGB là 24 bit. Verilog cho bạn một cơ chế duy nhất để biến bất kỳ tín hiệu nào thành nhiều bit: thêm một khoảng trong ngoặc vuông.
wire [7:0] data; // 8-bit wire, bit 7 is MSB, bit 0 is LSB
reg [15:0] address; // 16-bit reg
output reg [31:0] result; // 32-bit module output
Các số trong ngoặc vuông là vị trí bit của bit có trọng số cao nhất và thấp nhất. [7:0] nghĩa là "tín hiệu này có các bit đánh số từ 7 xuống 0", tổng cộng là 8 bit. Số thứ nhất là chỉ số cao. Số thứ hai là chỉ số thấp.
Thỉnh thoảng bạn sẽ thấy [0:7]: cùng số bit, nhưng thứ tự ngược lại khi cắt lát. Dạng [high:low] là quy ước áp đảo trong ngành; hãy dùng nó trừ khi bạn có lý do thật thuyết phục.
Ví dụ minh họa: bộ cộng 8 bit
Mỗi phép + cộng hai vector 8 bit và cho kết quả 9 bit. Các hằng số như 8'd10 có nghĩa là "giá trị thập phân 10 rộng 8 bit": chúng ta sẽ nói về chúng trong bài Hằng số dạng số.
Cắt lát: lấy ra từng bit
Khi đã có một vector, bạn có thể lấy ra từng bit hoặc một khoảng liên tiếp:
Đừng bận tâm đến dòng force trong testbench: chúng ta chỉ cần một cách đưa giá trị vào để minh họa việc cắt lát. Phần thú vị là chính các lát cắt.
Vài quy tắc:
- Chiều của lát cắt phải khớp với khai báo. Nếu bạn khai báo
[7:0], hãy cắt theo[high:low]. Đảo chiều là lỗi cú pháp. - Cắt ra ngoài khoảng sẽ cho
x(không xác định) khi mô phỏng. Công cụ tổng hợp có thể cảnh báo hoặc báo lỗi. - Việc chọn bit dựa trên chỉ số bạn đã viết:
data[0]là bit mang số0, mà (với khai báo[7:0]) chính là LSB.
Lát cắt với điểm bắt đầu thay đổi: +: và -:
Một nhu cầu phổ biến: "lấy 8 bit bắt đầu từ bit N". Bạn không thể viết trực tiếp data[N+7:N] vì Verilog yêu cầu cả hai đầu khoảng phải là hằng số. Cú pháp giải quyết việc này:
data[base +: width] // width bits starting at `base`, going UP
data[base -: width] // width bits starting at `base`, going DOWN
Độ rộng là hằng số (chúng ta lấy 8 bit mỗi lần), nhưng điểm bắt đầu có thể là một biểu thức lúc chạy. Đó chính là thứ bạn cần cho bộ nhớ đánh địa chỉ theo byte, các điểm lấy tín hiệu của thanh ghi dịch, v.v.
Mảng: một bước xa hơn vector
Vector là một tín hiệu nhiều bit. Mảng là tập hợp các vector, được đánh chỉ số độc lập:
reg [31:0] mem [0:1023];
Khai báo đó có hai khoảng, và hai khoảng này mang ý nghĩa khác nhau:
[31:0]là chiều packed: độ rộng của mỗi word.[0:1023]là chiều unpacked: có bao nhiêu word.
Vậy mem là 1024 thanh ghi 32 bit riêng biệt. Bạn truy cập một thanh ghi bằng một chỉ số:
mem[5] = 32'hCAFE_BABE; // write word 5
data = mem[address]; // read the word at `address`
Đó là một bộ nhớ nhỏ chứa các số chính phương. Thiết kế thật dùng cùng mẫu này để chứa tệp thanh ghi, bảng tra cứu, FIFO và mọi loại bộ nhớ trên chip lớn hơn một vector.
Packed và unpacked: vì sao quan trọng
Sự phân chia giữa chiều packed và unpacked xuất hiện ở khắp nơi. Biết rõ cái nào là cái nào giúp bạn đỡ mất rất nhiều thời gian gỡ lỗi:
- Vector packed là một tín hiệu. Bạn có thể coi toàn bộ nó là một số:
data + 1chạy được,data == 32'h0chạy được,data[7:0]chạy được. - Mảng unpacked là nhiều tín hiệu. Bạn không thể coi toàn bộ nó là một số:
mem + 1là lỗi cú pháp. Bạn phải chọn ra một word cụ thể trước.
Nhiều chiều packed cũng hợp lệ:
reg [3:0][7:0] regs; // 4 bytes packed together into a 32-bit signal
regs[0] là một byte (byte thấp). Toàn bộ regs là 32 bit. SystemVerilog dùng cách này rất nhiều.
Nhiều chiều unpacked tạo ra bộ nhớ 2 chiều:
reg [31:0] frame [0:479][0:639]; // 480x640 of 32-bit pixels
Bạn truy cập một điểm ảnh bằng frame[y][x]. Bộ đệm ảnh trong HDL sẽ trông như vậy.
Tiếp theo là gì
Giờ bạn đã có thể khai báo và thao tác với tín hiệu có độ rộng bất kỳ. Bài tiếp theo, Tham số, cho bạn thấy cách làm các độ rộng đó có thể cấu hình, để cùng một module chạy với 8 bit ở instance này và 32 bit ở instance khác. Sau đó chúng ta sẽ chuyển sang quy tắc viết hằng số (8'b1010_1100, 32'hDEAD_BEEF) và các giá trị x/z xuất hiện mỗi khi có thứ gì đó không được điều khiển.
Câu hỏi thường gặp
Vector trong Verilog là gì?
Vector là một tín hiệu nhiều bit. Bạn khai báo nó bằng cách thêm một khoảng vào wire hoặc reg: wire [7:0] data là một wire 8 bit. Các số trong ngoặc vuông là vị trí bit: ở đây bit 7 là bit có trọng số cao nhất và bit 0 là thấp nhất. Bạn có thể cắt lấy từng bit (data[3]) hoặc một khoảng liên tiếp (data[7:4]).
[7:0] trong Verilog nghĩa là gì?
[7:0] khai báo một khoảng từ bit 7 xuống bit 0, tính cả hai đầu: một tín hiệu 8 bit với bit 7 là bit có trọng số cao nhất. Số thứ nhất là chỉ số cao, số thứ hai là chỉ số thấp. Bạn cũng có thể viết [0:7] để đánh chỉ số kiểu little-endian, nhưng dạng [high:low] là quy ước phổ biến hơn hẳn trong code thực tế.
Cắt bit trong Verilog như thế nào?
Dùng chỉ số trong ngoặc vuông. data[3] lấy một bit. data[7:4] lấy bốn bit cao nhất thành một vector 4 bit. Lát cắt phải cùng chiều với khai báo: nếu bạn khai báo [7:0], hãy cắt theo [high:low]. SystemVerilog còn bổ sung data[3 +: 4] để cắt với điểm bắt đầu thay đổi và độ rộng cố định.
Mảng packed và unpacked trong Verilog khác nhau thế nào?
Mảng packed là một bus liền mạch duy nhất: reg [31:0] word là một tín hiệu 32 bit. Mảng unpacked (hay 'bộ nhớ') là tập hợp các word độc lập: reg [31:0] mem [0:1023] là 1024 thanh ghi 32 bit riêng biệt. Bạn có thể đọc hoặc ghi cả một word của mảng unpacked, nhưng không thể thao tác trên toàn bộ mảng như một tín hiệu.
Khai báo bộ nhớ trong Verilog như thế nào?
reg [31:0] mem [0:1023]; khai báo một bộ nhớ 1024 phần tử, mỗi phần tử rộng 32 bit. Cặp ngoặc vuông thứ nhất là độ rộng word (packed); cặp thứ hai là số lượng word (unpacked). Truy cập một phần tử bằng mem[address], và bạn có thể đọc hoặc ghi một lát cắt của phần tử đó bằng mem[address][7:0] khi đã bật cách đánh chỉ số của SystemVerilog-2005.