Hằng số nhưng có điểm đặc biệt
parameter là một hằng số: trình biên dịch chọn giá trị của nó và gắn cứng vào thiết kế trước khi mô phỏng bắt đầu. Điểm đặc biệt là ai khởi tạo module cũng có thể thay đổi giá trị đó. Cùng một tệp nguồn module có thể tạo ra các mạch kích thước khác nhau tùy theo cách nơi gọi tham số hóa nó.
Đó là cách xây dựng IP tái sử dụng được. Một module FIFO có tham số WIDTH và DEPTH có thể phục vụ mọi nhóm trong một công ty. Một bộ đếm có tham số WIDTH vẫn là cùng một mã nguồn dù nó đếm đến 16 hay 4 tỷ.
Khai báo tham số cơ bản
Ba phần cú pháp mới:
- Khối tham số:
#( parameter WIDTH = 8 )đặt giữa tên module và danh sách port. Giá trị mặc định8là giá trị module dùng khi không ai ghi đè. - Nơi sử dụng:
output reg [WIDTH-1:0] count. Tham số chỉ là một hằng số: chúng ta đặt nó vào khoảng bit. Cùng một mã nguồn module tạo ra output 8 bit hoặc 16 bit tùy theoWIDTH. - Cú pháp ghi đè:
counter #(.WIDTH(16)) dut16(...)lúc khởi tạo. Khối#(...)đứng trước tên instance, sau tên module. Các tham số bạn không nhắc đến giữ giá trị mặc định.
localparam: hằng số nội bộ
Có những hằng số bạn không muốn nơi gọi ghi đè. Mã hóa trạng thái là ví dụ kinh điển:
localparam giúp IDLE, RUNNING, DONE dùng được bên trong module nhưng không thể ghi đè khi khởi tạo. Đó là lựa chọn đúng: thay đổi giá trị trạng thái nào mang nghĩa IDLE từ bên ngoài module sẽ là thảm họa.
Dùng parameter cho những thứ nơi gọi nên cấu hình (độ rộng, độ sâu, tùy chọn hành vi) và localparam cho mọi thứ còn lại. Một mẫu phổ biến là suy ra localparam từ parameter:
parameter WIDTH = 32;
localparam WIDTH_M1 = WIDTH - 1; // computed once; not overrideable
Độ rộng tham số hóa trong thực tế
Cách dùng tham số phổ biến nhất là làm cho độ rộng bus linh hoạt. Đây là một bộ cộng hoạt động ở mọi độ rộng:
Một tệp nguồn, hai instance, hai độ rộng khác nhau, không cần sao chép và dán. Đó là toàn bộ lợi ích của tham số.
Nhiều tham số, ghi đè theo tên
Với module lớn hơn, bạn thường có nhiều tham số. Ghi đè chúng theo tên, theo bất kỳ thứ tự nào:
module fifo #(
parameter WIDTH = 8,
parameter DEPTH = 16,
parameter AFULL = DEPTH - 2
)(
input wire clk,
input wire reset,
// ... ports ...
);
// ...
endmodule
// At the call site:
fifo #(.WIDTH(32), .DEPTH(1024)) cmd_queue (.clk(clk), .reset(reset), ...);
Bạn không cần ghi đè mọi tham số; những tham số không được nhắc đến giữ giá trị mặc định. Giá trị mặc định của AFULL trong ví dụ được tính từ DEPTH, nghĩa là nếu bạn ghi đè DEPTH thì AFULL tự động thay đổi theo. Đây chính là kiểu phụ thuộc mà localparam cũng xử lý được nếu bạn không muốn nơi gọi ghi đè AFULL một cách độc lập.
Các lỗi thường gặp
Quên dấu # khi ghi đè. counter (.WIDTH(16)) dut(...) trông giống ghi đè nhưng Verilog đọc (.WIDTH(16)) là một kết nối port. Bạn cần viết counter #(.WIDTH(16)) dut(...).
Dùng tham số ở chỗ giá trị không đủ cố định. Tham số được xác định ở giai đoạn elaboration, trước khi có bất kỳ tín hiệu nào. Bạn không thể tham số hóa theo một tín hiệu lúc chạy: nếu giá trị phụ thuộc vào trạng thái của một input khi mô phỏng, đó không phải tham số mà là logic.
Nhầm parameter và localparam trong danh sách port. Chỉ parameter được đặt trong khối #(...) ở đầu. localparam nằm trong phần thân. Trình biên dịch sẽ báo nếu bạn đặt nhầm.
Tiếp theo là gì
Giờ bạn đã có thể tạo module có kích thước được quyết định lúc biên dịch. Hai bài tiếp theo nói về quy tắc viết hằng số (8'h1F, 4'b1010, 32'd100) và các giá trị x và z xuất hiện khi tín hiệu không được điều khiển hoặc bị tranh chấp. Sau đó chúng ta chuyển sang toán tử: mọi thứ bạn có thể làm với các vector và tham số vừa học.
Câu hỏi thường gặp
Tham số (parameter) trong Verilog là gì?
parameter là một hằng số lúc biên dịch được khai báo bên trong module. Bạn dùng nó được ở bất kỳ đâu cho phép hằng số: độ rộng bus, độ sâu bộ nhớ, mã hóa trạng thái, giá trị mặc định. Điều quan trọng là mỗi instance của module có thể ghi đè tham số, nên cùng một tệp nguồn có thể tạo ra, chẳng hạn, một bộ đếm 8 bit ở chỗ này và một bộ đếm 32 bit ở chỗ khác.
parameter và localparam trong Verilog khác nhau thế nào?
Giá trị parameter có thể được ghi đè từ bên ngoài module khi khởi tạo. Giá trị localparam thì không: chúng chỉ là hằng số nội bộ. Dùng localparam cho mã hóa trạng thái và các hằng số suy ra mà người viết module không muốn nơi gọi can thiệp.
Làm sao để ghi đè một parameter trong Verilog?
Khi khởi tạo module, thêm một khối ghi đè tham số trước tên instance: counter #(.WIDTH(16)) my_inst (.clk(clk), .count(count));. Cú pháp .WIDTH(16) gán giá trị cho đúng tham số đó; các tham số bạn không liệt kê giữ giá trị mặc định. Nhiều phép ghi đè được ngăn cách bằng dấu phẩy bên trong #(...).
Module tham số hóa là gì?
Đó là module có một hoặc nhiều khai báo parameter mà nơi gọi có thể ghi đè. Một FIFO tham số hóa có thể có tham số WIDTH và DEPTH, để cùng một mã nguồn tạo ra FIFO rộng 32 bit, sâu 16 ở chỗ này và FIFO rộng 8 bit, sâu 1024 ở chỗ khác. Các thư viện khối IP tái sử dụng được viết theo cách này.