Menu

Ghép nối và nhân bản trong Verilog: toán tử {}

Cách ghép các tín hiệu lại với nhau bằng {} và lặp một mẫu bit N lần bằng {N{...}}, hai toán tử Verilog không thể thiếu khi dựng bus rộng hơn từ nhiều mảnh.

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 toán tử nhỏ nhưng đáng giá

Verilog có hai dạng {}:

  1. Ghép nối (concatenation): {a, b, c} gộp nhiều tín hiệu thành một tín hiệu rộng hơn.
  2. Nhân bản (replication): {N{pattern}} sao chép một mẫu N lần.

Bạn sẽ dùng cả hai liên tục. Mỗi khi một bus cần được tách, gộp, đệm thêm bit hoặc mở rộng dấu, một trong hai toán tử này chính là câu trả lời.

Ghép nối: {a, b, c}

Trường hợp đơn giản nhất: xếp hai tín hiệu thành một:

Đọc từ trái sang phải: a là nửa cao, b là nửa thấp. Toán hạng đầu tiên trong {} là phần có trọng số lớn nhất. Độ rộng kết quả bằng tổng độ rộng các toán hạng.

Bạn có thể ghép bao nhiêu toán hạng cũng được:

wire [31:0] word = {byte3, byte2, byte1, byte0};

Và các toán hạng có thể có độ rộng khác nhau:

wire [11:0] frame = {start_bit, data_byte, parity, stop_bit};
//                    1 bit      8 bits     1 bit   2 bits  = 12

Một quy tắc chung: mọi toán hạng của phép ghép nối phải có độ rộng tường minh. Hằng số không ghi độ rộng (1 thay vì 1'b1) gây lỗi vì trình phân tích không biết phải cấp bao nhiêu bit. Luôn viết 1'b0, 1'b1, 4'd0, đừng bao giờ viết 0 hay 1 trơn trong {}.

Ghép nối ở vế trái

Ghép nối ở vế trái của phép gán tách một tín hiệu rộng trở lại thành các phần:

Đây là mẫu bộ cộng có bit nhớ kinh điển. {carry_out, sum_bits} là một đích 9 bit duy nhất ở vế trái phép gán, và các bit được chia ra: bit cao nhất vào carry_out, 8 bit thấp vào sum_bits.

Ghép nối ở vế trái dùng được trong assign lẫn trong các khối thủ tục:

always @(posedge clk) begin
    {high_byte, low_byte} <= incoming_word;
end

Nhân bản: {N{pattern}}

Toán tử nhân bản sao chép một mẫu với số lần cố định. Dạng của nó là {N{pattern}}: một số đếm, rồi đến mẫu nằm trong cặp ngoặc nhọn riêng:

N phải là hằng số, vì trình biên dịch cần biết độ rộng kết quả ở bước elaboration. Mẫu có thể là hằng số, một parameter hoặc bất kỳ biểu thức nào đã biết độ rộng.

Đừng quên cặp ngoặc bên trong. {8 1'b1} là lỗi cú pháp; {8{1'b1}} mới đúng. Cặp {} bên ngoài là toán tử; cặp {...} bên trong bao lấy mẫu được nhân bản.

Kết hợp cả hai: mở rộng dấu và mở rộng bằng số 0

Hai toán tử này kết hợp rất tự nhiên để tạo giá trị rộng hơn:

{ {8{a_negative[7]}}, a_negative } là mẫu mở rộng dấu chuẩn. Hãy đọc nó là: lặp bit dấu tám lần, rồi nối thêm 8 bit gốc. Kết quả là biểu diễn bù hai 16 bit của cùng một số.

Với việc mở rộng số không dấu, bạn có thể dựa vào phép gán: Verilog tự động mở rộng bằng số 0 khi đích rộng hơn:

wire [15:0] zext_auto = a_unsigned;   // works, top 8 bits are 0

Nhưng mở rộng dấu không bao giờ diễn ra ngầm, trừ khi cả toán hạng lẫn đích đều được khai báo signed. Cách nhân bản tường minh an toàn hơn.

Các mẫu hữu ích

Tạo mặt nạ gồm N bit 1

parameter N = 5;
wire [31:0] mask = { {32-N{1'b0}}, {N{1'b1}} };
// e.g. with N=5, mask = 32'h0000_001F

Đảo ngược một vector

Verilog không có sẵn hàm đảo ngược, nhưng bạn có thể viết trực tiếp bằng ghép nối:

wire [3:0] forward  = 4'b1010;
wire [3:0] reversed = {forward[0], forward[1], forward[2], forward[3]};
// reversed = 4'b0101

Với vector rộng hơn, một vòng lặp generate hoặc một function sẽ gọn hơn.

Gói các cờ vào một byte trạng thái

wire [7:0] status = {
    error_flag,    // [7]
    overflow,      // [6]
    underflow,     // [5]
    ready,         // [4]
    busy,          // [3]
    1'b0,          // [2] reserved
    1'b0,          // [1] reserved
    valid          // [0]
};

Các ô dự trữ 1 bit đều có độ rộng tường minh: đừng bao giờ viết 0 trơn trong {}.

Lỗi thường gặp

Hằng số không ghi độ rộng trong {}. {a, 0} là lỗi cú pháp hoặc là một số 0 rộng 32 bit. Luôn ghi độ rộng: {a, 1'b0}.

Quên cặp ngoặc bên trong khi nhân bản. {8 1'b1} không phân tích được; {8{1'b1}} thì được.

Nhầm thứ tự. {a, b} đặt a ở phía cao và b ở phía thấp. Hiểu ngược lại là bạn sẽ có thứ tự byte bị đảo ở đâu đó.

Nhân bản không lần. {0{...}} không hợp lệ trong Verilog chuẩn. SystemVerilog cho phép (và tạo ra kết quả rộng 0 bit). Verilog thuần sẽ từ chối.

Tiếp theo

Giờ bạn đã gặp mọi toán tử của Verilog. Chương tiếp theo chuyển hướng sang cấu trúc: cách các module kết nối với nhau, quy tắc về cổng và cú pháp khởi tạo module con để xây dựng thiết kế lớn hơn.

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

Ghép nối tín hiệu trong Verilog như thế nào?

Dùng dấu ngoặc nhọn: {a, b, c} tạo ra một vector rộng duy nhất bằng cách ghép các toán hạng theo thứ tự từ trái sang phải. MSB của a thành MSB của kết quả; LSB của c thành LSB. Độ rộng kết quả bằng tổng độ rộng các toán hạng. Phép ghép nối dùng được ở cả vế phải của phép gán (để tạo giá trị) lẫn vế trái (để tách giá trị).

{N{pattern}} trong Verilog nghĩa là gì?

Đó là toán tử nhân bản: nó tạo ra N bản sao của pattern ghép liền nhau. {8{1'b1}} là giá trị 8 bit toàn số 1 (8'hFF). {4{2'b10}} là giá trị 8 bit 8'b10101010. Nhân bản là cách để mở rộng bằng số 0, mở rộng dấu hoặc tạo bất kỳ mẫu bit lặp lại nào mà không phải viết tay.

Làm sao mở rộng dấu một tín hiệu trong Verilog?

Dùng nhân bản để lặp bit dấu: { {24{a[7]}}, a } mở rộng a 8 bit thành 32 bit bằng cách lặp bit 7 (bit dấu) 24 lần rồi nối thêm giá trị gốc. Với số không dấu, hãy nhân bản 1'b0, hoặc cứ để phép gán tự mở rộng khi đích rộng hơn.

Có thể dùng ghép nối ở vế trái của phép gán không?

Có, đó là cách gán cho nhiều đích từ một nguồn rộng. {carry, sum} = a + b; đưa kết quả phép cộng vào carry (bit cao) và sum (phần còn lại) chỉ trong một câu lệnh. Mỗi đích giữ nguyên độ rộng của nó; trình phân tích chia các bit của vế phải tương ứng.

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