EDA Playground'da Dene

Kalıtım (Inheritance)

Gün 2: Nesne Yönelimli Programlama (OOP) | Sınıf hiyerarşisi, super anahtar kelimesi, metod genişletme

Bu derste kalıtımı (inheritance) öğreneceğiz: Var olan bir sınıftan yeni sınıflar türeterek kodu yeniden kullanmayı, extends ile sınıf hiyerarşisi kurmayı ve super anahtar kelimesiyle üst sınıfın yapısına erişmeyi inceleyeceğiz.

Kalıtım Nedir?

Kalıtım, bir sınıfın (alt/türetilmiş sınıf) başka bir sınıfın (üst/temel sınıf) tüm özelliklerini ve metodlarını devralmasıdır. SystemVerilog'da bu, class AltSinif extends UstSinif sözdizimiyle yapılır. Alt sınıf, devraldığı yapının üzerine yeni alanlar ve metodlar ekleyebilir.

Kalıtımın doğrulamadaki en büyük faydası, ortak davranışı tek bir temel sınıfta toplayıp (örneğin genel bir Transaction), özelleşmiş türleri (okuma, yazma, hatalı işlem) bu temelden türetmektir. Bu hem tekrar eden kodu azaltır hem de polimorfizmin önünü açar.

  • Temel (base) sınıf: Ortak alan ve metodları içerir.
  • Türetilmiş (derived) sınıf: Temeli devralır, kendine özgü alanlar ekler ve gerektiğinde metodları yeniden tanımlar.
  • Çok seviyeli kalıtım: Bir alt sınıftan tekrar türetme yapılabilir (örneğin temel → yazma → hatalı yazma).

super Anahtar Kelimesi

super, alt sınıf içinden üst sınıfın üyelerine ve constructor'ına erişmeyi sağlar. En kritik kullanımı, alt sınıfın constructor'ında super.new(...) çağrısıdır: Alt sınıf, kendi alanlarını başlatmadan önce üst sınıfın doğru biçimde kurulmasını garanti etmelidir.

Kaynak Kod

// =============================================================================
// GUN 2 - Konu 5: Kalitim (Inheritance) ve super Anahtar Kelimesi
// =============================================================================

// Temel (Base) Sinif
class Transaction;
  int    id;
  int    data;
  string txn_type;
  
  static int count = 0;

  function new(int id = 0, int data = 0);
    this.id   = id;
    this.data = data;
    this.txn_type = "BASE";
    count++;
  endfunction

  function void display();
    $display("  [%s] ID=%0d, Data=0x%08h", txn_type, id, data);
  endfunction

  function bit is_valid();
    return (data != 0);
  endfunction
endclass

// Alt Sinif 1: Okuma Islemi
class ReadTransaction extends Transaction;
  logic [31:0] address;
  int          burst_length;

  function new(int id, logic [31:0] addr, int burst = 1);
    super.new(id, 0);             // Ust sinif constructor'i cagir
    this.address      = addr;
    this.burst_length = burst;
    this.txn_type     = "READ";
  endfunction

  function void display();
    $display("  [READ]      Addr=0x%08h, Burst=%0d", address, burst_length);
  endfunction
endclass

// Alt Sinif 2: Yazma Islemi
class WriteTransaction extends Transaction;
  logic [31:0] address;
  logic [31:0] write_data;
  logic [3:0]  byte_enable;

  function new(int id, logic [31:0] addr, logic [31:0] wdata, logic [3:0] be = 4'hF);
    super.new(id, wdata);
    this.address     = addr;
    this.write_data  = wdata;
    this.byte_enable = be;
    this.txn_type    = "WRITE";
  endfunction

  function void display();
    $display("  [WRITE]     Addr=0x%08h, WData=0x%08h, BE=0x%01h",
             address, write_data, byte_enable);
  endfunction
endclass

// Alt-Alt Sinif: Hatali Yazma (Hata Enjeksiyonu)
class ErrorWriteTransaction extends WriteTransaction;
  bit inject_addr_error;
  bit inject_data_error;

  function new(int id, logic [31:0] addr, logic [31:0] wdata);
    super.new(id, addr, wdata);
    this.txn_type = "ERR_WRITE";
    this.inject_addr_error = 0;
    this.inject_data_error = 0;
  endfunction

  function void corrupt();
    if (inject_addr_error) begin
      address ^= 32'hDEAD_0000;
      $display("  [!] Adres bozuldu: 0x%08h", address);
    end
    if (inject_data_error) begin
      write_data ^= 32'h0000_BEEF;
      $display("  [!] Veri bozuldu: 0x%08h", write_data);
    end
  endfunction

  function void display();
    $display("        Hata Enj.: addr=%0b, data=%0b",
             inject_addr_error, inject_data_error);
  endfunction
endclass

module kalitim;
  initial begin
    Transaction          base_txn;
    ReadTransaction      rd_txn;
    WriteTransaction     wr_txn;
    ErrorWriteTransaction err_txn;

    $display("=== Kalitim (Inheritance) ===\n");

    // --- Nesne olusturma ---
    $display("--- Hiyerarsik Nesneler ---");
    base_txn = new(0, 16'h0000);
    rd_txn   = new(1, 32'hA000_0000, 4);
    wr_txn   = new(2, 32'hB000_0000, 32'hCAFE_BABE);
    err_txn  = new(3, 32'hC000_0000, 32'h1234_5678);

    base_txn.display();
    rd_txn.display();
    wr_txn.display();

    // --- super kullanimi (zaten constructor'da) ---
    $display("\n--- Hata Enjeksiyonu ---");
    err_txn.inject_addr_error = 1;
    err_txn.inject_data_error = 1;
    $display("  Bozulmadan once:");
    err_txn.display();
    err_txn.corrupt();
    $display("  Bozulduktan sonra:");
    err_txn.display();

    // --- Statik sayac (tum nesneler arasinda paylasilir) ---
    $display("\n--- Statik Sayac ---");
    $display("  Toplam Transaction sayisi = %0d", Transaction::count);

    // --- Ust sinif referansi ile alt sinif nesnesi ---
    $display("\n--- Ust Sinif Referansi ---");
    $display("  base_txn.is_valid() = %0b", base_txn.is_valid());// Başta data'sı 0'dı
    base_txn = wr_txn;  // WriteTransaction -> Transaction (gecerli)
    $display("  base_txn.is_valid() = %0b", base_txn.is_valid());

    $display("\n=== Kalitim Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • Temel Transaction sınıfı: id, data, txn_type alanlarını ve static int count sayacını içerir. display() ve is_valid() ortak metodlarını sunar. Her new() çağrısında count++ ile toplam işlem sayısı artar.
  • ReadTransaction extends Transaction: Temeli devralır ve address ile burst_length alanlarını ekler. Constructor'da super.new(id, 0) ile üst sınıf kurulur, ardından kendi alanları ve txn_type = "READ" ayarlanır.
  • WriteTransaction extends Transaction: Benzer şekilde address, write_data, byte_enable ekler ve super.new(id, wdata) ile veriyi temel sınıfa iletir.
  • ErrorWriteTransaction extends WriteTransaction: İki seviyeli kalıtım örneğidir; WriteTransaction'dan türer. inject_addr_error/inject_data_error bayraklarını ekler. corrupt() metodu, bayraklar açıksa address ve write_data alanlarını XOR (^=) ile bozar.
  • super.new(...) zinciri: ErrorWriteTransaction::new, WriteTransaction::new'i çağırır, o da Transaction::new'i çağırır. Böylece tüm hiyerarşi sırayla başlatılır.
  • Statik sayaç paylaşımı: Transaction::count, hangi alt sınıftan olursa olsun oluşturulan tüm nesneleri sayar; çünkü count temel sınıfa ait static bir değişkendir.
  • Üst sınıf referansı: base_txn = wr_txn; satırı geçerlidir çünkü WriteTransaction bir Transaction'dır (upcasting). Atamadan sonra base_txn.is_valid(), wr_txn'in verisi 0 olmadığı için 1 döner.

Önemli Noktalar

  • Alt sınıfın constructor'ında, kendi alanlarınızı atamadan önce super.new(...) çağrısı yapın; birçok durumda bu zorunludur ve hiyerarşinin doğru başlatılmasını sağlar.
  • static üyeler temel sınıfta tanımlandığında tüm alt sınıf nesneleri tarafından paylaşılır; bu, tüm hiyerarşi genelinde sayım/istatistik için kullanışlıdır.
  • Bir alt sınıf nesnesi her zaman üst sınıf tipindeki bir handle'a atanabilir (upcasting); tersi (downcasting) için $cast gerekir.
  • Bu örnekteki display() metodları virtual değildir; bu nedenle base_txn = wr_txn sonrasında base_txn.display() çağrılsaydı temel sınıfın sürümü çalışırdı. Çalışma zamanında nesnenin gerçek tipine göre metod seçilmesi için virtual gerekir (sonraki ders: Polimorfizm).
  • Çok seviyeli kalıtım güçlüdür ama hiyerarşiyi gereğinden derin tutmak okunabilirliği düşürür; ortak davranışı doğru seviyede toplamaya özen gösterin.
  • Alt sınıfta super.new(...) yazmazsanız SystemVerilog örtük olarak super.new() çağırır (argümansız). Üst sınıfın constructor'ı zorunlu argüman istiyorsa bu derleme hatasına yol açar; bu yüzden pratikte super.new(...)'i her zaman açıkça yazın.
  • super.new(...) çağrısı constructor'ın ilk ifadesi olmalıdır; öncesine başka bir atama koyamazsınız.
  • Üst sınıfta aynı isimle tanımlanan virtual olmayan bir metodu alt sınıfta yeniden yazmak "gizleme" (hiding) olarak adlandırılır: hangi sürümün çalışacağı handle'ın tipine bağlıdır. Bu örnekte rd_txn.display() READ sürümünü çalıştırır ama base_txn = rd_txn; base_txn.display(); temel sürümü çalıştırır. Bu tuzak sonraki dersin konusudur.

Kalıtım Hiyerarşisi

Transaction            (id, data, txn_type, static count)
 ├── ReadTransaction   (+ address, burst_length)
 └── WriteTransaction  (+ address, write_data, byte_enable)
      └── ErrorWriteTransaction (+ inject_addr_error, inject_data_error, corrupt())

ErrorWriteTransaction nesnesi üç katmanlı bir soğan gibidir: en içte Transaction'ın alanları, ortada WriteTransaction'ın eklediği alanlar, en dışta kendi bayrakları. super.new zinciri bu katmanları içten dışa sırayla kurar.

Benzetme: Kalıtım, bir form şablonunu temel alıp üzerine yeni alanlar eklemektir. "Genel başvuru formu" (Transaction) ad ve numara ister; "burs başvurusu" (ReadTransaction) aynı formu alır, üstüne not ortalaması ekler. Burs formunu dolduran kişi genel formun alanlarını yeniden yazmaz; onlar miras gelir.

"is-a" Testi: Kalıtım Ne Zaman Doğru Seçim?

Alt sınıf, üst sınıfın bir türü olmalıdır: "ReadTransaction bir Transaction'dır" cümlesi doğru → kalıtım uygun. "Bir Driver bir Transaction'dır" cümlesi anlamsız → kalıtım değil, içerme (composition: Driver içinde Transaction handle'ı) kullanılmalı. Bu basit test, yanlış hiyerarşilerin çoğunu daha tasarım aşamasında eler.

Sık Yapılan Hatalar

  • super.new argümanlarını yanlış vermek: ReadTransaction içinde super.new(id, 0) çağrısı data'yı 0 yapar; is_valid() bu yüzden READ işlemlerinde 0 döner. Üst sınıfın alanlarının alt sınıf için ne anlama geldiğini bilinçli kararlaştırın.
  • Alt sınıfta üst sınıf alanını yeniden tanımlamak: ReadTransaction içine tekrar int id; yazmak iki ayrı id yaratır (biri üst, biri alt). Hata vermez ama kafa karıştırır.
  • Derin hiyerarşi hastalığı: 5–6 seviyeli kalıtım zincirleri, bir alanın nerede tanımlandığını bulmayı zorlaştırır. İki–üç seviye genellikle yeterlidir.
  • Statik sayacı alt sınıfa özel sanmak: Transaction::count tüm alt sınıf nesnelerini sayar. Yalnızca READ'leri saymak isterseniz ReadTransaction içine ayrı bir static int read_count eklemelisiniz.

Kendinizi Deneyin

  1. base_txn = rd_txn; base_txn.display(); satırlarını ekleyin. Hangi display() çalıştı? Sonraki dersi okumadan nedenini tahmin edin.
  2. ReadTransaction'a static int read_count = 0; ekleyip constructor'da artırın. Transaction::count ile ReadTransaction::read_count değerlerini karşılaştırın.
  3. ErrorWriteTransaction::new içinden super.new(...) satırını silin. Derleyici ne diyor? Neden?
  4. Transaction'a virtual function string to_string(); ekleyin ve her alt sınıfta kendi alanlarını da içeren bir sürüm yazın (önümüzdeki dersin ön hazırlığı).
Hızlı Kontrol: WriteTransaction wr = new(...); Transaction t = wr; geçerli mi? Tersi geçerli mi?

İlki geçerlidir (upcasting): bir WriteTransaction aynı zamanda bir Transaction'dır. Tersi (WriteTransaction w2 = t;) derleme hatasıdır; çünkü t herhangi bir Transaction türünü gösteriyor olabilir. Aşağıya dönüştürmek için $cast(w2, t) gerekir (sonraki ders).