Thứ Bảy, 9 tháng 2, 2019

[System Verilog][Class]Bài 8 - Virtual method

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ể.
Trước khi đọc bài viết này, bạn đọc tham khảo các bài 1bài 2 để biết rõ một số khái niệm và thuật ngữ cơ bản về class trong SV. Chú ý, bài viết sử dụng "class gốc" tương ứng với base class, "class mở rộng" tương ứng với subclass.

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 đó:
  1. type: là loại dữ liệu như void, bit, ...
  2. method_name: là tên method do người dùng định nghĩa
  3. 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).

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 inc_addr và hai virtual method là gen_vt_addr 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 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 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 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
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 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 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 vt_addr = c0895e80); inc_addr 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 = 12153528vt_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_pktext_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 vt_addr. Điểm khác biệt quan trọng là:
  1. non-virtual method, như gen_addr 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 đè".
  2. virtual method, như gen_vt_addrinc_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.
Một virtual method của  base class có thể bị ghi đè bởi non-virtual method trong các subclass nhưng tính chất "virtual" của nó vẫn được giữ nguyên. Trong ví dụ 1, method gen_vt_addr inc_vt_addr của subclass ext_packet không cần phải khai báo từ khóa virtual nhưng kết quả mô phỏng vẫn không thay đổi vì tính chất "virtual" của hai method này được bảo lưu từ class gốc.
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:
  1. base_packet: class gốc không được mở rộng từ class nào
  2. ext_packet: một subclass được mở rộng từ base_packet
  3. ext_packet_lv2: một subclass được mở rộng từ ext_packet
Các phần được tô màu xanh là các định nghĩa method, gen_vt_addr inc_vt_addr chỉ được khai báo virtual trong ext_packet nên tính chất virtual của các method này chỉ duy trì trong ext_packet ext_packet_lv2 (không áp dụng cho base_packet).
Hình 2: Kết quả mô phỏng của ví dụ 2
Trong kết quả mô phỏng, chú ý đến lần "TEST 1", lúc này các biến class đều được gán đến handle 65540, do thuộc tính "virtual" được khai báo trong ext_packet nên method gọi thông qua base_pkt không bị ghi đè.
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:
  1. 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)
  2. Tên các đối số phải đồng nhất (giống nhau, trùng tên)
  3. 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
  4. Các khai báo về chiều (direction) như input, output, inout, ref phải đồng nhất.
Ví dụ 3 - Kiểm chứng sự phù hợp virtual method khi ghi đè
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;
 //
 virtual function ext_packet_1 vt_method_ex (user_type user_in);
 endfunction //
endclass: ext_packet_2
Ví dụ trên có 3 class gồm:
  1. base_packet là class gốc
  2. ext_packet_1 là một class mở rộng từ base_packet không có tham số đầu vào
  3. ext_packet_2 là một class mở rộng từ base_packet có tham số đầy vào là user_type.
base_packet định nghĩa một kiểu dữ liệu tên vt_addr. Kiểu dữ liệu này tương đương với kiểu bit với độ rộng 32 bit. base_packet còn định nghĩa một virtual method vt_method_ex có các đặc điểm sau:
  1. Kiểu dữ liệu trả về là base_packet
  2. Kiểu dữ liệu của đối số user_in là kiểu bit với độ rộng 32 bit.
  3. Chiều của đối số user_in mặc định là input trong một định nghĩa function.
ext_packet_1 và ext_packet_2 mở rộng từ base_packet định nghĩa lại (ghi đè) method vt_method_ex. Chúng ta thấy rằng:
  1. 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.
  2. Kiểu dữ liệu của đối số user_in trong ext_packet_2 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. 
Một số lỗi không tương thích (không có sự phù hợp) giữa virtual method ghi đè và bị ghi đè sẽ được phân tích 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]":
ext_packet_2 #(bit [31:0]) ext_pkt_2; //legal
Hoặ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; //legal
Nế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; //Illegal
Thô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 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