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.
İ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.
Bloklayan uçlar
put() doluysa, get() boşsa süreci uyutur; hız farkı kendiliğinden dengelenir.
Bakmak ≠ almak
peek() kopyasını okur, elemanı çıkarmaz. num() içerideki sayıyı verir.
Bloklamadan dene
try_get() boşsa beklemez, 0 döner; sürecin başka iş yapabilir.
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.
PRODUCERSystemVerilog
CONSUMERSystemVerilog
DURUMcanlı
SİMÜLATÖR ÇIKTISI$display
Simülasyon bitene kadar num() en fazla kaç olur?
Aklında kalsın.
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.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ıysa1, mailbox boşsa0dö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,putasla bloklamaz. - Sınırlı (bounded):
new(N)ile oluşturulur; en fazlaNeleman alır. Dolduğundaputbekler. 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ı
Transactionsınıfı: İçindeid,datavesourcealanları 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_simplebasitinttipi taşır,mbx_boundedsınırlı (new(3)) vembx_unboundedsınırsızTransactionmailbox'larıdır. - Basit mailbox bölümü:
mbx_simplesırasıylaput(10),put(20),put(30)ile doldurulur venum()ile boyut 3 olarak gösterilir. Ardındanget(val)ile FIFO sırayla10ve20alınır.peek(val)bir sonraki elemanı (30) çıkarmadan okur; bu yüzden boyut değişmez. Son olaraktry_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):
#50zamanında başlar.mbx_bounded = new(3)ile kapasite 3'tür.producer6 transaction üretipputeder, ama mailbox dolduğunda bekler;consumerise#15aralıklarla yavaşçagetyapar. 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.joinile her ikisinin bitmesi beklenir. - Çoklu tüketici bölümü:
#250zamanında çalışır. Sınırsızmbx_unbounded'a tek bir üretici 10 transaction koyarken, iki tüketici (Tuketici-1veTuketici-2) aynı mailbox'tan paralelgetyapar. Aynı kaynaktan birden fazla tüketicinin nasıl iş paylaştığını (load balancing) örnekler.
Önemli Noktalar
putvegetbloklayıcı (blocking) metotlardır: dolu bir bounded mailbox'aput, boş bir mailbox'tangetbekler. Bu otomatik bekleme, üretici-tüketici senkronizasyonunu kendiliğinden sağlar.- Bloklamadan çalışmak istediğinizde
try_get(ve benzeritry_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. Parametresizmailboxise tip denetimsizdir ve önerilmez.- Çoklu tüketici senaryosunda her transaction yalnızca tek bir tüketici tarafından
getedilir; 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_portkarşılar: tekwrite()çağrısı bağlı tüm dinleyicilere ulaşır. - Mailbox nesne handle'ı taşır, nesneyi kopyalamaz. Üretici
put(t)sonrasındat.data = 0;yazarsa tüketici de değişmiş veriyi görür. Üretici aynı nesneyi yeniden kullanacaksaput(t.copy())göndermeli ya da her turda yeni nesne yaratmalıdır (örnektet = new(...)döngü içindedir, bu yüzden güvenlidir). mailboxaslında bir sınıftır:new()ile yaratılır, handle ile taşınır,nullolabilir. 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
getile kapı önünde beklersiniz,try_getile "yoksa dönerim" dersiniz,peekile 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
mailboxkullanmak:mailbox mbx = new();her tipi kabul eder; yanlış tipgetedildiğinde çalışma zamanı hatası alırsınız. Daimamailbox #(Tip). - Aynı nesneyi tekrar tekrar
putetmek: Tüketicideki tüm paketler aynı nesneyi gösterir; "hepsi son değere dönüştü" şikâyetinin kaynağı budur. getile sonsuz beklemek: Üretici bitmiş, tüketiciforever get()döngüsünde kalmıştır. Simülasyonun bitmesi için ya$finishya dajoin_any+ bitiş olayı gerekir (Lab 4).- Sınırlı mailbox'ta geri-basıncı unutmak:
putbloklandığı için üretici "takıldı" görünür; bu bir hata değil, akış kontrolüdür.try_putile bloklamadan deneyebilirsiniz. num()'a güvenerekgetyapmak: Çok tüketicili ortamdaif (mbx.num() > 0) mbx.get(t);bile yarışa açıktır: iki tüketici aynı andanum() > 0görür, biri alır, diğeri bloklanır.try_gettek adımda ve güvenlidir.
Kendinizi Deneyin
- 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. - Producer'daki
#5;ile Consumer'daki#15;değerlerini yer değiştirin (hızlı tüketici, yavaş üretici). Bu kez kim kimi bekliyor? - Çoklu tüketici örneğinde üretici
t'yi döngü dışında bir kez yaratıp her turda yalnızcat.id = i;güncellesin. Tüketicilerin bastığıTXN#değerleri ne oldu? Neden? mbx_simpleüzerindetry_peekvetry_putmetotlarını deneyin; sınırlı bir mailbox'tatry_putdolu 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.