Giao diện của một module
Danh sách port của module là giao diện của nó: tất cả những gì thế giới bên ngoài nhìn thấy và chạm vào được. Bên trong module, phần thân tính output từ input. Các port là những sợi dây đi qua ranh giới đó.
Kiểu khai báo ANSI hiện đại đặt hướng, kiểu và độ rộng ngay trên cùng một dòng:
module my_module(
input wire clk,
input wire reset,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid
);
// body
endmodule
Toàn bộ cấu trúc chỉ có vậy. Mỗi port có:
- Một hướng:
input,outputhoặcinout. - Một kiểu:
wirehoặcreg(hoặclogictrong SystemVerilog). - Một độ rộng: một khoảng như
[7:0], hoặc 1 bit nếu bỏ trống. - Một tên: định danh bạn sẽ dùng bên trong phần thân.
Ba hướng port
input: điều khiển từ bên ngoài
Input luôn là wire. Nguồn điều khiển nằm ngoài module: hoặc là tín hiệu của một module cấp cao hơn, hoặc là reg của testbench. Bên trong module này, bạn có thể đọc input trong biểu thức nhưng không bao giờ được gán giá trị cho chúng:
module reader(input wire [7:0] data);
initial $display("data = %h", data);
endmodule
Viết data = ... bên trong module sẽ gây lỗi.
output: điều khiển từ bên trong
Output được điều khiển bởi thứ gì đó bên trong module này. Nó có thể là wire (điều khiển bởi assign hoặc output của module con) hoặc reg (điều khiển từ always/initial).
module driver(
input wire a,
input wire b,
input wire clk,
output wire y, // wire - driven by assign
output reg q // reg - driven by always
);
assign y = a & b; // OK because y is wire
always @(posedge clk)
q <= a; // OK because q is reg
endmodule
Nếu cố gán cho y bên trong khối always, hoặc điều khiển q bằng assign, bạn sẽ gặp lỗi biên dịch. Từ khóa bạn chọn phải khớp với nguồn điều khiển.
inout: hai chiều
Port inout là dây có thể được điều khiển luân phiên bởi module hoặc bởi thứ được nối ở bên ngoài. Chúng xuất hiện ở ranh giới của chip: đường SDA của I²C, chân GPIO hai chiều, bus dữ liệu dùng chung. Bên trong module, bạn điều khiển hướng bằng mẫu ba trạng thái:
module bidir_pin(
input wire data_out,
input wire output_enable,
output wire data_in,
inout wire pin
);
assign pin = output_enable ? data_out : 1'bz;
assign data_in = pin;
endmodule
pin là dây dùng chung. Khi output_enable ở mức cao, module đưa data_out ra pin. Khi ở mức thấp, module nhả chân về trạng thái trở kháng cao (z), để một nguồn điều khiển bên ngoài dùng nó. data_in luôn đọc giá trị đang có trên chân.
inout hiếm gặp trong logic thuần nội bộ. Nếu bạn đang xây một bộ điều khiển bộ nhớ, một lõi CPU hay một pipeline xử lý ảnh, có thể bạn sẽ không bao giờ cần đến nó. Đây là tính năng dành cho vòng I/O ở ranh giới chip.
Port 1 bit và port nhiều bit
Khoảng độ rộng là tùy chọn: bỏ nó đi để có port 1 bit:
input wire valid, // 1 bit
input wire [7:0] data, // 8 bits
input wire [31:0] addr, // 32 bits
Bạn có thể dùng tham số cho độ rộng, đó chính là cách các module tham số hóa hoạt động:
module bus #(
parameter WIDTH = 32
)(
input wire [WIDTH-1:0] in,
output wire [WIDTH-1:0] out
);
assign out = in;
endmodule
Kiểu ANSI và kiểu Verilog-1995
Thỉnh thoảng bạn sẽ gặp code cũ tách riêng danh sách port và phần khai báo:
// Old Verilog-1995 style - DON'T do this in new code
module foo(clk, data_in, data_out);
input clk;
input [7:0] data_in;
output reg [7:0] data_out;
// ...
endmodule
Danh sách port chỉ chứa tên; mỗi port sau đó được khai báo riêng trong phần thân. Cách này dài dòng, dễ gặp lỗi "có tên trong danh sách port nhưng chưa khai báo" và phải gõ gấp đôi. Kiểu ANSI-2001 (kiểu được dùng xuyên suốt bộ tài liệu này) thay thế cả hai:
module foo(
input wire clk,
input wire [7:0] data_in,
output reg [7:0] data_out
);
// ...
endmodule
Hãy dùng kiểu ANSI. Các công cụ đã hỗ trợ nó từ năm 2001.
Ví dụ hoàn chỉnh
Chỉ một module đó đã có:
- Một tham số (
WIDTH). - Các input cho xung clock, reset, cho phép nạp, dữ liệu và cho phép dịch.
- Một output
reg(data_out) được điều khiển bên trong khốialways. - Một output
wire(msb_out) được điều khiển bởi phép gán liên tục. - Một khai báo kiểu ANSI đầy đủ liệt kê hướng, kiểu và độ rộng của từng port ngay trên dòng.
Gần như mọi module tổng hợp được mà bạn viết đều có dạng như vậy.
Tiếp theo là gì
Bài tiếp theo, Khởi tạo module, cho bạn thấy cách lấy module này và dùng nó bên trong một thiết kế lớn hơn. Bộ dịch bit bạn vừa viết tự nó chưa làm được gì nhiều; nó chỉ phát huy tác dụng khi có thứ gì đó khởi tạo nó và nối nó vào một hệ thống.
Câu hỏi thường gặp
Ba hướng port trong Verilog là gì?
input, output và inout. input được điều khiển từ bên ngoài module. output được điều khiển từ bên trong module. inout là hai chiều, hữu ích cho bus ba trạng thái (tri-state), nơi module vừa điều khiển vừa đọc cùng một chân. Phần lớn port bên trong thiết kế là input hoặc output; inout chỉ xuất hiện ở các chân của chip.
output wire và output reg khác nhau thế nào?
output wire nghĩa là output được điều khiển bởi một phép gán liên tục hoặc bởi output của một module con, tức mọi thứ nằm ngoài khối always. output reg nghĩa là nó được điều khiển từ một khối thủ tục (initial hoặc always). Từ khóa này quyết định bạn có thể gán cho tín hiệu từ bên trong always hay không, chứ không quyết định phần cứng sau tổng hợp có chứa flip-flop hay không.
Kiểu ANSI cho port Verilog là gì?
Kiểu ANSI khai báo hướng và kiểu của từng port ngay trong danh sách port: module foo(input wire [7:0] data, output reg [7:0] result);. Kiểu Verilog-1995 cũ hơn chỉ liệt kê tên trong danh sách port rồi khai báo lại bên trong module. Luôn dùng kiểu ANSI cho code mới: ngắn hơn, ít lỗi hơn và là chuẩn ở mọi công cụ hiện đại.
Một module Verilog có thể có nhiều input và output không?
Có. Một module có thể có bao nhiêu port tùy nhu cầu, với bất kỳ tổ hợp hướng nào. Ngăn cách chúng bằng dấu phẩy trong danh sách port. Thứ tự trong danh sách port quyết định thứ tự kết nối theo vị trí khi khởi tạo, nhưng gần như lúc nào bạn cũng dùng kết nối theo tên (.port(signal)) để không phụ thuộc vào thứ tự.