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

[System Verilog][Class]Bài 5 - Thuộc tính static

Bài viết này trình bày về việc khai báo biến (property) và method với từ khóa static trong class.

Lưu ý, bạn đọc tham khảo các bài 1, bài 2bài 3 và bài 4 về class trong System Verilog (SV) trước khi đọc bài này vì nhiều thuật ngữ cơ bản trong class đã được giải thích ở các bài trước nên chúng không được giải thích lại trong bài này.

1) Biến static (static property)
Chú ý, thuật ngữ "biến" và property được sử dụng trong bài viết này là một.
Trong SV, thuộc tính mặc định của một biến là non-static. Nếu muốn khai báo một biến static thì từ khóa static phải được sử dụng trước một khai báo biến. Một biến được khai báo static trong class sẽ có những đặc điểm sau:
  1. Biến static có thể được sử dụng mà không cần tạo đối tượng (sử dụng qua một handle null). Nghĩa là có khai báo tên đối tượng nhưng không cần khởi tạo với new.
  2. Tất cả các đối tượng được tạo ra từ class chỉ sử dụng một phiên bản duy nhất của biến static. Nghĩa là, tất cả các đối tượng đều chỉ nhìn thấy một giá trị chung. Biến static có thể được thay đổi giá trị trong bất kỳ đối tượng nào. Sau khi giá trị biến được cập nhật, tất cả các đối tượng khác đều nhìn thấy và sử dụng giá trị mới của biến.
Hai đặc điểm trên khác biệt với biến non-static:
  1. Biến non-static chỉ sử dụng được khi một đối tượng được khởi tạo (handle khác null)
  2. Mỗi đối tượng có một bản sao chép các biến non-static riêng để sử dụng độc lập nên sự thay đổi giá trị của biến non-static trong đối tượng này không ảnh hưởng đến đối tượng khác.
Ví dụ 1a - Khai báo và sử dụng static property
class ctrl_packet;
 static bit [31:0] addr;
 bit [63:0] wdata;
 bit we;
 bit sel;
 function new ();
   begin      addr[31:0]  = 32'd0;
      wdata[63:0] = 64'd0;
      we  = 1'b0;
      sel = 1'b1;
   end  endfunction
 task print;
   if (we) begin     $display ("Write packet: addr=%x, wdata=%x, we=%1x, sel=%1x", addr, wdata, we, sel);
   end   else begin     $display ("Read packet: addr=%x, wdata=%x, we=%1x, sel=%1x", addr, wdata, we, sel);
   end endtask: print
endclass: ctrl_packet
module class_ex_static;
  ctrl_packet wpkt_1;
  ctrl_packet wpkt_2;
  ctrl_packet wpkt_3;
  initial begin    wpkt_1 = new; //Create a new object
    wpkt_2 = new; //Create a new object
    $display ("- The object info: \n\t 1 - wpkt_1 is %d \n\t 2 - wpkt_2 is %d \n\t 3 - wpkt_3 is %d", wpkt_1, wpkt_2, wpkt_3);
    //
    //Update the value of properties via the object wpkt_1
    wpkt_1.addr  = 32'h0101_0101; //Note: addr is the static property
    wpkt_1.wdata = 64'h0000_1111_2222_3333;
    wpkt_1.we    = 1'b1;
    wpkt_1.sel   = 1'b1;
 
   $display ("1 - Update the value of properties via the object wpkt_1");
    wpkt_1.print;
    wpkt_2.print;
  $display ("Use a declared name without creating the object - addr: %x\n", wpkt_3.addr);
 
    //Update the value of addr via the object wpkt_2
    wpkt_2.addr  = 32'h8989_8989;
 
   $display ("2 - Update the value of properties via the object wpkt_2");
    wpkt_1.print;
    wpkt_2.print;
  $display ("Use a declared name without creating the object - addr: %x\n", wpkt_3.addr);
 
    //Update the value of addr via wpkt_3 which is not an object handle
    wpkt_3.addr  = 32'habab_abab;
 
    $display ("3 - Update the value of addr via wpkt_3 which is not an object handle");
    wpkt_1.print;
    wpkt_2.print;
  $display ("Use a declared name without creating the object - addr: %x\n", wpkt_3.addr);
  endendmodule: class_ex_static
Ví dụ trên xây dựng một class tên ctrl_packet. Class này khai báo 4 biến là addr, wdata, we sel. Trong đó, addr là một biến có thuộc tính static. Method new sẽ khởi tạo các biến về một giá trị ban đầu. Method print sẽ in ra dòng thông báo về thông tin của gói điều khiển tùy vào giá trị của biến we. Nếu we = 1 thì đó là một "Write packet", nếu we = 0 thì đó là một "Read packet".
Module class_ex_static được tạo để chạy mô phỏng kiểm chứng đặc tính của biến static (addr) so với các biến non-static (wdata, we sel). Trong module này, 3 tên đối tượng được khai báo là wpkt_1, wpkt_2 wpkt_3 nhưng wpkt_3 không được gán một handle đối tượng (không có khai báo wpkt_3 = new). Xem lại bài 1 để biết chi tiết. Thứ tự thực thi trong process initial như sau:
  1. Tạo đối tượng mới và gán handle của đối tượng đã tạo cho wpkt_1 và wpkt_2. Như đã nói, wpkt_3 không được gán cho bất cứ handle nào nên wpkt_3 là handle null.
  2. In ra thông tin của các handle đối tượng đã tạo
  3. Thay đổi giá trị các biến thông qua handle wpkt_1
  4. In thông tin gói điều khiển của đối tượng thông qua handle wpkt_1 và wpkt_2. In giá trị của biến static addr thông qua tên biến wpkt_3.
  5. Thay đổi giá trị biến addr thông qua handle wpkt_2
  6. In thông tin gói điều khiển của đối tượng thông qua handle wpkt_1 và wpkt_2. In giá trị của biến static addr thông qua tên biến wpkt_3.
  7. Thay đổi giá trị biến addr thông qua handle null wpkt_3
  8. In thông tin gói điều khiển của đối tượng thông qua handle wpkt_1 và wpkt_2. In giá trị của biến static addr thông qua tên biến wpkt_3.
Hình 1: Kết quả mô phỏng của ví dụ 1a
Kết quả mô phỏng cho chúng ta biết các thông tin sau:
  1. Hai đối tượng mới được tạo và giá trị handle của wpkt_1 65538, wpkt_2 là 131074. wpkt_3 là một handle null.
  2. Trong lần cập nhật giá trị biến thứ nhất, các biến được cập nhật thông qua handle wpkt_1 nên chỉ giá trị của các biến điều khiển bởi handle này thay đổi. Trừ biến static addr, nó được cập nhật cho toàn bộ các đối tượng vì biến static chỉ có 1 phiên bản duy nhất.
  3. Trong lần cập nhật thứ 2, chỉ biến static addr được thay đổi thông qua handle wpkt_2 đến giá trị 32'h8989_8989 và cũng được nhìn thấy bởi các handle khác.
  4. Trong lần cập nhật thứ 3, biến static được cập nhật đến giá trị 32'habab_abab thông qua một handle null wpkt_3 và giá trị này cũng cập nhật cho các đối tượng khác.
Khác với biến static, nếu biến non-static được truy cập thông qua một handle null, biên dịch code không bị lỗi nhưng lỗi chạy mô phỏng sẽ xuất hiện. Bạn có thể thử bằng cách thay một đoạn code "wpkt_3.addr" bằng "wpkt_3.wdata".
Hình 2: Lỗi mô phỏng khi truy xuất một biến non-static thông bằng handle null
Một biến static trong một method (function/task) của class cũng có các đặc điểm tương tự như một biết static trong class nhưng ngoài method.
Ví dụ 1b - Khai báo một biến static trong method
class addr_packet;
//
 task gen_addr_static;
   static bit [31:0] addr;    
   for (int i = 0; i < 2; i++) begin    
     addr = addr + 4; addr = addr & 32'hffff_fffc;
     $display ("[Static] Address %2d: %d", i, addr);
   end endtask: gen_addr_static
 //
 task gen_addr;
   bit [31:0] addr;    
   for (int i = 0; i < 2; i++) begin 
     addr = addr + 4; addr = addr & 32'hffff_fffc;
     $display ("[Non-static] Address %2d: %d", i, addr);
   end endtask: gen_addr
 //
endclass: addr_packet
//
module class_ex_static;
  addr_packet apkt_1;
  addr_packet apkt_2;
  initial begin  
    apkt_1 = new;
    $display ("- The object info: \n\t 1 - apkt_1 is %d \n\t 2 - apkt_2 is %d", apkt_1, apkt_2);
    //
    //apkt_1.gen_addr.addr = 2;
    $display ("1/ Check the non-static variable");
    apkt_1.gen_addr;
    apkt_2.gen_addr;
    $display ("--------------------------------\n");
    //
    $display ("2/ Check the static variable with addr is assigned via apkt_1");
    apkt_1.gen_addr_static.addr = 5;
    apkt_1.gen_addr_static;
    apkt_2.gen_addr_static;
    $display ("--------------------------------\n");
    //
    $display ("3/ Check the static variable with addr is assigned via apkt_2");
    apkt_2.gen_addr_static.addr = 0;
    apkt_1.gen_addr_static;
    apkt_2.gen_addr_static;
    $display ("--------------------------------\n");
  end 
endmodule
Trong ví dụ trên, class addr_packet có 2 method là gen_addr_static gen_addr. Chức năng hai method này giống nhau là tạo ra 2 giá trị địa chỉ liên tiếp align theo đơn vị 4. Hai method này chỉ khác nhau ở thuộc tính của biến addr. addr của gen_addr_static có khai báo static còn addr của gen_addr thì không.
Việc đặt tên trùng nhau để chứng minh phạm vi của hai biến addr này là khác nhau (thuộc 2 method khác nhau) nên chúng là hai biến độc lập.
Trong module class_ex_static, biến addr có thuộc tính static có thể được gán giá trị thông qua một handle đối tượng như apkt_1 hoặc một khai báo biến class như apkt_2 bằng dòng code:
apkt_1.gen_addr_static.addr = 5;
hoặc
apkt_2.gen_addr_static.addr = 0;
Còn biến non-static thì không thể được gán như trên, ví dụ như dòng code sau sẽ gây ra lỗi khi biên dịch code:
apkt_1.gen_addr.addr = 2;
Hình 3: Lỗi biên dịch khi cố ý gán một biến non-static trong method
Lỗi trên có thể được giải quyết bằng 2 cách:
  1. Khai báo static cho biến trong method như trong task gen_addr_static
  2. Khai báo static bên phải từ khóa task như sau "task static gen_addr" (phần này được trình bày sau trong bài viết này)
Hình 4: Kết quả mô phỏng ví dụ 1b
Đoạn code của module class_ex_static dùng để kiểm chứng và so sánh sự khác nhau giữa biến static và non-static được khai báo trong method. Chú ý, apkt_1 là một handle đối tượng còn apkt_2 chỉ là một khai báo biến với class addr_packet.
Đối với biến non-static, mỗi lần gọi method, một phiên bản của biến addr được tạo và giá trị addr được tạo lại từ đầu. Ở đây, addr được khai báo kiểu bit, một kiểu dữ liệu hai trạng thái nên giá trị mặc định của addr là 0. Sau hai lần lặp, giá trị addr lần lượt là 4 và 8.
Đối với biến static, chỉ một phiên bản duy nhất được tạo ra nên mỗi lần gọi method, dù ở bất kỳ handle hay biến class nào (apkt_1 hoặc apkt_2) thì nó đều tác động lên phiên bản này. Vì vậy, kết quả mô phỏng tăng đều 4 đơn vị với 2 bit cuối luôn bằng 0.
Hành vi khởi tạo giá trị ban đầu cho biến static và non-static cũng khác nhau.
Ví dụ 1c - Khởi tạo giá trị đầu cho biến static và non-static
class addr_packet;
//
 task gen_addr_static;
   static bit [31:0] addr = 8;   for (int i = 0; i < 2; i++) begin  
     addr = addr + 4;
     addr = addr & 32'hffff_fffc;
     $display ("[Static] Address %2d: %d", i, addr);
   end endtask: gen_addr_static
 //
 task gen_addr;
   bit [31:0] addr = 8;   for (int i = 0; i < 2; i++) begin  
     addr = addr + 4;
     addr = addr & 32'hffff_fffc;
     $display ("[Non-static] Address %2d: %d", i, addr);
   end endtask: gen_addr
 //
endclass: addr_packet
//
module class_ex_static;
  addr_packet apkt_1;
  addr_packet apkt_2;
  initial begin  
    apkt_1 = new;
    $display ("- The object info: \n\t 1 - apkt_1 is %d \n\t 2 - apkt_2 is %d", apkt_1, apkt_2);
    //
    $display ("1/ Check the non-static variable");
    apkt_1.gen_addr;
    apkt_2.gen_addr;
    $display ("--------------------------------\n");
    //
    $display ("2/ Check the static variable");
    apkt_1.gen_addr_static;
    apkt_2.gen_addr_static;
    $display ("--------------------------------\n");
    //
  end 
endmodule
Hình 5: Kết quả mô phỏng cho ví dụ 1c
Trong ví dụ trên, hai biến addr được khởi tạo giá trị ban đầu là 8 trong method. Đối với biến static, việc khởi tạo chỉ thực hiện 1 lần trong lần gọi đầu tiên của method. Đối với biến non-static, mỗi lần gọi method, biến sẽ được khởi tạo lại từ đầu.
2) Method tĩnh (static method)
Tương tự như property, thuộc tính mặc định của method là non-static. Việc khai báo từ khóa static trước từ khóa function/task khi định nghĩa method sẽ xác định thuộc tính static cho method. Một method static có những đặc điểm sau:
  1. Method static có thể được sử dụng mà không cần tạo đối tượng (sử dụng qua một handle null). Nghĩa là có khai báo tên đối tượng nhưng không cần khởi tạo với new.
  2. Có thể sử dụng trực tiếp các property và method static trong cùng class.
  3. Không thể sử dụng các thành phần non-static và từ khóa this bên trong method static. Việc sử dụng sai sẽ gây ra lỗi khi biên dịch code.
Ví dụ 2 - Khai báo và sử dụng method static
class ctrl_packet_ex extends ctrl_packet;
 static task mask_addr;   addr[31:0] = addr[31:0] & 32'hffff_fffc;
 endtask: mask_addr
endclass: ctrl_packet_ex
//
module class_ex_static;
  ctrl_packet wpkt_1;
  ctrl_packet wpkt_2;
  ctrl_packet_ex wpkt_ex;  initial begin    wpkt_1 = new; //Create a new object
    wpkt_2 = new; //Create a new object
    $display ("- The object info: \n\t 1 - wpkt_1 is %d \n\t 2 - wpkt_2 is %d \n\t 3 - wpkt_ex is %d", wpkt_1, wpkt_2, wpkt_ex);
 
    wpkt_1.addr = 32'h0123_ffff;
    wpkt_ex.mask_addr;  $display (" addr of wpkt_1 %x\n addr of wpkt_2 %x\n addr of wpkt_ex %x\n", wpkt_1.addr, wpkt_2.addr, wpkt_ex.addr);
 
  endendmodule: class_ex_static
Ví dụ trên tạo một class mới ctrl_packet_ex được mở rộng từ class ctrl_packet. Class mở rộng được thêm một method static tên mask_addr để tạo ra một giá trị addr luôn align the đơn vị 4 bằng cách gán 2 bit cuối luôn bằng 0. Vì addr là một biến static nên có thể sử dụng trực tiếp trong method static. Nếu một biến non-static như wdata, we hoặc sel được gọi và dùng trong method mask_addr sẽ gây ra lỗi biên dịch.
Module class_ex_static sẽ khai báo 3 tên biến, 2 biến wpkt_1 wpkt_2 là loại ctrl_packet, 1 biến wpkt_ex thuộc loại ctrl_packet_ex nhưng chỉ có wpkt_1 wpkt_2 được tạo đối tượng và gán một giá trị handle. Biến addr được gán giá trị 32'h0123_ffff thông qua handle wpkt_1, được điều chỉnh lại giá trị thông qua biến class wpkt_ex bằng method static mask_addr. Sau đó, giá trị addr được in ra để kiểm tra.
Hình 6: Kết quả mô phỏng của ví dụ 2
3) Thời gian sống tĩnh (static lifetime)
Khi định nghĩa một method (function/task) với từ khóa static, vị trí của từ khóa static so với vị trí của từ khóa function/task sẽ có ý nghĩa và tác dụng khác nhau.
Nếu từ khóa static đặt trước từ khóa function/task (như mục 2) thì ta nói đó là một "static method".
Nếu từ khóa static đặt sau từ khóa function/task thì nó quy định thời gian sống cho các đối số và biến trong method là tĩnh (static lifetime). Vai trò của từ khóa static lúc này giống như vai trò của từ khóa automatic.
Như vậy, khi nói đến một "static method" thì bạn hiểu là:
static function/task
Còn khi nói đến một method có "static lifetime" của các biến thì bạn hiểu là:
function/task static
Sau đây là liệt kê các cách khai báo thuộc tính static cho method và cách hiểu:

  1. static function/task <method_name>: khai báo một "static method" với thời gian sống của các biến là automatic.
  2. static function/task automatic <method_name>: khai báo một "static method" với thời gian sống của các biến là automatic.
  3. static function/task static <method_name>: khai báo một "static method" với thời gian sống của các biến là static.
  4. function/task static <method_name>: khai báo một "non-static method" với thời gian sống của các biến là static.
Ví dụ 3 - So sánh khái niệm "static method" và "static lifetime"
class count_packet;
  //non-static function with static lifetime
  function static integer inc;
    int count;
    inc = count++;
    $display ("[non-static] INC: count = %d", count);
  endfunction: inc
  //static function with automatic lifetime
  static function integer inc_static;
    int count;
    inc_static = count++;
    $display ("[static] INC: count = %d", count);
  endfunction: inc_static
 
endclass: count_packet
//
module class_ex_static;
  count_packet pkt_1;
  count_packet pkt_2;
  count_packet pkt_3;
  initial begin 
    pkt_1 = new;
    pkt_2 = new;
    $display ("- The object info: \n\t 1 - pkt_1 is %d \n\t 2 - pkt_2 is %d \n\t 3 - pkt_3 is %d", pkt_1, pkt_2, pkt_3);
    //
    $display ("First");
    pkt_1.inc;
    pkt_2.inc;
    pkt_3.inc;
    //
    $display ("Second");
    pkt_1.inc_static;
    pkt_2.inc_static;
    pkt_3.inc_static;
 
  end 
endmodule: class_ex_static
Hình 7: Kết quả mô phỏng cho ví dụ 3
Method có khai báo thời gian sống tĩnh cho các biến (static lifetime) như inc thì các biến trong method sẽ mang thuộc tính static nên chỉ có một phiên bản duy nhất sử dụng cho các đối tượng khác nhau. Một static method không có khai báo gì thêm như inc_static thì các biến mặc định là automatic lifetime nên mỗi lần gọi method, một bản sao của các biến được tạo ra.

Hy vọng bài viết này trình bày đầy đủ các khía cạnh khác nhau khi sử dụng từ khóa static trong class. Mọi góp ý và trao đổi xin comment dưới bài viết.

0 bình luận:

Đăng nhận xét