1. Verilog function là gì? (Khái niệm cơ bản và vai trò)
Verilog HDL (Hardware Description Language) là ngôn ngữ mô tả phần cứng được sử dụng để thiết kế và mô phỏng mạch số. Trong đó, function (hàm) là một cơ chế giúp đóng gói các xử lý cụ thể để dễ dàng tái sử dụng. Hiểu rõ Verilog function không chỉ giúp cải thiện khả năng đọc và bảo trì mã, mà còn góp phần nâng cao hiệu quả trong thiết kế mạch. Bài viết này sẽ giải thích khái niệm cơ bản về Verilog function và cách sử dụng chúng trong thực tế.
Function là gì?
Verilog function là một khối thực hiện tính toán hoặc xử lý cụ thể và trả về một giá trị duy nhất. Việc sử dụng function giúp giảm bớt sự lặp lại mã và làm cho thiết kế mạch đơn giản hơn.
Đặc điểm của function
Có thể có một hoặc nhiều đầu vào (chỉ sử dụng input)
Chỉ có một đầu ra (giá trị trả về)
Không thể chứa độ trễ thời gian (ví dụ #10)
Bên trong function luôn mô tả logic tổ hợp (combinational logic)
Function được định nghĩa bên ngoài khối always và được đánh giá ngay lập tức, khác với task
Khi nào sử dụng Verilog function
Verilog function thường được dùng trong các trường hợp sau:
1. Mô tả mạch tổ hợp
Vì function trả về kết quả ngay lập tức theo đầu vào, nó thường được sử dụng trong các mạch tổ hợp (Combinational Logic).
Ví dụ: cộng, trừ, encoder, decoder.
2. Tăng khả năng tái sử dụng mã
Loại bỏ mã lặp lại, gom các xử lý thường dùng vào một hàm duy nhất.
Ví dụ: công thức tính toán phức tạp với nhiều điều kiện.
3. Giảm lỗi thiết kế
Việc gom xử lý vào một nơi duy nhất giúp giảm sai sót khi chỉnh sửa.
Ví dụ: tính CRC hoặc kiểm tra parity.
Sự khác nhau giữa function và task
Ngoài function, Verilog còn có task. Hai khái niệm này tương tự nhưng có những khác biệt:
Mục
function
task
Đầu ra
Chỉ 1
Nhiều
Đầu vào
Có
Có
Biến nội bộ
Có
Có
Độ trễ (#10)
Không
Có
Sử dụng trong always
Có
Không
Cách gọi
tên_function(tham_số)
tên_task(tham_số);
Khi nên dùng function
Cần kết quả tính toán ngay
Xử lý logic không có độ trễ
Xử lý đơn giản trả về một giá trị duy nhất
Khi nên dùng task
Có độ trễ thời gian (ví dụ #10)
Cần nhiều đầu ra
Xử lý debug trong mô phỏng (ví dụ ghi log)
Tóm tắt
Verilog function là hàm nhận đầu vào và trả về một giá trị duy nhất.
Phù hợp cho mô tả mạch tổ hợp, không hỗ trợ độ trễ.
Giúp giảm mã lặp lại, cải thiện khả năng đọc.
Khác với task, cần chọn đúng tùy trường hợp.
2. Cách viết Verilog function 【Ví dụ đơn giản cho người mới bắt đầu】
Trong phần trước, chúng ta đã tìm hiểu khái niệm cơ bản của Verilog function. Ở đây, chúng ta sẽ đi sâu vào cách viết function trong Verilog.
Cú pháp cơ bản của function
Verilog function được viết theo cấu trúc sau:
function [độ_rộng_đầu_ra] tên_function;
input [độ_rộng_đầu_vào] tên_input1, tên_input2, ...;
begin
tên_function = biểu_thức_tính_toán;
end
endfunction
Điểm cần chú ý
Khai báo bằng từ khóa function
Biến có cùng tên với function chính là giá trị trả về
Khai báo đầu vào với input (không dùng output hay inout)
Xử lý logic được viết trong begin ... end
Function được định nghĩa ngoài khối always
Ví dụ đơn giản về Verilog function
Ví dụ dưới đây minh họa một function cộng 8-bit:
module example;
function [7:0] add_function;
input [7:0] a, b;
begin
add_function = a + b;
end
endfunction
reg [7:0] x, y, sum;
initial begin
x = 8'b00001100; // 12
y = 8'b00000101; // 5
sum = add_function(x, y);
$display("Sum: %d", sum); // Sum: 17
end
endmodule
Giải thích
add_function nhận 2 đầu vào 8-bit a, b và trả về tổng
Dùng sum = add_function(x, y); để gọi hàm và gán kết quả
Bên trong khối initial sử dụng $display để in kết quả
Khai báo đầu vào và đầu ra trong function
Khai báo đầu vào
Function chỉ nhận input làm tham số.
function [7:0] my_function;
input [7:0] in1, in2;
begin
my_function = in1 & in2; // Phép AND
end
endfunction
Lưu ý: Không thể dùng output. Đầu ra chính là biến có cùng tên với function.
Function có điều kiện rẽ nhánh
Có thể sử dụng if hoặc case trong function.
function [3:0] max_function;
input [3:0] a, b;
begin
if (a > b)
max_function = a;
else
max_function = b;
end
endfunction
Hàm này trả về giá trị lớn hơn giữa a và b.
Tóm tắt
Định nghĩa bằng từ khóa function và trả về một giá trị duy nhất
Chỉ có thể dùng input (không dùng output)
Kết quả trả về bằng cách gán cho tên_function
Có thể sử dụng điều kiện if hoặc case
3. Cách sử dụng Verilog function 【Kèm ví dụ thực tế】
Trong phần trước, chúng ta đã học cú pháp và cách viết cơ bản của Verilog function.
Ở phần này, chúng ta sẽ tìm hiểu cách áp dụng function trong thiết kế thực tế thông qua các ví dụ cụ thể.
Cách gọi function
Verilog function được gọi giống như biến thông thường, theo cú pháp tên_function(tham_số1, tham_số2, ...).
Ví dụ dưới đây định nghĩa một hàm XOR 8-bit và sử dụng trong module:
module function_example;
function [7:0] xor_function;
input [7:0] a, b;
begin
xor_function = a ^ b;
end
endfunction
reg [7:0] x, y, result;
initial begin
x = 8'b11001100;
y = 8'b10101010;
result = xor_function(x, y); // gọi function
$display("XOR Result: %b", result); // XOR Result: 01100110
end
endmodule
Điểm cần nhớ
Gọi function bằng cú pháp biến = function(tham_số);
Có thể dùng trong khối always hoặc initial
Hoạt động như mạch tổ hợp (combinational logic)
Sử dụng function trong mạch tổ hợp
Vì function luôn được đánh giá ngay lập tức, nên rất hữu ích khi xây dựng mạch tổ hợp.
Ví dụ dưới đây triển khai bộ giải mã 2-to-4 bằng function:
module decoder_example;
function [3:0] decoder;
input [1:0] sel;
begin
case (sel)
2'b00: decoder = 4'b0001;
2'b01: decoder = 4'b0010;
2'b10: decoder = 4'b0100;
2'b11: decoder = 4'b1000;
default: decoder = 4'b0000;
endcase
end
endfunction
reg [1:0] select;
wire [3:0] decoded_output;
assign decoded_output = decoder(select); // sử dụng function
initial begin
select = 2'b01;
#10; // thêm độ trễ để quan sát trong mô phỏng
$display("Decoded Output: %b", decoded_output); // Decoded Output: 0010
end
endmodule
Giải thích
decoder nhận đầu vào 2-bit và chuyển thành đầu ra 4-bit
Dùng case để quyết định đầu ra theo giá trị đầu vào
assign dùng để gán đầu ra của function cho decoded_output → Function có thể được dùng như một phần của mạch tổ hợp
Sự khác nhau giữa always và function 【Bảng so sánh】
Cả Verilog function và always đều dùng để mô tả logic, nhưng chúng có mục đích và giới hạn khác nhau.
Mục
function
always
Vị trí viết
Bên ngoài khối always
Bên trong khối always
Đầu vào
Chỉ input
Có thể là reg hoặc wire
Đầu ra
1 giá trị duy nhất
Có thể cập nhật nhiều giá trị
Độ trễ (#10)
Không
Có
Lưu trạng thái
Không (đánh giá ngay lập tức)
Có (có thể dùng như flip-flop)
Ứng dụng chính
Mạch tổ hợp
Mạch tuần tự hoặc xử lý theo sự kiện
Nguyên tắc sử dụng
Dùng function cho các phép toán logic đơn giản (mạch tổ hợp)
Dùng always cho các mạch có trạng thái (flip-flop, bộ đếm)
Nếu cần độ trễ (#10), hãy dùng always thay vì function
Tóm tắt cách sử dụng Verilog function
✅ Gọi bằng cú pháp tên_function(tham_số) ✅ Thích hợp cho thiết kế mạch tổ hợp, khác với always ✅ Có thể dùng case và if để viết logic linh hoạt
✅ Ứng dụng trong decoder, tính toán số học, v.v.
4. Ứng dụng của Verilog function (Thiết kế Decoder và ALU)
Chúng ta đã tìm hiểu cú pháp và cách sử dụng cơ bản của Verilog function.
Trong phần này, chúng ta sẽ xem xét cách áp dụng function trong thiết kế mạch số thực tế như Decoder và ALU (Arithmetic Logic Unit).
Triển khai function cho Decoder (2-to-4 Decoder)
Decoder là mạch chuyển đổi số bit đầu vào nhỏ thành số bit đầu ra lớn hơn.
Ví dụ: 2-bit đầu vào → 4-bit đầu ra.
Ta có thể viết Decoder 2-to-4 bằng function như sau:
module decoder_example;
function [3:0] decoder;
input [1:0] sel;
begin
case (sel)
2'b00: decoder = 4'b0001;
2'b01: decoder = 4'b0010;
2'b10: decoder = 4'b0100;
2'b11: decoder = 4'b1000;
default: decoder = 4'b0000;
endcase
end
endfunction
reg [1:0] select;
wire [3:0] decoded_output;
assign decoded_output = decoder(select); // sử dụng function
initial begin
select = 2'b00; #10;
$display("Decoded Output: %b", decoded_output);
select = 2'b01; #10;
$display("Decoded Output: %b", decoded_output);
select = 2'b10; #10;
$display("Decoded Output: %b", decoded_output);
select = 2'b11; #10;
$display("Decoded Output: %b", decoded_output);
end
endmodule
Triển khai function cho ALU (Cộng, Trừ, AND, OR)
ALU (Arithmetic Logic Unit) là mạch trung tâm của CPU, thực hiện cộng, trừ và các phép toán logic (AND, OR).
Dưới đây là ví dụ thiết kế một ALU 8-bit đơn giản bằng function:
module alu_example;
function [7:0] alu;
input [7:0] a, b;
input [1:0] op; // tín hiệu điều khiển 2-bit
begin
case (op)
2'b00: alu = a + b; // Cộng
2'b01: alu = a - b; // Trừ
2'b10: alu = a & b; // AND
2'b11: alu = a | b; // OR
default: alu = 8'b00000000;
endcase
end
endfunction
reg [7:0] x, y;
reg [1:0] opcode;
wire [7:0] result;
assign result = alu(x, y, opcode); // gọi function
initial begin
x = 8'b00001100; // 12
y = 8'b00000101; // 5
opcode = 2'b00; #10;
$display("Addition Result: %d", result); // 12 + 5 = 17
opcode = 2'b01; #10;
$display("Subtraction Result: %d", result); // 12 - 5 = 7
opcode = 2'b10; #10;
$display("AND Result: %b", result); // AND
opcode = 2'b11; #10;
$display("OR Result: %b", result); // OR
end
endmodule
Tóm tắt
✅ Function có thể áp dụng cho mạch tổ hợp như Decoder và ALU ✅ Dùng case để mô tả các phép toán linh hoạt ✅ Cải thiện khả năng đọc và dễ tái sử dụng code ✅ Function phù hợp cho mạch tổ hợp, không phù hợp cho mạch tuần tự (có độ trễ)
5. Lưu ý khi sử dụng Verilog function
Verilog function là công cụ mạnh mẽ giúp tăng khả năng đọc và tái sử dụng mã, nhưng cũng có một số hạn chế.
Phần này sẽ giải thích chi tiết những điểm cần chú ý khi dùng function.
Không thể gọi đệ quy
Trong Verilog, function không được phép gọi đệ quy (recursive call).
Tức là function không thể tự gọi lại chính nó.
❌ Ví dụ sai: Hàm có đệ quy
function [3:0] factorial;
input [3:0] n;
begin
if (n == 0)
factorial = 1;
else
factorial = n * factorial(n - 1); // ❌ không hợp lệ
end
endfunction
Đoạn code này sẽ gây lỗi khi mô phỏng.
✅ Giải pháp: dùng vòng lặp
Nếu muốn xử lý lặp, hãy sử dụng vòng lặp trong always hoặc dùng task.
task factorial_task;
input [3:0] n;
output [15:0] result;
integer i;
begin
result = 1;
for (i = 1; i <= n; i = i + 1)
result = result * i;
end
endtask
→ Dùng vòng lặp thay cho đệ quy.
Không thể dùng độ trễ (#10) trong function
Verilog function luôn được đánh giá ngay lập tức (combinational logic),
nên không thể chứa lệnh trễ (#10).
❌ Ví dụ sai: dùng độ trễ trong function
function [7:0] delay_function;
input [7:0] in;
begin
#10; // ❌ không hợp lệ
delay_function = in + 1;
end
endfunction
→ Đoạn code trên sẽ báo lỗi biên dịch.
✅ Giải pháp: dùng always hoặc task
Nếu cần độ trễ, hãy dùng task hoặc always block.
task delay_task;
input [7:0] in;
output [7:0] out;
begin
#10;
out = in + 1;
end
endtask
→ Các xử lý có độ trễ nên dùng task.
Phân biệt function và task
Ngoài function, Verilog còn có task.
Chúng có sự khác nhau rõ rệt, cần sử dụng đúng mục đích.
Mục
function
task
Đầu ra
1 giá trị duy nhất (tên hàm)
Nhiều giá trị (dùng biến output)
Đầu vào
input duy nhất
input và output đều có thể
Biến nội bộ
Có
Có
Độ trễ (#10)
Không
Có
Dùng trong always
Có
Không
Cách gọi
tên_function(tham_số)
tên_task(tham_số);
Khi nên dùng function
✅ Khi cần kết quả tính toán ngay lập tức (cộng, trừ, logic)
✅ Khi mô tả mạch tổ hợp không có độ trễ ✅ Khi chỉ cần một giá trị đầu ra
Khi nên dùng task
✅ Khi cần xử lý có độ trễ (#10)
✅ Khi có nhiều đầu ra ✅ Khi cần debug trong mô phỏng (ghi log, giám sát)
Function không thể định nghĩa trong always
Function trong Verilog không được khai báo bên trong always.
Chúng phải được định nghĩa bên ngoài module hoặc always.
❌ Ví dụ sai: định nghĩa function trong always
always @(a or b) begin
function [7:0] my_function; // ❌ không hợp lệ
input [7:0] x, y;
begin
my_function = x + y;
end
endfunction
end
✅ Cách đúng
Khai báo function bên ngoài, rồi gọi trong always.
function [7:0] add_function;
input [7:0] x, y;
begin
add_function = x + y;
end
endfunction
always @(a or b) begin
result = add_function(a, b); // ✅ gọi function
end
Tóm tắt
✅ Function có nhiều hạn chế ✅ Không hỗ trợ đệ quy (dùng vòng lặp hoặc task thay thế)
✅ Không chứa độ trễ (dùng always hoặc task)
✅ Không được định nghĩa trong always (phải ở ngoài)
✅ Chỉ có một giá trị trả về (nhiều đầu ra → dùng task)
✅ Cần phân biệt rõ giữa function và task để dùng đúng
6. 【FAQ】Các câu hỏi thường gặp về Verilog function
Chúng ta đã tìm hiểu từ cơ bản đến ứng dụng và lưu ý khi dùng Verilog function.
Phần này tổng hợp những câu hỏi thường gặp và câu trả lời.
Khác nhau giữa function và task?
Q. Sự khác nhau giữa Verilog function và task là gì? Khi nào nên dùng?
A. Function dùng để trả về một giá trị duy nhất ngay lập tức, trong khi task có thể có nhiều đầu ra hoặc chứa độ trễ.
Mục
function
task
Đầu ra
1 giá trị duy nhất (tên hàm)
Nhiều giá trị (biến output)
Đầu vào
input duy nhất
input / output đều có thể
Biến nội bộ
Có
Có
Độ trễ (#10)
Không
Có
Dùng trong always
Có
Không
Cách gọi
tên_function(tham_số)
tên_task(tham_số);
Khi nên dùng function
✅ Khi cần kết quả tính toán ngay (cộng, trừ, phép logic)
✅ Khi mô tả mạch tổ hợp không có độ trễ ✅ Khi chỉ cần 1 giá trị trả về
Khi nên dùng task
✅ Khi cần xử lý có độ trễ (#10)
✅ Khi cần nhiều đầu ra ✅ Khi debug trong mô phỏng (monitor, display)
Có thể dùng reg trong function?
Q. Có thể khai báo reg trong function không?
A. Không thể, nhưng có thể dùng integer thay thế.
Ví dụ:
function [7:0] multiply;
input [3:0] a, b;
integer temp;
begin
temp = a * b;
multiply = temp;
end
endfunction
Khi nào nên dùng function?
Q. Function phù hợp dùng trong trường hợp nào?
A. Function phù hợp cho các phép toán đơn giản hoặc mô tả mạch tổ hợp.
Ví dụ:
Phép toán số học (cộng, trừ, logic)
Decoder hoặc Encoder
So sánh giá trị (tìm max, min)
Kiểm tra lỗi (ví dụ parity check)
⚠️ Không nên dùng cho mạch tuần tự (có flip-flop).
Có thể gọi function khác bên trong function không?
Q. Có thể gọi một function khác trong function không?
A. Có thể, nhưng cần cẩn thận về sự phụ thuộc giữa các function.
Ví dụ:
function [7:0] add;
input [7:0] a, b;
begin
add = a + b;
end
endfunction
function [7:0] double_add;
input [7:0] x, y;
begin
double_add = add(x, y) * 2; // gọi function khác
end
endfunction
Phân biệt function và always?
Q. Nên dùng function hay always trong Verilog?
A. Function dùng cho mạch tổ hợp, còn always dùng cho mạch tuần tự.
Mục
function
always
Độ trễ (#10)
Không
Có
Lưu trạng thái
Không (đánh giá ngay)
Có (dùng làm flip-flop, counter)
Ứng dụng
Mạch tổ hợp (xử lý tức thì)
Mạch tuần tự (có trạng thái)
Ví dụ:
✅ Function (mạch tổ hợp)
function [7:0] add;
input [7:0] a, b;
begin
add = a + b;
end
endfunction
✅ Always (mạch tuần tự)
always @(posedge clk) begin
sum <= a + b; // hoạt động như flip-flop
end
Tóm tắt
✅ Function phù hợp cho tính toán đơn giản và mạch tổ hợp ✅ Task dùng khi có nhiều đầu ra hoặc cần độ trễ ✅ Always phù hợp cho mạch tuần tự có trạng thái ✅ Function không hỗ trợ #delay và không lưu trạng thái