Thứ Hai, 28 tháng 1, 2019

[System Verilog][Class]Bài 2 - Mở rộng class

Trong System Verilog (SV), mở rộng class là một khả năng giúp class có tính kế thừa và tái sử dụng rất cao. Bài viết này trình bày chi tiết về khả năng này và những vấn đề liên quan đến việc mở rộng class giúp bạn đọc nhanh chóng hiểu và sử dụng.

1) Tổng quan
Khả năng "mở rộng class" (class extension) là việc tạo một class mới dựa trên một class đã có sẵn. Class mới gọi là class con (subclass) và class dùng để mở rộng gọi là class gốc (base class). Từ khóa sử dụng cho việc mở rộng class trong SV là extends. Class gốc và class mở rộng có nhiều cách gọi khác nhau, các bạn có thể tham khảo hình 1 để dễ dàng đọc hiểu các tài liệu tham khảo.
Hình 1: Các tên gọi khác nhau của class gốc và class mở rộng
Việc tạo class theo phương pháp mở rộng làm subclass mang các đặc điểm sau đây:
  1. Tính kế thừa (inheritability): thể hiện ở việc subclass có tất cả các đặc điểm bao gồm các thuộc tính (property) và  các method của base class.
  2. Tính sửa đổi (modifiability) thêm các ràng buộc (constraint) để điều chỉnh các thuộc tính cũ của base class hoặc định nghĩa lại các method của base class.
  3. Tính mở rộng (extensibility): thể hiện ở việc subclass có thể được thêm nhưng thuộc tính, method mới.
Chúng ta sẽ lần lượt phân tích các đặc điểm trên thông qua các ví dụ cụ thể trong bài viết này.
2) Làm thế nào để mở rộng class (tạo subclass)?
Cấu trúc cơ bản dùng để mở rộng một class (tạo subclass) như sau:
class <subclass_name> extends <baseclass_name>;
  [properties or methods]
endclass
Ví dụ 1 - Mở rộng một class
class new_class extends packet;
...
endclass
Trong ví dụ trên, new_class là một subclass được mở rộng từ một class đã có sẵn tên packet. Chú ý, một class được mở rộng như new_class có thể dùng làm base class cho một subclass khác.
Để bắt đầu phân tích các điểm nổi bật của việc mở rộng class, giử sử có một base class như sau:
Ví dụ 2 - Class được sử dụng làm base class trong các ví dụ của bài viết
class rand_packet;
     //
     // 1: Declare the class properties
     //
     integer num;
     integer rand_data []; //A dynamic array
     integer i;
     //
     // 2: Constructor
     //
     function new;
       begin          num = 1;
          i = 0;
       end      endfunction     //
     // 3: Build the class methods (tasks or functions)
     //
     // Method 1: Task in class (object method)
     // Create the random data and store to an array element
     task build_data ();
       begin         rand_data = new[num]; //Set the size of dynamic array
         for (i = 0; i < num; i++) begin           rand_data[i] = $random; //Assign a random value to an array entry
         end       end     endtask: build_data
     //
     // Method 2: Task in class (object method)
     // Display all values of array
     task print ();
       begin         for (i=0; i < num; i ++) begin           $display("rand_data[%d] %x",i, rand_data[i]);
         end       end      endtask: print
     //
     // Method 3: Function in class (object method)
     // Get the size of the array
     function integer get_size();
       begin         get_size = num;
       end     endfunction: get_size
     
  endclass: rand_packet
Class này có chức năng chính là tạo ra một mảng lưu các giá trị ngẫu nhiên với số lượng giá trị phụ thuộc vào biến num. Các bạn đọc lại bài định nghĩa về class để biết thêm chi tiết.
3) Tính kế thừa (inheritability)
Như đã trình bày, một class mở rộng sẽ kế thừa toàn bộ các thành phần (property và method) của class gốc.
Ví dụ 3 - Mở rộng class và chỉ kế thừa
class rand_packet_ihr extends rand_packet;

endclass: rand_packet_ihr
Trong ví dụ trên, class rand_packet_ihr được mở rộng từ class rand_packet. Phần thân của class rand_packet_ihr không có bất cứ đoạn code nào thêm. Trong trường hợp này, rand_packet_ihr kế thừa nguyên vẹn các thành phần của rand_packet mà không kèm thêm bất cứ sự điều chỉnh hay thành phần mới nào.
Ví dụ 4 - Kiểm chứng kết quả khi sử dụng rand_packet_ihr và rand_packet
rand_packet pkt;
pkt = new;
pkt.build_data();
$display ("Size of packet pkt: %0d",pkt.get_size());
pkt.print();
rand_packet_ihr pkt_ihr;
pkt_ihr = new;
pkt_ihr.build_data();
$display ("Size of packet pkt_ihr: %0d",pkt_ihr.get_size());
pkt_ihr.print();
Hình 2: Kết quả mô phỏng ví dụ 3 và 4
Để kiểm chứng tính kế thừa, ví dụ trên tạo ra hai đối tượng với handle pkt pkt_ihr từ, một từ class gốc rand_packet và một từ class mở rộng rand_packet_ihr. Kết quả thực thi các method của hai đối tượng là như nhau:
  1. Số lượng giá trị mặc định num = 1
  2. Chức năng của method get_size giống nhau
  3. Chức năng của method print giống nhau

Chú ý, giá trị rand_data[0] của pktpkt_ihr khác nhau là do giá trị này được tạo ra từ function $random.
Tính kế thừa trong trường hợp này giống như việc tạo một class mới tên rand_packet_ihr với code của phần thân class được sao chép nguyên vẹn từ class rand_packet.
Hình 3: Mở rộng class chỉ kế thừa toàn vẹn như ví dụ 3
Nếu việc mở rộng class chỉ để chỉ kế thừa toàn vẹn như ví dụ 3 thì thuộc tính này không có lợi ích gì đặc biệt ngoài việc sử dụng để đổi tên (rename) class. Thuộc tính này chỉ phát huy sức mạnh của nó khi đi kèm với "tính sửa đổi" và "tính mở rộng" vì nếu bạn không cần thêm bất cứ chỉnh sửa gì thì bạn chỉ cần sử dụng class đã có sẵn, không cần phải mở rộng.
4) Tính sửa đổi (modifiability)
"Sửa đổi" ở đây được hiểu là điều chỉnh những thành phần đã có sẵn của class gốc trong class mở rộng. Tuy phần này cũng có thể xem như là thuộc tính mở rộng nhưng tác giả muốn tách riêng thành một mục gọi là thuộc tính sửa đổi để nhấn mạnh nội dung này.
Ví dụ 5 - Mở rộng class và điều chỉnh thành phần của class gốc
class rand_packet_mdf extends rand_packet;
 function new;
   begin     num = 3;
   end endfunction
 task build_data ();
   begin     rand_data = new[num];
     for (i = 0; i < num; i++) begin       rand_data[i] = 2*i;
     end   end
 endtask: build_data
endclass: rand_packet_mdf
Trong ví dụ trên, class mở rộng rand_packet_mdf được suy ra từ class gốc rand_packet nhưng được điều chỉnh ở hai điểm:
  1. Giá trị ban đầu của biến num được gán lại là 3 thay cho giá trị 1 trong class gốc
  2. Method build_data được viết lại thay thế cho build_data trong class gốc. build_data trong class mở rộng sẽ lấy giá trị bằng cách nhân đôi biến lặp i (trong  class gốc là lấy một giá trị ngẫu nhiên cho function $random sinh ra).
Còn lại, biến i, method print và method get_size không thay đổi so với class gốc.
Ví dụ 6 - Kiểm chứng kết quả khi sử dụng class rand_packet_mdf và rand_packet
rand_packet      pkt;
pkt = new;
pkt.build_data();
$display ("Size of packet pkt: %0d",pkt.get_size());
pkt.print();
//
rand_packet_mdf  pkt_mdf;
pkt_mdf = new;
pkt_mdf.build_data();
$display ("Size of packet pkt_mdf: %0d",pkt_mdf.get_size());
pkt_mdf.print();
Hình 4: Kết quả mô phỏng ví dụ 5 và 6
Khi một method của class gốc được viết lại trong class mở rộng bằng cách sử dụng trùng tên, ví dụ như method build_data trong ví dụ 5. Trường hợp này, method trong class mở rộng sẽ ghi đè (override) lên method cùng tên trong class gốc. Nói cách khác method trong class gốc bị ghi đè (overridden) bởi method trùng tên trong class mở rộng. "Ghi đè" được hiểu là các xử lý của method trong class gốc hoàn toàn bị thay thế bởi các xử lý mới được mô tả trong method của class mở rộng.
Đối với function new thì khác, hãy chú ý điểm này, new là một constructor, một method đặc biệt. new sẽ không bị ghi đè mà chỉ mở rộng thêm. Nghĩa là, những xử lý trong function new của class gốc vẫn được thực thi trước khi thực thi các xử lý trong function new của class mở rộng. Để kiểm chứng, function new của class rand_packet được sửa lại như sau:
function new;
 begin    num = 1;
    i = 0;
    $display ("This is rand_packet with num = %d and i = %d", num, i);
 end endfunction
function new của class rand_packet_mdf được sửa lại như sau:
function new;
 begin
   num = 3;
  $display ("This is rand_packet_mdf with num = %d and i = %d", num, i);
 end
endfunction
Tạo một đối tượng của class rand_packet để chạy mô phỏng như sau:
rand_packet_mdf  pkt_mdf;
pkt_mdf = new;
Kết quả thu được:
Hình 5: Kết quả mô phỏng cho function new
Hình 5 cho thấy ngay khi đối tượng được tạo bởi phép gán "pkt_mdf = new", các xử lý trong function new của class gốc chạy trước và cho hiển thị dòng 1. Sau đó, các xử lý được mô tả thêm trong function new của class mở rộng được thực thi và cho hiển thị như dòng 2. Như vậy, function new không bị "ghi đè" (override) mà chỉ là thêm (add) xử lý.
Việc mở rộng class như trong ví dụ 5 tương đương với việc viết một class mới như minh họa hình 6.
Hình 6: Mở rộng class có sửa đổi như ví dụ 5 
Tính sửa đổi kết hợp với tính kế thừa giúp giảm thời gian xây dựng class mới khi người lập trình chỉ tập trung điều chỉnh các thành phần cần thiết để đáp ứng nhu cầu mới (function new và method build_data) còn các thành phần không sửa đổi (print, get_size) có thể tái sử dụng (re-use).
5) Tính mở rộng (extensibility)
"Mở rộng" trong mục này được hiểu là thêm thuộc tính và method mới vào class mở rộng, nó khác với việc chỉnh sửa thuộc tính và method của class gốc như mục 4. Với khả năng này, một class mở rộng kế thừa nguyên vẹn các thành phần của class gốc, đồng thời thêm vào những thuộc tính và method mới để mở rộng chức năng của class.
Ví dụ 7 - Tính mở rộng của class
class rand_packet_ext extends rand_packet;
   
 rand bit [3:0] mod_value;
 constraint mod_value_range {
 mod_value[3:0] <= 4'd3;
 mod_value[3:0] > 0;
 }

 task countup;
   begin     for (i = 0; i < num; i++) begin       rand_data[i] = rand_data[i] + mod_value;
     end   end endtask
endclass: rand_packet_ext
Trong ví dụ này class rand_packet_ext được mở rộng từ class rand_packet và thêm các thành mới như sau:
  1. Thêm một property mới là mod_value, biến này được khai báo dạng rand để có thể gán một giá trị ngẫu nhiên khi sử dụng method $randomize, đây là một method có sẵn của SV.
  2. Thêm một constraint tên mod_value_range cho biến mod_value để ràng buộc giá trị ngẫu nhiên của biến này nằm trong khoảng từ 1 đến 3.
  3. Thêm một methos mới tên countup có chức năng cộng từng phần tử của mảng rand_data với giá trị mod_value.
Việc mở rộng class như trên tương đương với việc sao chép toàn bộ class gốc sang class mới và viết thêm code cho các thành phần mới như hình hinh họa sau:
Hình 7: Mở rộng class và thêm các thành phần mới như ví dụ 7
Để kiểm chứng hoạt động của class, các bạn có thể sử dụng đoạn code sau:

//Create a new object
rand_packet_ext pkt_ext;
pkt_ext = new;
//Assign the number of elements of dynamic array
pkt_ext.num = 3;
$display ("Size of packet pkt_ext: %0d",pkt_ext.get_size());
//Create the count unit (mod_value)
for (int j = 0; j < 10; j++) begin
 if (pkt_ext.randomize() == 1) begin
  $display ("The value of mod_value: %d", pkt_ext.mod_value);
 end
end
//Create the random array before counting up
$display ("//Before counting up");
pkt_ext.build_data();
pkt_ext.print();
//Add all elements of array with mod_value
$display ("//After counting up");
pkt_ext.countup;
pkt_ext.print();

Đoạn code trên gồm các phần như sau:
1. Tạo một đối tượng mới của class rand_packet_ext
rand_packet_ext pkt_ext;
pkt_ext = new;
2. Thiết lập số phần tử của mảng rand_data
pkt_ext.num = 3;
3. Tạo một giá trị ngẫu nhiên cho biến mod_value

for (int j = 0; j < 10; j++) begin 
  if (pkt_ext.randomize() == 1) begin 
     $display ("The value of mod_value: %d", pkt_ext.mod_value);
  end
end
Việc lặp 10 lần chỉ để chứng minh tác dụng của constraint mod_value_range. Với constraint này, các giá trị ngẫu nhiên được tạo ra sẽ chỉ là 1, 2 hoặc 3. Giá trị ngẫu nhiên của một biến rand như mod_value được sinh ra do 2 yếu tố. Thứ nhất, biến phải khai báo rand hoặc randc. Thứ hai, gọi method $randomize. Trong ví dụ này, method $randomize được gọi trong điều kiện if.
4. Tạo các giá trị ngẫu nhiên và gán vào mảng rand_data theo số lượng đã thiết lập bởi ở biến num. Sau đó, các giá trị mảng rand_data được in ra để kiểm tra.
$display ("//Before counting up");
pkt_ext.build_data();
pkt_ext.print();
5. Cộng các giá trị trong mảng rand_data với giá trị giá trị mod_value. Chú ý, giá trị mod_value được sử dụng là giá trị ngẫu nhiên cuối cùng được tạo ra ở bước 3. Sau đó, các giá trị mảng rand_data được in lại lần nữa để kiểm tra.
$display ("//After counting up");
pkt_ext.countup;
pkt_ext.print();
Hình 8: Kết quả mô phỏng kiểm chứng hoạt động của class ví dụ 7
Tóm lại, bài viết tập trung mô tả về cách tạo một class mới bằng cách mở rộng từ một class có sẵn. Đồng thời giải thích cách thức điều chỉnh và thêm thành phần mới trong class mở rộng.

0 bình luận:

Đăng nhận xét