virtual method được sử dụng rất nhiều. Trong UVM, một phương pháp mô phỏng phổ biến hiện nay, nhiều method được khai báo virtual. Vì vậy, việc hiểu virtual method là bước quan trọng để tiếp cận và làm việc với UVM. Bài viết này sẽ trình bày về mục đích và cách sử dụng virtual method thông qua các ví dụ cụ thể.
1) Tổng quan
Một virtual method là một method (function/task) có khai báo từ khóa virtual trước các từ khóa function/task. Cú pháp:
virtual function <type> <method_name> <(arguments)>;
virtual task <method_name>;
Trong đó:
- type: là loại dữ liệu như void, bit, ...
- method_name: là tên method do người dùng định nghĩa
- arguments: là các đối số cho function
2) Thuộc tính ghi đè (override)
Thuộc tính của virtual method là một method của đối tượng tạo từ class mở rộng (subclass) có thể ghi đè lên method của đối tượng class gốc (base class).
Thuộc tính của virtual method là một method của đối tượng tạo từ class mở rộng (subclass) có thể ghi đè lên method của đối tượng class gốc (base class).
Ví dụ 1 - Thuộc tính ghi đè của virtual method
class base_packet;
//
bit [31:0] addr;
bit [31:0] vt_addr;
//non-virtual method
task gen_addr;
//get a random address and mask 2 LSB bits to 0
addr[31:0] = $random & 32'hffff_fffc;
$display ("[gen_addr] Address: %8h", addr[31:0]);
endtask: gen_addr
//
function void inc_addr (bit [31:0] user_addr);
addr[31:0] = user_addr[31:0] + 32'd4; //Increase 4
$display ("[inc_addr] Address: %8h", addr[31:0]);
endfunction: inc_addr
//
//virtual method
//
virtual task gen_vt_addr; //get a random address and mask 2 LSB bits to 0
while ((this.vt_addr[31:0] == this.addr[31:0]) || (this.vt_addr[31:0] == 0)) begin
vt_addr[31:0] = $random & 32'hffff_fffc;
end
$display ("[gen_vt_addr] Address: %8h", vt_addr[31:0]);
endtask: gen_vt_addr
//
//virtual function void inc_vt_addr (bit [31:0] user_vt_addr);
virtual function void inc_vt_addr (bit [31:0] user_vt_addr); vt_addr[31:0] = user_vt_addr[31:0] + 32'd4; //Increase 4
$display ("[inc_vt_addr] Address: %8h", vt_addr[31:0]);
endfunction: inc_vt_addr
//
endclass: base_packet
//
class ext_packet extends base_packet;
//Create a class instance inside this class
//
//non-virtual method
task gen_addr;
//get a random address and mask 4 LSB bits to 0
addr[31:0] = $random & 32'hffff_fff0;
$display ("[gen_addr] Address: %8h", addr[31:0]);
endtask: gen_addr
//
function void inc_addr (bit [31:0] user_addr);
addr[31:0] = user_addr[31:0] + 32'd16; //Increase 16
$display ("[inc_addr] Address: %8h", addr[31:0]);
endfunction: inc_addr
//
//virtual method
//
virtual task gen_vt_addr; //get a random address and mask 4 LSB bits to 0
while ((this.vt_addr[31:0] == this.addr[31:0]) || (this.vt_addr[31:0] == 0)) begin
vt_addr[31:0] = $random & 32'hffff_fff0;
end
$display ("[gen_vt_addr] Address: %8h", vt_addr[31:0]);
endtask: gen_vt_addr
//
virtual function void inc_vt_addr (bit [31:0] user_vt_addr); //function void inc_vt_addr (bit [31:0] user_vt_addr);
vt_addr[31:0] = user_vt_addr[31:0] + 32'd16; //Increase 16
$display ("[inc_vt_addr] Address: %8h", vt_addr[31:0]);
endfunction: inc_vt_addr
//
endclass: ext_packet
module class_ex_virtual;
//
base_packet base_pkt;
ext_packet ext_pkt;
int count = 0;
//
initial begin //
base_pkt = new;
ext_pkt = new;
//1/ Check the methods after creating
$display ("TEST %2d", count);
$display ("-- The handle value: \n\t 1 - base_pkt is %d \n\t 2 - ext_pkt is %d", base_pkt, ext_pkt);
$display ("-- base_pkt");
base_pkt.gen_addr;
base_pkt.inc_addr(base_pkt.addr);
base_pkt.gen_vt_addr;
base_pkt.inc_vt_addr(base_pkt.vt_addr);
$display ("-- ext_pkt");
ext_pkt.gen_addr;
ext_pkt.inc_addr(ext_pkt.addr);
ext_pkt.gen_vt_addr;
ext_pkt.inc_vt_addr(ext_pkt.vt_addr);
$display ("-- Check the value of all variable");
$display ("variable of base_pkt: addr %8h vt_addr %8h", base_pkt.addr, base_pkt.vt_addr);
$display ("variable of ext_pkt: addr %8h vt_addr %8h", ext_pkt.addr, ext_pkt.vt_addr);
count++;
//
//2/ Check the methods with overriding
base_pkt = ext_pkt; // //
$display ("TEST %2d", count);
$display ("-- The handle value: \n\t 1 - base_pkt is %d \n\t 2 - ext_pkt is %d", base_pkt, ext_pkt);
$display ("-- base_pkt");
base_pkt.gen_addr;
base_pkt.inc_addr(base_pkt.addr);
base_pkt.gen_vt_addr;
base_pkt.inc_vt_addr(base_pkt.vt_addr);
$display ("-- ext_pkt");
ext_pkt.gen_addr;
ext_pkt.inc_addr(ext_pkt.addr);
ext_pkt.gen_vt_addr;
ext_pkt.inc_vt_addr(ext_pkt.vt_addr);
$display ("-- Check the value of all variable");
$display ("variable of base_pkt: addr %8h vt_addr %8h", base_pkt.addr, base_pkt.vt_addr);
$display ("variable of ext_pkt: addr %8h vt_addr %8h", ext_pkt.addr, ext_pkt.vt_addr);
count++;
end
//
endmodule: class_ex_virtual
Ví dụ trên gồm một base class tên base_packet, một class mở rộng tên ext_apcket và một module để chạy mô phỏng kiểm chứng.
base_packet có hai non-virtual method là gen_addr và inc_addr và hai virtual method là gen_vt_addr và inc_vt_addr. Hai non-virtual method sẽ tác động lên biến addr, còn hai virtual method sẽ tác động lên biến vt_addr. Chức năng của gen_addr và gen_vt_addr là tương đương nhau, tạo một giá trị ngẫu nhiên với hai bit cuối luôn bằng 0. Chức năng của inc_addr và inc_vt_addr cũng tương đương nhau là tăng một giá trị địa chỉ thêm 4 đơn vị.
ext_packet được mở rộng từ base_packet. ext_packet có 4 method tương tự base_packet nhưng chức năng được điều chỉnh lại, xem các vị trí tô màu vàng.
class_ex_virtual sẽ sử dụng hai định nghĩa class trên để tạo ra hai đối tượng được điều khiển bởi hai biến class base_pkt và ext_pkt. Các thao tác trên hai handle lưu trong hai biến class này sẽ cho thấy sự khác nhau giữa non-virtual method và virtual method. Đồng thời, nó thể hiện thuộc tính ghi đè của virtual method trên đối tượng tạo bởi base class.
Phân tích kết quả mô phỏng, đối với lần kiểm tra thứ nhất "TEST 0", sau khi khởi tạo, chúng ta có hai đối tượng với hai handle riêng biệt là base_pkt = 65538 và ext_pkt = 65539. Các method được gọi để sử dụng trên 2 đối tượng này là độc lập. Đối tượng được tạo từ class nào sẽ sử dụng đúng chức năng của method định nghĩa trong class đó. Ví dụ, gen_addr và gen_vt_addr gọi từ base_pkt sẽ tạo ra địa chỉ có 2 bit cuối luôn được gán bằng 0 (addr = 12153524 và vt_addr = c0895e80); inc_addr và inc_vt_addr gọi từ base_pkt sẽ tạo ra một giá trị địa chỉ mới bằng đối số cộng thêm 4 (addr = 12153528 và vt_addr = c0895e84).
Đối với lần kiểm tra thứ 2 "TEST 1", base_pkt được gán giá trị handle của ext_pkt, cùng bằng 65539. Sau khi gán, các method được gọi từ base_pkt và ext_pkt đều chỉ tác động đến một vùng bộ nhớ duy nhất, nghĩa là cùng truy cập đến chung một biến addr và vt_addr. Điểm khác biệt quan trọng là:
Chú ý, tính chất "virtual" của một method tính từ thời điểm method đó được khai báo trong một class và được giữ nguyên trong các class mở rộng từ class này.
Ví dụ 2 - Kiểm chứng tính chất "virtual" của method
base_packet có hai non-virtual method là gen_addr và inc_addr và hai virtual method là gen_vt_addr và inc_vt_addr. Hai non-virtual method sẽ tác động lên biến addr, còn hai virtual method sẽ tác động lên biến vt_addr. Chức năng của gen_addr và gen_vt_addr là tương đương nhau, tạo một giá trị ngẫu nhiên với hai bit cuối luôn bằng 0. Chức năng của inc_addr và inc_vt_addr cũng tương đương nhau là tăng một giá trị địa chỉ thêm 4 đơn vị.
ext_packet được mở rộng từ base_packet. ext_packet có 4 method tương tự base_packet nhưng chức năng được điều chỉnh lại, xem các vị trí tô màu vàng.
class_ex_virtual sẽ sử dụng hai định nghĩa class trên để tạo ra hai đối tượng được điều khiển bởi hai biến class base_pkt và ext_pkt. Các thao tác trên hai handle lưu trong hai biến class này sẽ cho thấy sự khác nhau giữa non-virtual method và virtual method. Đồng thời, nó thể hiện thuộc tính ghi đè của virtual method trên đối tượng tạo bởi base class.
Hình 1: Kết quả mô phỏng của ví dụ 1 |
Đối với lần kiểm tra thứ 2 "TEST 1", base_pkt được gán giá trị handle của ext_pkt, cùng bằng 65539. Sau khi gán, các method được gọi từ base_pkt và ext_pkt đều chỉ tác động đến một vùng bộ nhớ duy nhất, nghĩa là cùng truy cập đến chung một biến addr và vt_addr. Điểm khác biệt quan trọng là:
- non-virtual method, như gen_addr và inc_addr, nếu được gọi từ base_pkt sẽ thực thi theo chức năng định nghĩa trong base_packet còn nếu được gọi từ ext_pkt sẽ thực thi theo chức năng định nghĩa trong ext_packet. Ta nói rằng "non-virtual method của class gốc base_packet không bị ghi đè".
- virtual method, như gen_vt_addr và inc_vt_addr, đều thực thi theo chức năng định nghĩa trong ext_packet chi dù được gọi từ base_pkt hay ext_pkt. Ta nói rằng "virtual method của class gốc base_packet bị ghi đè bởi virtual method trong class mở rộng". gen_vt_addr được gọi từ base_pkt tạo ra một giá trị ngẫu nhiên có 4 bit cuối bằng 0 (b1f05670) thay vì chỉ 2 bit cuối như base_packet đã định nghĩa; inc_vt_addr được gọi từ base_pkt cộng thêm 8 đơn vị để tạo ra một giá trị vt_addr mới (b1f05680) thay vì cộng thêm 4 như base_packet đã định nghĩa.
Chú ý, tính chất "virtual" của một method tính từ thời điểm method đó được khai báo trong một class và được giữ nguyên trong các class mở rộng từ class này.
Ví dụ 2 - Kiểm chứng tính chất "virtual" của method
class base_packet;
//
bit [31:0] vt_addr;
//
//non-virtual method
//
task gen_vt_addr;
//get a random address and mask 2 LSB bits to 0
while (this.vt_addr[31:0] == 0) begin
vt_addr[31:0] = $random & 32'hffff_fffc;
end
$display ("[base_packet - gen_vt_addr] Address: %8h", vt_addr[31:0]);
endtask: gen_vt_addr
//
function void inc_vt_addr (bit [31:0] user_vt_addr);
vt_addr[31:0] = user_vt_addr[31:0] + 32'd4; //Increase 4
$display ("[base_packet - inc_vt_addr] Address: %8h", vt_addr[31:0]);
endfunction: inc_vt_addr
//
endclass: base_packet
class ext_packet extends base_packet;
//
//virtual method
//
virtual task gen_vt_addr;
//get a random address and mask 4 LSB bits to 0
while (this.vt_addr[31:0] == 0) begin
vt_addr[31:0] = $random & 32'hffff_fff0;
end
$display ("[ext_packet - gen_vt_addr] Address: %8h", vt_addr[31:0]);
endtask: gen_vt_addr
//
virtual function void inc_vt_addr (bit [31:0] user_vt_addr);
vt_addr[31:0] = user_vt_addr[31:0] + 32'd16; //Increase 16
$display ("[ext_packet - inc_vt_addr] Address: %8h", vt_addr[31:0]);
endfunction: inc_vt_addr
//
endclass: ext_packet
class ext_packet_lv2 extends ext_packet;
//
task gen_vt_addr;
//get a random address and mask 8 LSB bits to 0
vt_addr[31:0] = vt_addr[31:0] & 32'hffff_ff00;
$display ("[ext_packet_lv2 - gen_vt_addr] Address: %8h", vt_addr[31:0]);
endtask: gen_vt_addr
//
function void inc_vt_addr (bit [31:0] user_vt_addr);
vt_addr[31:0] = user_vt_addr[31:0] + 32'd256; //Increase 32
$display ("[ext_packet_lv2 - inc_vt_addr] Address: %8h", vt_addr[31:0]);
endfunction: inc_vt_addr
//
endclass: ext_packet_lv2
module class_ex_virtual;
//
base_packet base_pkt;
ext_packet ext_pkt;
ext_packet_lv2 ext_pkt_lv2;
int count = 0;
//
initial begin //
base_pkt = new;
ext_pkt = new;
ext_pkt_lv2 = new;
//1/ Check the methods after creating
$display ("TEST %2d", count);
$display ("-- The handle value: \n\t 1 - ext_pkt is %d \n\t 2 - ext_pkt is %d \n\t 3 - ext_pkt_lv2 is %d", base_pkt, ext_pkt, ext_pkt_lv2);
$display ("-- base_pkt");
base_pkt.gen_vt_addr;
base_pkt.inc_vt_addr(base_pkt.vt_addr);
$display ("-- ext_pkt");
ext_pkt.gen_vt_addr;
ext_pkt.inc_vt_addr(ext_pkt.vt_addr);
$display ("-- ext_pkt_lv2");
ext_pkt_lv2.gen_vt_addr;
ext_pkt_lv2.inc_vt_addr(ext_pkt_lv2.vt_addr);
count++;
//2/ Check the methods with overriding
base_pkt = ext_pkt_lv2;
ext_pkt = ext_pkt_lv2; //
//
$display ("TEST %2d", count);
$display ("-- The handle value: \n\t 1 - ext_pkt is %d \n\t 2 - ext_pkt is %d \n\t 3 - ext_pkt_lv2 is %d", base_pkt, ext_pkt, ext_pkt_lv2);
$display ("-- base_pkt");
base_pkt.gen_vt_addr;
base_pkt.inc_vt_addr(base_pkt.vt_addr);
$display ("-- ext_pkt");
ext_pkt.gen_vt_addr;
ext_pkt.inc_vt_addr(ext_pkt.vt_addr);
$display ("-- ext_pkt_lv2");
ext_pkt_lv2.gen_vt_addr;
ext_pkt_lv2.inc_vt_addr(ext_pkt_lv2.vt_addr);
count++;
end //
endmodule: class_ex_virtual
Ví dụ trên có 3 class gồm:
- base_packet: class gốc không được mở rộng từ class nào
- ext_packet: một subclass được mở rộng từ base_packet
- ext_packet_lv2: một subclass được mở rộng từ ext_packet
Hình 2: Kết quả mô phỏng của ví dụ 2 |
3) Sự phù hợp của virtual method khi ghi đè
3.1) Quy định về tính tương đồng (phù hợp)
Khi "ghi đè" một virtual method của class gốc bằng một method trong class mở rộng, "ghi đè" ở đây nghĩa là định nghĩa một method trong class mở rộng có tên trùng với method trong class gốc, virtual method trong class mở rộng phải đáp ứng các yêu cầu sau đây:
3.2) Kiểu dữ liệu (Type) không tương đồng
Như đã trình bày, Việc biên dịch không gây lỗi vì user_type có thể phù hợp hoặc không phù hợp với kiểu dữ liệu "bit [31:0]". Điều này chỉ có thể biết khi sử dụng class ext_packet_2.
Nếu ext_packet_2 được khai báo và sử dụng như sau thì sẽ không gây lỗi vì user_type là "bit [31:0]":
# ** Error: (vsim-8113) D:/system_verilog/class/class_ex_virtual_type.sv (19): Type of argument 'user_in' for virtual method 'vt_method_ex' in subclass 'ext_packet_2__1' does not match the type of argument in superclass 'base_packet'.
#
# Region: /class_ex_virtual_type_sv_unit::ext_packet_2 #(int)
Nếu khai báo kiểu dữ liệu của user_in trong method của class mở rộng khác với method trong class gốc, ví dụ như:
** Error: D:/system_verilog/class/class_ex_virtual_type.sv(12): (vlog-2909) Type of argument 'user_in' of virtual method 'vt_method_ex' in class 'ext_packet_1' does not match with type of argument in the abstract superclass 'base_packet'.
3.3) Tên đối số không đồng nhất
Đối với virtual method, số lượng đối số và tên đối số trong method ghi đè của class mở rộng phải trùng với class gốc. Trong ví dụ 3, đối số này là user_in. Giả sử, user_in của ext_packet_1 được đổi thành user_diff.
** Error: D:/system_verilog/class/class_ex_virtual_type.sv(18): (vlog-2911) Argument name 'user_diff' for virtual method 'vt_method_ex' in sub class 'ext_packet_1' does not match the argument name 'user_in' in superclass 'base_packet'.
3.4) Chiều (direction) không đồng nhất
Trong ví dụ 3, chiều mặc định của đối số user_in là input vì nó là đối số của function. Giả sử, user_in của ext_packet_1 được khai báo là ref.
** Error: D:/system_verilog/class/class_ex_virtual_type.sv(18): Mode 'ref' of argument 'user_in' for virtual method 'vt_method_ex' does not match mode 'input' in superclass 'base_packet'
3.1) Quy định về tính tương đồng (phù hợp)
Khi "ghi đè" một virtual method của class gốc bằng một method trong class mở rộng, "ghi đè" ở đây nghĩa là định nghĩa một method trong class mở rộng có tên trùng với method trong class gốc, virtual method trong class mở rộng phải đáp ứng các yêu cầu sau đây:
- Kiểu dữ liệu của đối số (argument) phải phù hợp (thuật ngữ "phù hợp" ứng với từ matching)
- Tên các đối số phải đồng nhất (giống nhau, trùng tên)
- Các khai báo từ hạn định (qualifier), là từ quy định giới hạn/thuộc tính như protected, local, ... phải đồng nhất
- Các khai báo về chiều (direction) như input, output, inout, ref phải đồng nhất.
class base_packet;
//
typedef bit [31:0] vt_addr;
//
virtual function base_packet vt_method_ex (bit [31:0] user_in);
endfunction //
endclass: base_packet
class ext_packet_1 extends base_packet;
//
virtual function ext_packet_1 vt_method_ex (vt_addr user_in);
endfunction //
endclass: ext_packet_1
class ext_packet_2 #(type user_type = int) extends base_packet;Ví dụ trên có 3 class gồm:
//
virtual function ext_packet_1 vt_method_ex (user_type user_in);
endfunction //
endclass: ext_packet_2
- base_packet là class gốc
- ext_packet_1 là một class mở rộng từ base_packet không có tham số đầu vào
- ext_packet_2 là một class mở rộng từ base_packet có tham số đầy vào là user_type.
- Kiểu dữ liệu trả về là base_packet
- Kiểu dữ liệu của đối số user_in là kiểu bit với độ rộng 32 bit.
- Chiều của đối số user_in mặc định là input trong một định nghĩa function.
- Kiểu dữ liệu của đối số user_in trong ext_packet_1 là vt_addr, phù hợp với kiểu dữ liệu "bit [31:0]" trong base_packet nên không gây lỗi biên dịch và mô phỏng.
- Kiểu dữ liệu của đối số user_in trong ext_packet_2 là user_type, có thể phù hợp hoặc không phù hợp với kiểu dữ liệu "bit [31:0]" trong base_packet nên không gây lỗi biên dịch nhưng sẽ gây lỗi mô phỏng nếu không sử dụng đúng.
3.2) Kiểu dữ liệu (Type) không tương đồng
Như đã trình bày, Việc biên dịch không gây lỗi vì user_type có thể phù hợp hoặc không phù hợp với kiểu dữ liệu "bit [31:0]". Điều này chỉ có thể biết khi sử dụng class ext_packet_2.
Nếu ext_packet_2 được khai báo và sử dụng như sau thì sẽ không gây lỗi vì user_type là "bit [31:0]":
ext_packet_2 #(bit [31:0]) ext_pkt_2; //legalHoặc ext_packet_2 được khai báo và sử dụng như sau cũng không gây lỗi vì user_type là vt_addr, một kiểu đã được định nghĩa là "bit [31:0]".
ext_packet_2 #(base_packet::vt_addr) ext_pkt_2; //legalNếu ext_packet_2 được khai báo và sự dụng như sau sẽ gây lỗi khi chạy mô phỏng vì user_type là "int", kiểu mặc định được gán cho user_type trong class này:
ext_packet_2 #() ext_pkt_2; //IllegalThông điệp lỗi có thể như sau:
# ** Error: (vsim-8113) D:/system_verilog/class/class_ex_virtual_type.sv (19): Type of argument 'user_in' for virtual method 'vt_method_ex' in subclass 'ext_packet_2__1' does not match the type of argument in superclass 'base_packet'.
#
# Region: /class_ex_virtual_type_sv_unit::ext_packet_2 #(int)
Nếu khai báo kiểu dữ liệu của user_in trong method của class mở rộng khác với method trong class gốc, ví dụ như:
virtual function ext_packet_1 vt_method_ex (int user_in);sẽ gây ra lỗi ngay khi biên dịch:
** Error: D:/system_verilog/class/class_ex_virtual_type.sv(12): (vlog-2909) Type of argument 'user_in' of virtual method 'vt_method_ex' in class 'ext_packet_1' does not match with type of argument in the abstract superclass 'base_packet'.
3.3) Tên đối số không đồng nhất
Đối với virtual method, số lượng đối số và tên đối số trong method ghi đè của class mở rộng phải trùng với class gốc. Trong ví dụ 3, đối số này là user_in. Giả sử, user_in của ext_packet_1 được đổi thành user_diff.
virtual function ext_packet_1 vt_method_ex (vt_addr user_diff);Lỗi biên dịch sẽ xảy ra và thông báo về sự khác tên đối số:
** Error: D:/system_verilog/class/class_ex_virtual_type.sv(18): (vlog-2911) Argument name 'user_diff' for virtual method 'vt_method_ex' in sub class 'ext_packet_1' does not match the argument name 'user_in' in superclass 'base_packet'.
3.4) Chiều (direction) không đồng nhất
Trong ví dụ 3, chiều mặc định của đối số user_in là input vì nó là đối số của function. Giả sử, user_in của ext_packet_1 được khai báo là ref.
virtual function ext_packet_1 vt_method_ex (ref vt_addr user_in);Lỗi biên dịch sẽ xảy ra và thông báo về sự khác direction:
** Error: D:/system_verilog/class/class_ex_virtual_type.sv(18): Mode 'ref' of argument 'user_in' for virtual method 'vt_method_ex' does not match mode 'input' in superclass 'base_packet'
0 bình luận:
Đăng nhận xét