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.

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.