EDA Playground'da Dene

Mailbox

Gün 4: Süreçler Arası İletişim (IPC) | Bileşenler arası veri aktarımı: put, get, peek, try_get

Bu derste SystemVerilog'un yerleşik mailbox sınıfını öğreneceksiniz. Olaylar (events) sadece "bir şey oldu" sinyali taşırken, mailbox gerçek veriyi bir süreçten diğerine güvenli biçimde aktaran bir FIFO kuyruğudur ve doğrulama ortamlarının bel kemiğidir.

01 — POSTA KUTUSU

İki süreç, tek kutu, sıfır yarış.

Mailbox veriyi taşıyan FIFO'dur. En güzel yanı: bloklama. Kutu doluysa üretici, boşsa tüketici otomatik bekler.

put · get

Bloklayan uçlar

put() doluysa, get() boşsa süreci uyutur; hız farkı kendiliğinden dengelenir.

peek · num

Bakmak ≠ almak

peek() kopyasını okur, elemanı çıkarmaz. num() içerideki sayıyı verir.

try_get

Bloklamadan dene

try_get() boşsa beklemez, 0 döner; sürecin başka iş yapabilir.

02 — ÜRETİCİ / TÜKETİCİ LABORATUVARI

Hızları sen belirle, blokajı izle.

Kapasiteyi ve iki tarafın hızını değiştir. Zaman çizelgesinde turuncu alanlar, sürecin beklediği (bloklu) anlardır.

MAILBOX
ALMA
SENARYO
t = 0 ns

PRODUCERSystemVerilog

CONSUMERSystemVerilog

DURUMcanlı

SİMÜLATÖR ÇIKTISI$display

03 — MEYDAN OKUMA

Simülasyon bitene kadar num() en fazla kaç olur?


    
SERİ 0 · EN İYİ 0
04 — ÖZET

Aklında kalsın.

Mailbox = veri taşıyan FIFOEvent sadece "oldu" der; mailbox gerçek transaction'ı taşır.
put() doluysa bloklarYalnızca new(N) ile sınırlı mailbox'ta; sınırsızda asla beklemez.
get() boşsa bloklarTüketici, veri gelene kadar uyur; polling yazmana gerek yok.
try_get() bloklamazBaşarıyı 1/0 döndürür; başka iş yapmak istediğinde kullan.
peek() çıkarmazSadece kopyasını okur; num() değişmez.
Sınır = akış kontrolüHızlı üretici, yavaş tüketiciyi ezemez: kapasite dolunca üretici otomatik yavaşlar.

Mailbox Nedir?

mailbox, süreçler arasında veri taşımak için kullanılan, FIFO (ilk giren ilk çıkar) mantığıyla çalışan dahili bir sınıftır. İçine nesne veya basit tip konulabilir; özellikle mailbox #(tip) parametreli (type-parameterized) biçimi, yalnızca belirtilen tipte veri kabul ederek tip güvenliği sağlar.

Temel metotları şunlardır:

  • put(veri) → Mailbox'a veri koyar. Mailbox sınırlıysa (bounded) ve doluysa, yer açılana kadar bloklar (bekler).
  • get(degisken) → FIFO sırasıyla bir eleman alır ve mailbox'tan çıkarır. Boşsa veri gelene kadar bloklar.
  • peek(degisken) → Elemanı çıkarmadan kopyasını okur; mailbox'ın içeriği değişmez.
  • try_get(degisken) → Bloklamadan denemeli alış yapar; başarılıysa 1, mailbox boşsa 0 döner.
  • num() → Mailbox'taki eleman sayısını döndürür.

Bounded vs Unbounded ve Kullanım Yerleri

  • Sınırsız (unbounded): new() ile oluşturulur; kapasitesi sınırsızdır, put asla bloklamaz.
  • Sınırlı (bounded): new(N) ile oluşturulur; en fazla N eleman alır. Dolduğunda put bekler. Bu, gerçek donanımdaki sınırlı tampon (buffer) davranışını modellemek ve akış kontrolü (flow control) sağlamak için idealdir.

Mailbox, klasik üretici-tüketici (producer-consumer) kalıbının doğal aracıdır: bir generator transaction üretip mailbox'a koyar, bir driver bunları alıp işler. İki taraf birbirinin hızını bilmek zorunda kalmadan, bloklama davranışı sayesinde otomatik olarak senkronize olur.

Kaynak Kod

// =============================================================================
// GUN 4 - Konu 4: Mailbox - Bilesenler Arasi Veri Aktarimi
// =============================================================================

class Transaction;
  int id;
  int data;
  string source;

  function new(int id, int data, string source = "unknown");
    this.id = id;
    this.data = data;
    this.source = source;
  endfunction

  function void display();
    $display("      TXN #%0d: data=%0d, source=%s", id, data, source);
  endfunction
endclass

module mailbox_ornek;

  mailbox #(Transaction) mbx_bounded;    // Sinirli mailbox
  mailbox #(Transaction) mbx_unbounded;  // Sinirsiz mailbox
  mailbox #(int)         mbx_simple;     // Basit tip mailbox

  // --- Basit Mailbox: put ve get ---
  initial begin
    $display("=== Mailbox Kullanimi ===\n");

    $display("--- Basit Mailbox (int) ---");
    mbx_simple = new();  // Sinirsiz
    
    // put: Veri koy
    mbx_simple.put(10);
    mbx_simple.put(20);
    mbx_simple.put(30);
    $display("  3 eleman eklendi, boyut = %0d", mbx_simple.num());
    
    // get: Veri al (FIFO)
    begin
      int val;
      mbx_simple.get(val);
      $display("  get() = %0d, boyut = %0d", val, mbx_simple.num());
      mbx_simple.get(val);
      $display("  get() = %0d, boyut = %0d", val, mbx_simple.num());
    end
    
    // peek: Almadan bak
    begin
      int val;
      mbx_simple.peek(val);
      $display("  peek() = %0d, boyut = %0d (degismedi)", val, mbx_simple.num());
    end

    // try_get: Bloklama yapmadan al
    begin
      int val;
      if (mbx_simple.try_get(val))
        $display("  try_get() = %0d", val);
      if (!mbx_simple.try_get(val))
        $display("  try_get() basarisiz (bos mailbox)");
    end
  end

  // --- Sinirli Mailbox ile Producer-Consumer ---
  initial begin
    #50;
    $display("\n--- Sinirli Mailbox (bounded=3) ---");
    mbx_bounded = new(3);  // Maks 3 eleman

    fork
      // Producer
      begin : producer
        for (int i = 0; i < 6; i++) begin
          Transaction t;
          t = new(i, $urandom_range(100,999), "Producer");
          $display("  [%0t] Producer: #%0d gonderiyor... (kuyruk=%0d)",
                   $time, i, mbx_bounded.num());
          mbx_bounded.put(t);  // Dolu ise bekler (blocking)
          $display("  [%0t] Producer: #%0d gonderildi", $time, i);
          #5;
        end
      end

      // Consumer (yavas)
      begin : consumer
        Transaction t;
        repeat (6) begin
          #15;  // Yavas tuketim
          mbx_bounded.get(t);
          $display("  [%0t] Consumer: alindi ->", $time);
          t.display();
        end
      end
    join

    $display("  [%0t] Producer-Consumer tamamlandi", $time);
  end

  // --- Sinirsiz Mailbox ile coklu tuketici ---
  initial begin
    #250;
    $display("\n--- Coklu Tuketici ---");
    mbx_unbounded = new();

    fork
      // Uretici
      begin
        for (int i = 0; i < 10; i++) begin
          Transaction t;
          t = new(i, i*100, "Gen");
          mbx_unbounded.put(t);
          #3;
        end
      end

      // Tuketici 1
      begin
        Transaction t;
        repeat (5) begin
          mbx_unbounded.get(t);
          $display("  [%0t] Tuketici-1: TXN#%0d (data=%0d)", $time, t.id, t.data);
          #7;
        end
      end

      // Tuketici 2
      begin
        Transaction t;
        repeat (5) begin
          mbx_unbounded.get(t);
          $display("  [%0t] Tuketici-2: TXN#%0d (data=%0d)", $time, t.id, t.data);
          #10;
        end
      end
    join

    $display("  [%0t] Coklu tuketici tamamlandi", $time);
    #10;
    $display("\n=== Mailbox Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • Transaction sınıfı: İçinde id, data ve source alanları bulunur; display() metodu bunları okunaklı biçimde yazdırır. Mailbox'ların nesne taşıma yeteneğini göstermek için kullanılır.
  • Üç mailbox bildirimi: mbx_simple basit int tipi taşır, mbx_bounded sınırlı (new(3)) ve mbx_unbounded sınırsız Transaction mailbox'larıdır.
  • Basit mailbox bölümü: mbx_simple sırasıyla put(10), put(20), put(30) ile doldurulur ve num() ile boyut 3 olarak gösterilir. Ardından get(val) ile FIFO sırayla 10 ve 20 alınır. peek(val) bir sonraki elemanı (30) çıkarmadan okur; bu yüzden boyut değişmez. Son olarak try_get, önce başarılı olur (30'u alır), ikinci çağrıda mailbox boş olduğu için bloklamadan başarısız döner.
  • Sınırlı mailbox (producer-consumer): #50 zamanında başlar. mbx_bounded = new(3) ile kapasite 3'tür. producer 6 transaction üretip put eder, ama mailbox dolduğunda bekler; consumer ise #15 aralıklarla yavaşça get yapar. Bu, sınırlı tamponun nasıl akış kontrolü sağladığını gösterir: hızlı üretici, yavaş tüketici yer açana kadar bloklanır. join ile her ikisinin bitmesi beklenir.
  • Çoklu tüketici bölümü: #250 zamanında çalışır. Sınırsız mbx_unbounded'a tek bir üretici 10 transaction koyarken, iki tüketici (Tuketici-1 ve Tuketici-2) aynı mailbox'tan paralel get yapar. Aynı kaynaktan birden fazla tüketicinin nasıl iş paylaştığını (load balancing) örnekler.

Önemli Noktalar

  • put ve get bloklayıcı (blocking) metotlardır: dolu bir bounded mailbox'a put, boş bir mailbox'tan get bekler. Bu otomatik bekleme, üretici-tüketici senkronizasyonunu kendiliğinden sağlar.
  • Bloklamadan çalışmak istediğinizde try_get (ve benzeri try_put/try_peek) kullanın; dönüş değerini mutlaka kontrol edin.
  • peek, elemanı kuyrukta bırakır; aynı veriye birden fazla kez bakmak veya almadan incelemek için kullanışlıdır.
  • Sınırlı mailbox (new(N)) gerçek donanım tamponlarını modellemek ve test sırasında geri-basınç (backpressure) davranışını doğrulamak için tercih edilir.
  • mailbox #(Transaction) gibi parametreli kullanım tip güvenliği sağlar; yanlış tipte veri konmasını derleme zamanında engeller. Parametresiz mailbox ise tip denetimsizdir ve önerilmez.
  • Çoklu tüketici senaryosunda her transaction yalnızca tek bir tüketici tarafından get edilir; aynı veriyi birden çok tüketiciye dağıtmak için ayrı mailbox'lar veya yayın (broadcast) mekanizması gerekir. UVM'de bu ihtiyacı uvm_analysis_port karşılar: tek write() çağrısı bağlı tüm dinleyicilere ulaşır.
  • Mailbox nesne handle'ı taşır, nesneyi kopyalamaz. Üretici put(t) sonrasında t.data = 0; yazarsa tüketici de değişmiş veriyi görür. Üretici aynı nesneyi yeniden kullanacaksa put(t.copy()) göndermeli ya da her turda yeni nesne yaratmalıdır (örnekte t = new(...) döngü içindedir, bu yüzden güvenlidir).
  • mailbox aslında bir sınıftır: new() ile yaratılır, handle ile taşınır, null olabilir. Bileşenler aynı mailbox'ı paylaşmak için aynı handle'ı almalıdır (Lab 4'te bunu göreceksiniz).

Mailbox mı, Kuyruk mu?

mailbox Kuyruk ([$])
Süreçler arası güvenli mi? ✅ get/put bloklar ve atomiktir ❌ Yarış koşullarına açık, elle senkron gerekir
Boşken okuma get bekler pop_front uyarı verir, varsayılan değer döner
Doluyken yazma (sınırlı) put bekler (geri-basınç) Fazla eleman düşürülür
Rastgele erişim / arama ❌ Yalnızca baştan get/peek ✅ q[i], find with, sort
Tipik rol Bileşenler arası kanal Tek bileşen içinde depo (scoreboard listesi)

Pratik kural: iki süreç arasında veri akıyorsa mailbox; tek bir sürecin içinde veri saklanıyorsa kuyruk.

Benzetme: Mailbox, kapıdaki posta kutusudur. Postacı (üretici) mektubu bırakır; kutu doluysa (sınırlı mailbox) kapıda bekler. Siz (tüketici) kutuya bakarsınız; mektup yoksa get ile kapı önünde beklersiniz, try_get ile "yoksa dönerim" dersiniz, peek ile mektubu almadan zarfın üstünü okursunuz. Kutu, kimin ne zaman geldiğini dert etmeden iki tarafı buluşturur.

Sık Yapılan Hatalar

  • Parametresiz mailbox kullanmak: mailbox mbx = new(); her tipi kabul eder; yanlış tip get edildiğinde çalışma zamanı hatası alırsınız. Daima mailbox #(Tip).
  • Aynı nesneyi tekrar tekrar put etmek: Tüketicideki tüm paketler aynı nesneyi gösterir; "hepsi son değere dönüştü" şikâyetinin kaynağı budur.
  • get ile sonsuz beklemek: Üretici bitmiş, tüketici forever get() döngüsünde kalmıştır. Simülasyonun bitmesi için ya $finish ya da join_any + bitiş olayı gerekir (Lab 4).
  • Sınırlı mailbox'ta geri-basıncı unutmak: put bloklandığı için üretici "takıldı" görünür; bu bir hata değil, akış kontrolüdür. try_put ile bloklamadan deneyebilirsiniz.
  • num()'a güvenerek get yapmak: Çok tüketicili ortamda if (mbx.num() > 0) mbx.get(t); bile yarışa açıktır: iki tüketici aynı anda num() > 0 görür, biri alır, diğeri bloklanır. try_get tek adımda ve güvenlidir.

Kendinizi Deneyin

  1. Sınırlı mailbox örneğinde kapasiteyi new(1) yapın. Producer'ın hangi anlarda beklediğini [%0t] damgalarından takip edin; new(3) ile karşılaştırın.
  2. Producer'daki #5; ile Consumer'daki #15; değerlerini yer değiştirin (hızlı tüketici, yavaş üretici). Bu kez kim kimi bekliyor?
  3. Çoklu tüketici örneğinde üretici t'yi döngü dışında bir kez yaratıp her turda yalnızca t.id = i; güncellesin. Tüketicilerin bastığı TXN# değerleri ne oldu? Neden?
  4. mbx_simple üzerinde try_peek ve try_put metotlarını deneyin; sınırlı bir mailbox'ta try_put dolu kutuya ne döndürüyor?
Hızlı Kontrol: Boş bir mailbox'ta get() ve try_get() nasıl davranır?

get() veri gelene kadar bloklar (süreç bekler). try_get() ise hiç beklemez; mailbox boşsa hemen 0 döndürür, doluysa veriyi alıp 1 döndürür. Dönüş değerini kontrol etmeyi unutmayın.