Thứ Sáu, 25 tháng 1, 2019

[System Verilog][Class]Bài 1 - Định nghĩa và tạo đối tượng Class trong system verilog

Trong System Verilog (SV), class là một công cụ được sử dụng rất nhiều và rất hữu dụng. Các phương pháp mô phỏng dùng SV như UVM cũng được xây dựng trên các class. Hy vọng bài viết này giúp các bạn hiểu cơ bản về class trong ngôn ngữ SV.

1) Class là gì?
Class là một loại dữ liệu (data type) có cấu tạo gồm dữ liệu (data) và các chương trình con (subroutine). Cả dữ liệu và chương trình con của class được gọi chung là thành phần (member) của class.
Hình 1: Cấu trúc của class
Trong SV, một loại dữ liệu sẽ gồm một tập các giá trị (value) và một tập các “xử lý” (operation) có thể thao tác hiệu chỉnh, sử dụng các giá trị đó. Class có đầy đủ 2 yếu tố để định nghĩa một loại dữ liệu.
Hình 2: Cấu trúc của loại dữ liệu (data type)
Dữ liệu của class còn được gọi là các thuộc tính của class (class property). Các chương trình (subroutine) con còn được gọi là các method hoặc procedure. Các method có thể là function hoặc task.
Xét đoạn code ví dụ sau đây (trích từ tài liệu IEEE Std 1800™-2012).

Ví dụ 1 – Định nghĩa một class:
class Packet ;
//data or class properties
bit [3:0] command;
bit [40:0] address;
bit [4:0] master_id;
integer time_requested;
integer time_issued;
integer status;
typedef enum { ERR_OVERFLOW= 10, ERR_UNDERFLOW = 1123} PCKT_TYPE;
const integer buffer_size = 100;
const integer header_size;
// initialization
function new();
  command = 4'd0;
  address = 41'b0;
  master_id = 5'bx;
  header_size = 10;
endfunction
// methods
// public access entry points
task clean();
  command = 0;
  address = 0;
  master_id = 5'bx;
endtask
task issue_request( int delay );
  ...
endtask
function integer current_status();
  current_status = status;
endfunction
endclass
Trong đoạn code trên, class Packet gồm 9 thuộc tính (command, address, master_id, time_requested, time_issued, status, PCKT_TYPE, buffer_size và header_size) và 4 method (new, clean, issue_request, current_status). Trong đó, new là một method đặc biệt được build sẵn bởi SV. Khi một đối tượng class được tạo ra thì method new sẽ được thực thi đầu tiên. Trong đoạn code ví dụ, method new được mở rộng thêm để gán các giá trị ban đầu cho một số biến trong class. Gọi là “được mở rộng thêm” vì nếu class Packet không định nghĩa method new thì method new mặc định vẫn được thực thi. Nghĩa là, nếu không định nghĩa method new trong class thì mặc định, class vẫn có một method new giống như việc viết một method new rỗng như sau:
function new();
endfunction
2) Tại sao cần phải sử dụng class?
Class là thành phần thể hiện rõ đặc tính lập trình hướng đối tượng (OOP - Object-Oriented Program) của SV. Class giúp tạo, gán, truy cập và xóa bỏ các đối tượng một cách linh động. “Đối tượng” là các class instance.
Class có tính kế thừa rất cao, một class mới có thể được mở rộng (extends) từ một class đã được viết trước đó để kế thừa toàn bộ các đặc tính (dữ liệu và method) của class này. Bên cạnh tính kế thừa, class mới có thể điều chỉnh thuộc tính và method của class nó kế thừa hoặc thêm vào các thuộc tính và method mới.
3) Làm thế nào để xây dựng một class?
Chú ý, phần này chỉ trình bày cấu trúc class với những thành phần cơ bản hay dùng nhất để giúp các bạn nhanh chóng nắm bắt cách viết một class chứ không phân tích đầy đủ toàn bộ các thành phần có thể có trong class.
Cấu trúc một class cơ bản gồm những thành phần sau:
1. Khai báo thuộc tính (property) của class
2. Constructor
3. Xây dựng các method (task, function) của class

Ví dụ 2: Xây dựng một class cơ bản
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
Trên đây là ví dụ về việc xây dựng một class cơ bản. Class rand_packet có chức năng chính là tạo ra một mảng các giá trị ngẫu nhiên (random) với số lượng do người sử dụng tùy chọn.
Bước 1, các thuộc tính class được khai báo gồm
  1. num: số lượng giá trị ngẫu nhiên sẽ được tạo đồng thời là kích thước của mảng lưu các giá trị này.
  2. rand_data: là một mảng động (dynamic array) có kích thước tùy thuộc vào giá trị num
  3. i: là biến sử dụng cho các vòng lặp trong method build_data và print.
Bước 2, constructor của một class là một function new. Các dòng code viết trong function này sẽ được thực hiện một cách tự động khi một đối tượng class được tạo ra. Trong ví dụ này, thuộc tính num sẽ được gán bằng 1 và i sẽ được gán bằng 0 ngay khi một đối tượng class mới được tạo ra. Như đã trình bày ở mục trên, nếu class không có constructor thì nó sẽ thực thi function new mặc định. Như vậy, constructor giúp chúng ta gán các giá trị ban đầu cho các thuộc tính hoặc thực hiện một số tác vụ ban đầu ngay khi một đối tượng class được tạo ra.

Bước 3, các method được xây dựng cho class rand_packet bao gồm build_data (task), print (task) và get_size (function).

Chú ý, constructor (function new) sẽ tự động chạy khi một đối tượng được tạo ra (xem mục 4 để biết 1 đối tượng được tạo như thế nào) còn các method chỉ chạy khi được gọi.
4) Làm sao để tạo ra một đối tượng class?
Vậy “đối tượng” (object) ở đây là gì? Một đối tượng là một instance của class. Một định nghĩa class có thể được dùng để tạo ra nhiều đối tượng khác nhau. Điều này giống như “một định nghĩa module có thể được gọi nhiều lần để tạo nhiều instance khác nhau”. Một đối tượng được tạo ra bằng 2 bước sau:
1. Khai báo một biến với loại dữ liệu class
2. Khởi tạo biến là tạo đối tượng bằng function new và gán handle của đối tượng đến biến đã khai báo (chú ý, ngữ cảnh sử dụng của new trong trường hợp này là khác với new sử dụng ở constructor khi định nghĩa class ở mục 3)
Cú pháp tạo một đối tượng class:
<class_name> <handle_name>;
<handle_name> = new();
Hai bước trên có thể được thực hiện trong cùng một dòng code:
<class_name> <handle_name> = new();

Ví dụ 3 – Tạo một đối tượng class (class instance)
rand_packet pkt; //declare a variable of class rand_packet
pkt = new; // initialize variable to a new allocated object
rand_packet q = new;
Trong mục 3, chúng ta đã định nghĩa một loại dữ liệu rand_packet. Ví dụ 3, chúng ta khai báo 2 đối tượng là pkt và q. Mỗi đối tượng này là một class instance.
biến pkt được khai báo với loại dữ liệu tên rand_packet. Sau đó, function new được sử dụng để tạo một đối tượng mới và handle của đối tượng này được gán đến biến pkt. Handle của đối tượng giống như pointer trong ngôn ngữ C nhưng có nhiều giới hạn (tham khảo thêm trong tài liệu về chuẩn SV), chứ không “tự do” như C. Thông qua handle, chúng ta có thể truy xuất các biến và sử dụng các method của đối tượng.
Chú ý, function new sử dụng trong ngữ cảnh này sẽ tạo ra một handle đối tượng gán cho pkt và q. Nó khác với function new trong constructor của class, constructor được dùng để gán giá trị đầu hoặc thực thi một số tác vụ của khi đối tượng được tạo bằng phép gán pkt = new và q = new. 
Một đối tượng được khai báo nhưng không được khởi tạo thì sao? Khi không được tạo, function new không được gọi, thì handle của đối tượng mang giá trị đặc biệt là null. Chú ý, “null” là giá trị đặc biệt của SV có thể được dùng để so sánh và kiểm ttra xem một đối tượng đã được khở tạo hay chưa. Giả sử chúng ta không có “p = new” thì p sẽ bằng “null”.
5) Truy xuất thuộc tính và sử dụng các method trong một đối tượng class
Để truy xuất thuộc tính và sử dụng các method trong một class, chúng ta sử dụng cách sau đây:
<object_handle>.<property or method>

Ví dụ 4 – Sử dụng class
rand_packet pkt; //Declare a object
rand_packet q;
pkt = new; //Create a handle refereing to a allocated object
pkt.num = 3;
pkt.build_data(); //Create 3 random data values
$display ("Size of packet pkt: %0d",pkt.get_size()); //Check the number of values
pkt.print(); //Show the 3 random data value
q = new; //Create a handle refereing to a allocated object
q.build_data(); //Create 1 random data value
$display ("Size of packet q: %0d",q.get_size()); //Check the number of values
q.print(); //Show the 1 random data value
Trong ví dụ trên, pkt chính là handle của một đối tượng được tạo từ class rand_packet. Sau khi tạo một đối tượng, thuộc tính “num” được gán giá trị 3 bằng phép gán:
pkt.num = 3;
Giá trị này sẽ thay thế cho giá trị khởi tạo ban đầu “num = 1” trong constructor của class rand_packet.
pkt.build_data();
Code trên sẽ gọi và sử dụng method build_data để tạo ra 3 giá trị ngẫu nhiên vì num = 3. Tương tự, hai thành phần code sau gọi và sử dụng 2 method get_size và print.
pkt.get_size()
pkt.print()
Đối với đối tượng được điều khiển bởi handle q, gọi tắt là đối tượng q, nó sẽ sử dụng giá trị mặc định num = 1 được gán trong constructor của class rand_packet.
6) Đối tượng và handle của đối tượng
Đọc đến đây, các bạn đã hiểu rõ đối tượng và handle của đối tượng chưa? Phần này, tác giả muốn trình bày riêng một chút để đảm bảo chúng ta có cách nhìn đúng và giống nhau. Quay lại ví dụ về cách tạo ra đối tượng.
rand_packet pkt;
pkt = new;
Câu hỏi là "pkt" được gọi là gì? "pkt" là một biến có loại dữ liệu là rand_packet. Chú ý, class định nghĩa một loại dữ liệu (data type). "pkt" được gọi là một "biến class" (class variable). Biến này được tạo ra bởi dòng code:
rand_packet pkt;
Biến class "pkt" sẽ được gán một giá trị gọi là "handle của đối tượng" thông qua dòng code:
pkt = new;
Như vậy, handle của đối tượng là giá trị gán cho biến class "pkt" khi new được gọi. handle giống như một địa chỉ riêng của đối tượng, nhờ địa chỉ này chúng ta có thể chỉ chính xác đến các thuộc tính và method của đối tượng đã tạo. "Đối tượng" được tạo ra khi thực thi new nhưng chúng ta không tác động trực tiếp đến đối tượng mà thông qua một biến class lưu handle của đối tượng.
Tóm lại, pkt là một biến class. Khi một đối tượng được tạo ra, nó sẽ lưu giữ handle của đối tượng. Lúc này, pkt có thể được gọi là "một handle của đối tượng". Cách gọi "đối tượng pkt" là cách gọi không chính xác.
7) Ví dụ hoàn chỉnh và kết quả
Các thành phần được viết trong một module để chạy mô phỏng.
module class_ex;
  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
 
   //
   //Create an object (instance class)
   //
   rand_packet pkt; //Declare a object
   rand_packet q;
   initial begin
     pkt = new; //Create a handle refereing to a allocated object
     pkt.num = 3;
     pkt.build_data(); //Create 3 random data values
     $display ("Size of packet pkt: %0d",pkt.get_size()); //Check the number of values
     pkt.print(); //Show the 3 random data value
   
     q = new; //Create a handle refereing to a allocated object
     q.build_data(); //Create 1 random data value
     $display ("Size of packet q: %0d",q.get_size()); //Check the number of values
     q.print(); //Show the 1 random data value
   end
endmodule
Hình 3: Kết quả mô phỏng
Lịch sử cập nhật:
1) 2019.02.04 - Chỉnh sửa nội dung mục 6

0 bình luận:

Đăng nhận xét