Thứ Hai, 28 tháng 1, 2019

[System Verilog][Class]Bài 3 - Hiểu về function new

Function new là một chức năng đã được build sẵn (built-in) trong System Verilog (SV). Function này là một phần không thể thiếu khi xây dựng các class. Bài viết này sẽ trình bày về các khía cạnh sử dụng khác nhau của function new.
Hãy đọc các bài viết liên quan đến class Bài 1Bài 2 trong SV trước khi đọc bài này.

1) new() là gì?
new là một function được xây dựng sẵn của SV nên nó là một keyword của SV. new không xác định loại dữ liệu trả về (type) và các phép gán sử dụng trong new phải là phép gán blocking, ký hiệu là "=" (Xem lại ví dụ bài 2).
Ví dụ 1 - So sánh new và function khác
function new;
 begin    
   num = 1;
    i = 0;
 end 
endfunction//
function integer get_size();
 begin    
get_size = num;
 end 
endfunction: get_size
Trong ví dụ trên, get_size() có xác định loại dữ liệu trả về là integer còn new thì không. new là một function nên cũng chỉ sử dụng phép gán blocking như get_size().
Nếu cố khai báo loại dữ liệu trả về cho function new thi phần mềm biên dịch sẽ báo lỗi.
Ví dụ 2 - Khai báo loại dữ liệu trả về cho new (vi phạm)
function integer new;
 begin    num = 1;
    i = 0;
 end 
endfunction
Ví dụ trên cố tình khai báo integer cho function new. Một lỗi biên dịch code sẽ sinh ra:
Hình 1: Lỗi biên dịch code khi khai báo loại dữ liệu trả về cho function new
2) Chức năng của new() đối với class
Liên quan đến class, new có hai ngữ cảnh sử dụng:
  1. Tạo đối tượng class (class instance) và gán handle của đối tượng đến một biến class (xem bài 1)
  2. Class constructor: Gán giá trị ban đầu cho các biến hoặc thực hiện một số tác vụ ban đầu khi một đối tượng class được khởi tạo.
Ngữ cảnh sử dụng thứ nhất, các bạn đọc mục 4 của bài 1.
Ngữ cảnh sử dụng thứ hai, các bạn đọc mục 3 của bài 1.
Ví dụ 3 - new là một constructor
class rand_packet;
     //
     // 1: Declare the class properties
     //
     integer num = 1;
     integer rand_data []; //A dynamic array
     integer i;
     //
     // 2: Constructor
     //
     function new;
       begin  
          num = 5;
          i = 0;
         $display ("This is rand_packet with num = %d and i = %d", num, i);
       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;
         end  
          //$display ("This is a base class");
       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
Trong ví dụ trên, khi một đối tượng class được tạo, ví dụ như bởi code sau:
rand_packet      pkt = new;
function new trong class sẽ được gọi. Nó thực hiện gán num = 5, i = 1 và hiển thị dòng thông báo về giá trị của num và i thông qua $display.
Hình 2: Kết quả thực thi constructor new trong ví dụ 3
Khi class là một constructor, function new có thể được truyền thêm các đối số (argument) để sử dụng linh động class khi khởi tạo các đối tượng (class instance). 
Ví dụ 4 - function new có đối số
function new (integer exp_num = 0);
 begin
    if (exp_num != 0) begin
      num = exp_num;
    end
    else begin
      num = 1;
    end
    i = 0;
    $display ("This is rand_packet with num = %d and i = %d", num, i);
 end 
endfunction
Trong ví dụ trên, một đối số exp_num được thêm vào function new. Đối số này được sử dụng để xử lý việc gán giá trị ban đầu cho biến num. Nếu đối số truyền vào lớn hơn 0 thì num sẽ lấy giá trị của exp_num. Nếu đối số không được truyền cho new hoặc <= 0 thì num = 1. Thay function new của ví dụ 4 vào class của ví dụ 3 rồi sử dụng đoạn code sau để chạy mô phỏng kiểm chứng:
rand_packet      pkt;
pkt = new(8);
pkt.build_data();
$display ("Size of packet pkt: %0d",pkt.get_size());
pkt.print();
Chú ý dòng pkt = new(8), đối số được truyền cho function new là 8. Kết quả:
Hình 3: Kết quả mô phỏng của function new với đối số là 8
Một class luôn tồn tại function new cho dù nó có được khai báo hay không. Nếu một class không khai báo function new thì nó sẽ sử dụng function new mặc định của hệ thống. Việc không khai báo function new cho class tương đương với việc khai báo một function new rỗng.
function new();
endfunction
3) new() trong class mở rộng
Như đã trình bày trong mục 4 của bài 2, new là một method đặc biệt của class. Nó không bị "ghi đè" (overridden) hoàn toàn trong class mở rộng.
Ví dụ 5 - new của class mở rộng
class rand_packet_mdf extends rand_packet;
  function new;
   begin
     num = 3;
   $display ("This is rand_packet_mdf with num = %d and i = %d", num, i);
   end
  endfunction

endclass: rand_packet_mdf
Ví dụ trên là một class mở rộng từ class rand_packet. Trong class mở rộng này, chỉ function new được viết lại. Khi chạy mô phỏng kiểm chứng với code sau:
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ới một đối tượng của class mở rộng rand_packet_mdf
Kết quả cho thấy new của class gốc rand_packet vẫn được thực thi và hiển thị ở dòng đầu tiên hình 4. Điều này là bởi vì new trong class mở rộng rand_packet_mdf sẽ thực thi mặc định lệnh super.new trước khi thực thi các dòng code nào khác mô tả trong new. super.new sẽ gọi và thực thi new của class gốc rand_packet.
Hình 5: Sự tương đương trong mô tả function new trong class mở rộng và class tạo mới (không được mở rộng từ class khác)
Một số lưu ý về super.new:
1. super.new luôn được thực thi đầu tiên trong function new của class mở rộng dù nó có được mô tả hay không. Vì vậy, nếu mô tả super.new sau các dòng code khác trong function new, lỗi biên dịch sẽ xảy ra.
Ví dụ 6 - mô tả sai vị trí super.new trong function new (sai)
function new;
 begin
   num = 3;
   super.new;
  $display ("This is rand_packet_mdf with num = %d and i = %d", num, i);
 end
endfunction
Hình 6: Lỗi biên dịch code khi mô tả sai vị trí super.new trong fucntion new của class mở rộng
2. Không mô tả super.new trong class không mở rộng (không có extends)
Ví dụ 7 - Mô tả super.new trong một class không mở rộng từ class khác (sai)
class rand_packet;
  function new;
   begin
     super.new;
     num = 3;
    $display ("This is rand_packet with num = %d and i = %d", num, i);
   end
  endfunction

endclass: rand_packet
Hình 7: Lỗi biên dịch khi mô tả super.class trong một class không mở rộng
4) Khai báo local và protected cho new()
4.1) local
Khi new là một constructor của class, nó có thể được khai báo là local hoặc protected.
local là một từ khóa dùng để xác định một thành phần chỉ được truy xuất nội bộ. Trong class, một property hoặc method được khai báo local sẽ chỉ sử dụng trong phạm vi class đó. Nó không thể được truy xuất ở phạm vi ngoài class nên không thể được truy xuất thông qua handle của đối tượng. Các thành phần được khai báo local gọi là "bị ẩn" (hiding) đối với tất cả các phạm vi ngoài class, bao gồm cả subclass được suy ra từ class này. Chú ý, hiding (data hiding) là một thuật ngữ hay sử dụng để nói về phương pháp ẩn và giới hạn truy cập các thành phần của class từ bên ngoài.
new là một method, nó có thể được khai báo local. Việc khai báo local cho new sẽ làm cho một class không thể được mở rộng, nói cách khác nó không thể làm base class cho một class mở rộng.
Ví dụ 8 - Khai báo thêm local cho new trong class rand_packet 
local function new;
 begin    num = 1;
    i = 0;
    $display ("This is rand_packet with num = %d and i = %d", num, i);
 end
endfunction
Khi khai báo thêm local cho function new của class rand_packet trong ví dụ 3, trình biên dịch sẽ báo lỗi đối với class mở rộng rand_packet_mdf.
Hình 8: Lỗi biên dịch khi mở rộng class rand_packet vì khai báo local cho new
Như đã trình bày ở trên, function new của class mở rộng (rand_packet_mdf) luôn gọi new của class gốc thông qua super.new. Việc khai báo local cho new trong class gốc (rand_packet) làm cho super.new trong subclass (rand_packet_mdf) không thể thực hiện được vì new mang thuộc tính local nên không thể truy xuất new bên ngoài class gốc (rand_packet).
Ngoài ra, chúng ta không thể tạo đối tượng (một class instance) từ một class new được khai báo local vì new không được phép gọi ngoài phạm vi class. Ví dụ, giả sử class rand_packet new được khai báo local như ví dụ 8. Sau đó, một đối tượng được tạo từ rand_packet như sau:
rand_packet      pkt = new;
Hình 9: Lỗi biên dịch khi tạo một đối tượng từ class có new được khai báo local
Có thể thấy, một class có constructor được khai báo local không sai cú pháp nhưng nó không thể mở rộng được và không thể tạo đối tượng để sử dụng nên chúng ta không dùng khai báo local cho constructor của class.
4.2) protected
protected là từ khóa có tác dụng tương tự local ở đặc điểm "không cho phép truy xuất các thành phần được khai báo protected bên ngoài class" và  khác ở đặc điểm "cho phép truy xuất các thành phần được khai báo protected trong class mở rộng (subclass)".
Nếu khai báo protected cho constructor của class thì class sẽ không thể tạo trực tiếp một đối tượng từ class này.
Ví dụ 9 - Khai báo thêm protected cho new trong class rand_packet
protected function new;
 begin    num = 1;
    i = 0;
    $display ("This is rand_packet with num = %d and i = %d", num, i);
 end
endfunction
Khi khai báo protected cho new, việc tạo đối tượng trực tiếp từ rand_packet sẽ bị lỗi biên dịch:
rand_packet      pkt = new;
Hình 10: Lỗi biên dịch khi tạo đối tượng trực tiếp từ class new được khai báo protected
Tuy nhiên, việc mở rộng class và truy xuất các thành phần protected thông qua class mở rộng là được phép, ví dụ như class rand_packet_mdf được mở rộng từ class rand_packet được chấp nhận.

Lịch sử cập nhật:
1) 2019.01.30 - Thêm mục "Khai báo local và protected cho new()"
2) 2022.10.30 - Sửa các từ khóa nonblocking thành blocking

2 bình luận: