EDA Playground'da Dene

Method Argüman Yönleri - input, output, inout ve ref Farkı

Gün 2: Method Argüman Yönleri | Yönlerin farkları

Bu derste bir task veya function'a argüman geçerken kullanılan dört yönü (input, output, inout, ref) ve özellikle sınıf handle'ları söz konusu olduğunda aralarındaki davranış farklarını öğreneceğiz. Bu farkları kavramak, doğrulama ortamında nesnelerin beklenmedik şekilde değişmesini (veya değişmemesini) anlamak için kritiktir.

Argüman Yönleri ve Kopyalama Mantığı

Bir handle (sınıf değişkeni) aslında bir nesneye işaret eden adrestir. Bir metoda handle geçtiğinizde, yönün ne olduğu adresin mi yoksa nesnenin mi etkileneceğini belirler. Burada iki ayrı kavramı birbirinden ayırmak gerekir: handle'ın işaret ettiği nesnenin içeriğini değiştirmek (d.value = 10) ile handle'ı başka bir nesneye yönlendirmek (d = new(...)).

  • input: Dışarıdaki my_obj adresini içeriye kopyalar. İçeride d.value = 10 dediğinizde asıl nesneyi değiştirirsiniz (çünkü iki handle aynı adrese bakar). Ama d = new(99) dediğinizde yalnızca içerideki kopya etiketin yönünü değiştirmiş olursunuz; task bitince bu kopya silinir ve dışarıdaki my_obj eski nesneye bakmaya devam eder.

  • output: İçeriye hiçbir bilgi almaz; argüman task'a girerken null'dır. İçeride yeni bir nesne yaratılır (new) ve task bittiği anda bu yeni adres dışarıdaki my_obj'ye yazılır.

  • inout: input ve output'un birleşimidir. Dışarıdaki adresi alır, içeride kullanmanıza izin verir. İçeride handle'ı başka bir nesneye yönlendirirseniz (new(30)), task bittiği an bu yeni adres dışarıya kopyalanır.

  • ref: Kopya yoktur. İçerideki d ile dışarıdaki my_obj kelimenin tam anlamıyla aynı değişkendir. İçeride handle'a new() atadığınız an, dışarıdaki değişken de anında o yeni nesneye bakmaya başlar (task'ın bitmesini bile beklemez). Adres kopyalama bile yapılmadığı için en hızlısıdır.

Benzetme: Nesne bir ev, handle ise evin adresinin yazılı olduğu kâğıt olsun. input: adresin fotokopisini veririm; eve gidip içini değiştirebilirsin ama fotokopiye başka adres yazman benim kâğıdımı etkilemez. output: boş bir kâğıt verirsin, ben üzerine yeni bir adres yazıp geri veririm. inout: fotokopimi verirsin, ben üstünü silip yeni adres yazarım ve geri verdiğimde sen de o adresi kullanırsın. ref: bana kâğıdın kendisini verirsin; üzerine yazdığım anda elindeki değişmiş olur.

Yön Girişte dışarıdaki handle görülür mü? Nesne içeriği değişebilir mi? d = new() dışarıya yansır mı? Ne zaman yansır?
input Evet (kopya) Evet Hayır —
output Hayır (null başlar) — Evet Metot bitiminde
inout Evet (kopya) Evet Evet Metot bitiminde
ref Evet (aynı değişken) Evet Evet Anında

Kaynak Kod

// =============================================================================
// GUN 2 Konu 2.1 - ARGÜMAN YÖNLERİ: input vs output vs inout vs ref
// =============================================================================

class Dummy;
  int value;
  
  // Kurucu Metot (Constructor)
  function new(int v = 0);
    value = v;
  endfunction
endclass

module arg_directions_demo;

  // 1. INPUT: İçeri giren handle'ı (işaretçiyi) kopyalar. 
  // Çağrıldığı yerdeki asıl handle'ın işaret ettiği yeri değiştiremez, 
  // ancak nesnenin İÇERİĞİNİ değiştirebilir.
  task automatic test_input(input Dummy d);
    // Nesnenin içeriğini değiştirmek dışarıdan görünür
    d.value = 10; 
    
    // Handle'ın kendisini değiştirmek dışarıdan GÖRÜNMEZ
    d = new(99); 
  endtask

  // 2. OUTPUT: Başlangıçta null'dır. Task bittiğinde handle'ı DIŞARI kopyalar.
  task automatic test_output(output Dummy d);
    // Task bittikten sonra dışarıdaki handle bu yeni nesneyi işaret edecektir
    d = new(20); 
  endtask

  // 3. INOUT: Handle'ı İÇERİ kopyalar ve task bittiğinde DIŞARI kopyalar.
  task automatic test_inout(inout Dummy d);
    // Gelen nesneyi değiştirir
    d.value = 35; 
    
    // Yeni bir nesneye işaret eder, task bittikten sonra dışarısı bu yeni adresi görecektir
    d = new(30); 
  endtask

  // 4. REF: Handle'ın GERÇEK hafıza referansını iletir (kopyalama yapılmaz).
  // Handle üzerindeki herhangi bir değişiklik anında dışarıdan görünür.
  task automatic test_ref(ref Dummy d);
    // Dışarıdaki handle anında bu yeni nesneyi işaret etmeye başlar
    d = new(40); 
  endtask

  initial begin
    Dummy my_obj;

    $display("=== Argüman Yönleri Demosu ===\n");

    // --- INPUT ---
    my_obj = new(1);
    test_input(my_obj);
    // my_obj.value 99 DEĞİL, 10 olacaktır. 'new(99)' task bittiğinde silindi.
    $display("[INPUT]  test_input sonrasi : my_obj.value = %0d", my_obj.value); 

    // --- OUTPUT ---
    // my_obj adresi tamamen üzerine yazılır.
    test_output(my_obj);
    $display("[OUTPUT] test_output sonrasi: my_obj.value = %0d", my_obj.value); 

    // --- INOUT ---
    test_inout(my_obj);
    // my_obj artık test_inout içinde oluşturulan yeni nesneyi işaret ediyor.
    $display("[INOUT]  test_inout sonrasi : my_obj.value = %0d", my_obj.value); 

    // --- REF ---
    test_ref(my_obj);
    // my_obj anında doğrudan değiştirildi.
    $display("[REF]    test_ref sonrasi   : my_obj.value = %0d", my_obj.value); 

    $finish;
  end
endmodule

Kodun Açıklaması

  • Dummy sınıfı: Tek bir value özelliği ve onu başlatan new(int v = 0) constructor'ı içerir. Yön farklarını gözlemlemek için sade bir taşıyıcı olarak kullanılır.
  • test_input: d.value = 10 satırı gelen nesnenin içeriğini değiştirir ve bu dışarıdan görünür. Ancak d = new(99) yalnızca içerideki yerel handle'ı yeni bir nesneye yönlendirir; task bitince bu kopya kaybolur. Bu yüzden çıktıda my_obj.value 99 değil 10'dur.
  • test_output: Argüman task'a girerken null kabul edilir; içeride hiçbir giriş bilgisi kullanılmaz. d = new(20) ile oluşturulan yeni nesne, task bittiğinde dışarıdaki my_obj'ye kopyalanır. Sonuç my_obj.value = 20 olur.
  • test_inout: Hem girişte hem çıkışta kopyalama yapar. d.value = 35 gelen nesneyi değiştirir, ama sonra d = new(30) ile yeni bir nesneye geçilir. Task bittiğinde dışarı kopyalanan bu son handle olduğundan my_obj.value = 30 olur.
  • test_ref: ref ile kopya yapılmaz; içerideki d ile dışarıdaki my_obj aynı değişkendir. d = new(40) çağrıldığı anda my_obj da yeni nesneyi işaret etmeye başlar; task'ın bitmesini beklemez. Sonuç my_obj.value = 40 olur.
  • automatic anahtar kelimesi: Tüm task'lar automatic olarak tanımlanmıştır; bu, her çağrıda argümanların ve yerel değişkenlerin kendi kopyasının oluşturulmasını sağlar (özyineleme ve yeniden giriş güvenliği için gereklidir).

Önemli Noktalar

  • input ile gelen bir handle üzerinden nesnenin içeriğini değiştirebilirsiniz, fakat handle'ı yeni bir nesneye yönlendirmeniz dışarıyı etkilemez.
  • output argümanına metoda girerken güvenmeyin; başlangıçta null kabul edilir ve yalnızca metot bitiminde dışarı yazılır.
  • inout ve output, değeri metot bittiğinde dışarı kopyalar; bu nedenle değişiklik metot sırasında değil, dönüşte görünür.
  • ref en hızlısıdır (adres kopyalama bile yapılmaz) ve değişiklikler anında dışarıya yansır; ancak bu güç, istenmeyen yan etkilere de yol açabileceği için dikkatli kullanılmalıdır.
  • Yalnızca okumak için bir nesneyi geçiyorsanız ve onu değiştirmeyecekseniz const ref tercih edilebilir; bu hem performans sağlar hem de kazara değişikliği önler.
  • ref argümanlar yalnızca automatic metodlarda kullanılabilir; statik bir modül task'ında ref yazmak derleme hatasıdır. Koddaki tüm task'ların automatic olmasının bir nedeni de budur.
  • Bu kurallar yalnızca handle'lar için değil, büyük diziler ve kuyruklar için de geçerlidir: 10.000 elemanlı bir kuyruğu input ile geçmek tüm kuyruğu kopyalar; ref (veya const ref) ile geçmek kopyasızdır. Performans farkı büyük testbench'lerde belirgindir.

Sık Yapılan Hatalar

  • input ile gelen handle'a new atayıp dışarının değişmesini beklemek: En klasik hata. Dışarı etkilemek istiyorsanız inout veya ref kullanın ya da nesneyi return edin.
  • output argümanı içeride okumak: output argümanı girişte null'dır; d.value okumaya çalışmak null-handle hatası verir.
  • ref ile geçilen kuyruğu döngü içinde değiştirmek: ref anlık yansıdığı için, çağıran taraf aynı kuyruğu paralel bir süreçte geziyorsa indeksler kayar. const ref ile niyetinizi netleştirin.
  • Varsayılan yönü unutmak: Yön yazılmazsa argüman input'tur. task t(Dummy d) ile task t(input Dummy d) aynıdır.

Kendinizi Deneyin

  1. test_input içindeki d = new(99); satırından önce d.value = 10; yerine sonra yazın. my_obj.value ne oldu? Neden?
  2. test_output task'ında d = new(20); satırından önce $display("%0d", d.value); eklemeyi deneyin. Hangi hatayı aldınız?
  3. task automatic fill(ref int q[$]); q.push_back(1); endtask yazıp bir kuyrukla çağırın; ardından ref'i input yapıp farkı gözlemleyin.
  4. automatic anahtar kelimesini test_ref'ten silin ve derleyicinin ne dediğini not alın.
Hızlı Kontrol: input Dummy d ile çağrılan bir task içinde d.value = 10 dışarıyı etkiler mi?

Evet. input handle'ın kopyasını taşır ama kopya da aynı nesneyi gösterir; nesnenin içeriğini değiştirmek dışarıdan görülür. Etkilemeyen şey, d = new(...) ile handle'ı başka bir nesneye yönlendirmektir.