EDA Playground'da Dene

Sınıflar ve Nesneler

Gün 2: Nesne Yönelimli Programlama (OOP) | OOP temelleri: sınıf tanımlama, nesne oluşturma, handle kavramı

Bu derste SystemVerilog'da nesne yönelimli programlamanın temel taşı olan sınıfları (class) ve nesneleri (object) öğreneceğiz. Bir sınıfın nasıl tanımlandığını, nesnelerin new() ile nasıl oluşturulduğunu ve handle (referans) kavramının değişken kopyalamasından neden farklı olduğunu inceleyeceğiz.

Sınıf ve Nesne Nedir?

Sınıf (class), birbirine ait verileri (özellikleri) ve bu veriler üzerinde çalışan metodları tek bir şablon altında toplayan bir yapıdır. Sınıf yalnızca bir tasarımdır; bellekte yer kaplamaz. Nesne (object) ise bu şablondan new() ile üretilen, bellekte gerçekten var olan somut bir örnektir.

Donanım doğrulamada sınıflar; transaction, paket, sürücü (driver), monitör gibi test bileşenlerini modellemek için kullanılır. Sınıflar dinamik olduğundan yalnızca simülasyon ortamında (testbench) yaşarlar, sentezlenmezler.

  • Özellikler (properties): Sınıfın verilerini tutan değişkenlerdir (int id, bit [7:0] data gibi).
  • Metodlar (methods): Sınıfın davranışını tanımlayan function ve task'lardır.
  • Constructor (new): Nesne oluşturulurken çalışan, özellikleri başlatan özel metoddur.

Handle (Referans) Kavramı

SystemVerilog'da bir sınıf değişkeni, nesnenin kendisini değil, nesneye işaret eden bir handle (referans/işaretçi) tutar. Bu, C/C++'daki pointer mantığına benzer ve OOP'yi anlamanın en kritik noktasıdır:

  • Bir sınıf değişkeni tanımlandığında ama new() çağrılmadığında, değeri null olur (henüz bir nesneyi işaret etmez).
  • İki handle aynı nesneye işaret edebilir. Bir handle'ı diğerine atamak nesneyi kopyalamaz, sadece aynı nesneye ikinci bir referans oluşturur.
  • Bu nedenle bir handle üzerinden yapılan değişiklik, aynı nesneyi işaret eden tüm handle'lardan görülür.

Kaynak Kod

// =============================================================================
// GUN 2 - Konu 1: OOP Temelleri - Siniflar (Classes) ve Nesneler (Objects)
// =============================================================================

class Packet;
  // Ozellikler (Properties)
  int    id;
  bit [7:0] data;
  string name;

  // Constructor (Kurucu Metod)
  function new(int id = 0, bit [7:0] data = 0, string name = "default");
    this.id   = id;
    this.data = data;
    this.name = name;
  endfunction

  // Metod: Paketi yazdir
  function void display();
    $display("  Packet: id=%0d, data=0x%02h, name=%s", id, data, name);
  endfunction

endclass

module siniflar_ve_nesneler;
  initial begin
    Packet pkt1, pkt2, pkt3;

    $display("=== Siniflar ve Nesneler ===\n");

    // --- Nesne Olusturma (Instantiation) ---
    $display("--- Nesne Olusturma ---");
    pkt1 = new();                          // Varsayilan degerlerle
    pkt2 = new(1, 8'hAB, "veri_paketi");  // Parametreli
    pkt3 = new(2, 8'hFF, "kontrol");      // Parametreli

    pkt1.display();
    pkt2.display();
    pkt3.display();

    // --- Nesne Ozelliklerine Erisim ---
    $display("\n--- Ozellik Erisimi ---");
    pkt1.id   = 99;
    pkt1.data = 8'hDE;
    pkt1.name = "guncellenmis";
    $display("  Guncellenen pkt1:");
    pkt1.display();

    // --- Handle (Referans) Davranisi ---
    $display("\n--- Handle (Referans) ---");
    begin
      Packet pkt_ref;
      pkt_ref = pkt2;  // Kopyalama degil, ayni nesneye referans
      pkt_ref.data = 8'h00;
      $display("  pkt_ref.data = 0x%02h (degistirildi)", pkt_ref.data);
      $display("  pkt2.data    = 0x%02h (ayni nesne!)", pkt2.data);
    end

    // --- null Kontrolu ---
    $display("\n--- null Kontrolu ---");
    begin
      Packet pkt_null;  // Baslatilmamis -> null
      if (pkt_null == null)
        $display("  pkt_null henuz olusturulmadi (null)");
      
      pkt_null = new(10, 8'hBB, "yeni");
      if (pkt_null != null)
        $display("  pkt_null artik mevcut:");
      pkt_null.display();
    end

    // --- Nesne Dizisi ---
    $display("\n--- Nesne Dizisi ---");
    begin
      Packet pkt_arr [5];
      foreach (pkt_arr[i]) begin
        pkt_arr[i] = new(i, i * 16, $sformatf("pkt_%0d", i));
      end
      foreach (pkt_arr[i])
        pkt_arr[i].display();
    end

    $display("\n=== Siniflar ve Nesneler Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • Packet sınıfı: id, data ve name özelliklerinden oluşur. Bu üç değişken her Packet nesnesinin kendine ait verisidir.
  • new() constructor'ı: Varsayılan değerli argümanlar (id = 0, data = 0, name = "default") alır. this.id = id ifadesi, nesnenin id özelliğine parametre olarak gelen id değerini atar; this ile özellik ve parametre ayrıştırılır.
  • display() metodu: Nesnenin güncel değerlerini ekrana yazdırır. void döndürdüğü için bir değer üretmez.
  • Nesne oluşturma: pkt1 = new() varsayılan değerlerle, pkt2 = new(1, 8'hAB, "veri_paketi") ise parametreli olarak nesne yaratır. Her new() çağrısı bellekte ayrı bir nesne üretir.
  • Özellik erişimi: pkt1.id = 99 gibi nokta operatörü (.) ile nesnenin özelliklerine doğrudan erişilip değer atanabilir.
  • Handle davranışı: pkt_ref = pkt2 satırı pkt2'nin işaret ettiği nesneyi kopyalamaz; pkt_ref ile pkt2 aynı nesneyi gösterir. Bu yüzden pkt_ref.data = 8'h00 yapıldığında pkt2.data da 0x00 olur.
  • null kontrolü: pkt_null tanımlanır ama new() çağrılmadığı için null'dır. if (pkt_null == null) ile bu durum kontrol edilir; ardından new(10, 8'hBB, "yeni") ile gerçek bir nesneye bağlanır.
  • Nesne dizisi: Packet pkt_arr [5] beş adet handle tutar. foreach döngüsünde her eleman için ayrı ayrı new() çağrılır; aksi halde dizinin tüm elemanları null kalırdı.

Önemli Noktalar

  • Bir sınıf değişkeni tanımlamak nesne oluşturmaz. Nesneyi bellekte var etmek için mutlaka new() çağrılmalıdır; aksi halde handle null kalır.
  • null bir handle üzerinden özelliğe veya metoda erişmek (örneğin pkt_null.display() nesne yokken) çalışma zamanı hatasına yol açar. Erişimden önce gerekirse == null kontrolü yapın.
  • Handle ataması (pkt_ref = pkt2) kopya değildir; gerçek bir kopya gerekiyorsa özel bir copy() metodu yazılmalıdır.
  • Nesne dizilerinde dizinin kendisini bildirmek elemanları oluşturmaz; her elemana ayrı new() gerekir.
  • this anahtar kelimesi, parametre adı ile özellik adı çakıştığında özelliği işaret etmek için kullanılır ve kodu okunaklı kılar.
  • SystemVerilog'da çöp toplama (garbage collection) otomatiktir: bir nesneye işaret eden son handle da null olduğunda ya da kapsam dışına çıktığında nesne simülatör tarafından serbest bırakılır. C++'taki gibi delete yoktur; bellek sızıntısı yerine "yanlışlıkla hâlâ bir handle tutuyor olmak" asıl risktir.
  • Sınıflar module içinde, bir package içinde ya da derleme biriminde (compilation unit) tanımlanabilir. Gerçek projelerde sınıflar neredeyse her zaman bir package içinde toplanır; böylece birden fazla modül/dosya aynı sınıfı import ile paylaşır.

Struct ile Class Arasındaki Fark

Gün 1'de struct ile ilişkili verileri gruplamıştık. Peki neden class'a ihtiyaç var?

struct class
Bellek Değişken tanımlandığı anda ayrılır Yalnızca new() çağrıldığında ayrılır
Atama (b = a) Değer kopyalanır (bağımsız iki kopya) Handle kopyalanır (aynı nesne)
Metodlar Yok function / task içerebilir
Kalıtım, polimorfizm Yok extends, virtual ile var
Rastgeleleştirme Yalnızca std::randomize ile rand alanlar + randomize() yerleşik
Sentezlenebilirlik packed ise evet Hayır (yalnızca testbench)
Tipik rol Pin seviyesi veri, bus paketi Transaction, driver, monitor, scoreboard

Benzetme: Sınıf bir ev planı, nesne o plandan inşa edilmiş gerçek bir ev, handle ise evin adresidir. Planı çizmek (sınıfı tanımlamak) arsa kaplamaz; new() evi inşa eder. İki kişiye aynı adresi vermek (pkt_ref = pkt2) iki ev yapmaz, iki kişi aynı eve girer. Adresi olmayan bir kâğıt (null handle) ile kapıyı çalmaya çalışırsanız (pkt_null.display()) simülasyon çöker.

Sık Yapılan Hatalar

  • new() unutmak: Packet p; p.id = 5; yazmak, null handle üzerinden erişim nedeniyle çalışma zamanı hatası verir. Sınıf değişkeni tanımlamak nesne yaratmaz.

  • Döngüde tek nesne yaratıp kuyruğa koymak: Aşağıdaki kod aynı nesneyi 5 kez kuyruğa ekler; son değişiklik hepsini etkiler.

    Packet p = new();
    repeat (5) begin
      p.id++;
      q.push_back(p);   // HATA: ayni handle 5 kez eklendi
    end
    

    Doğrusu her turda p = new(); çağırmaktır.

  • Handle karşılaştırması ile içerik karşılaştırmasını karıştırmak: pkt1 == pkt2 iki handle'ın aynı nesneyi gösterip göstermediğini sorar, alanların eşitliğini değil. İçerik karşılaştırması için bir compare() metodu yazmalısınız (sonraki ders).

  • Dizi tanımlayıp elemanları new etmemek: Packet arr[5]; beş null handle içerir; her elemana ayrı new() gerekir.

Kendinizi Deneyin

  1. pkt_ref = pkt2; satırından sonra pkt_ref = new(7, 8'h77, "yeni"); ekleyin. pkt2.data değişti mi? Neden?
  2. Packet sınıfına function bit is_same_object(Packet other); return (this == other); endfunction metodu ekleyin; pkt1, pkt2 ve pkt_ref ile test edin.
  3. pkt_arr dizisini new() çağırmadan display() etmeyi deneyin ve simülatörün verdiği hata mesajını not alın. Bu mesajı tanımak gerçek projelerde çok zaman kazandırır.
  4. pkt1 = null; yazıp ardından if (pkt1 == null) kontrolü yapın. Nesneye başka handle kalmadıysa ne oldu?
Hızlı Kontrol: Packet a = new(); Packet b = a; b.id = 9; sonrası a.id nedir?

9. Çünkü b = a nesneyi kopyalamaz, yalnızca handle'ı kopyalar; a ve b aynı nesneyi gösterir. Bağımsız kopya için b = a.copy() gibi açık bir kopyalama metodu gerekir.