fork...join
Gün 4: Süreçler Arası İletişim (IPC) | Eşzamanlı süreçler: join, join_any, join_none
Bu derste SystemVerilog'da eşzamanlı (paralel) süreçleri başlatmanın temel yapı taşı olan fork...join bloklarını ve üç farklı sonlanma biçimini öğreneceksiniz. Bir testbench'te sürücü (driver), monitör ve sıralayıcı (sequencer) gibi bileşenlerin aynı anda çalışması bu yapı sayesinde mümkün olur.
Aynı fork. Üç farklı kader.
Alt süreçler hep aynı anda başlar. Fark, ana sürecin ne zaman devam ettiğinde.
join
Tüm alt süreçler bitene dek ana süreç durur. Süre = en uzun süreç.
join_any
Biri bitti mi ana süreç devam eder. Diğerleri arka planda yaşar.
join_none
Ana süreç anında ilerler. Alt süreçler bağımsız yaşamaya başlar.
Zamanı kendin sür.
Zaman çizelgesine tıkla-sürükle, süreleri kaydırıcılarla değiştir, modu seç. Sağdaki kod ve $display çıktısı simülasyonla birlikte akar.
KAYNAK KODfork_join.sv
SİMÜLATÖR ÇIKTISI$display
automatic yoksa, herkes aynı değişkeni paylaşır.
Aynı görevi fork içinde üç kez çağırdın. Değişkenler statikse tek bir bellek alanı var — son yazan kazanır.
BELLEK1 paylaşılan çerçeve
Veriler birbirine karışırÇIKTI$display
Ana süreç ne zaman devam eder?
Aklında kalsın.
#0 ya da gecikme gerekebilir.join_any sonrası artıkları bekle ya da öldür; yoksa simülasyon beklenmedik uzar.fork...join Nedir?
fork...join bloğu, içindeki her bir ifadeyi (statement) ayrı bir paralel süreç (process/thread) olarak başlatır. Normal sıralı (sequential) kod yukarıdan aşağıya satır satır işlenirken, fork bloğunun içindeki satırlar aynı simülasyon zamanında eş zamanlı olarak hayata geçer. Bu, donanımın doğası gereği paralel olduğu bir dünyada gerçek davranışı modellemek için kritiktir.
fork ile açılan blok üç farklı anahtar kelimeyle kapatılabilir ve ana sürecin ne zaman devam edeceği bu seçime göre değişir:
join→ Ana sürecin devam edebilmesi için forkdaki TÜM alt süreçlerin bitmesini bekler. Toplam süre, en uzun süren süreç kadardır.join_any→ Alt süreçlerden HERHANGİ BİRİ (yani ilk biten) tamamlandığında ana süreç devam eder. Diğer süreçler arka planda çalışmaya devam eder.join_none→ Ana süreç HİÇBİRİNİ BEKLEMEDEN anında devam eder; alt süreçler arka planda yaşamaya başlar.
Neden Önemli ve Nerede Kullanılır?
- Doğrulama ortamları: UVM ve klasik testbench'lerde generator, driver, monitor gibi bileşenler genellikle
fork...join_noneile başlatılıp birbirinden bağımsız çalışır. - Timeout / watchdog kalıpları:
join_anyile "ya iş biter ya da zaman aşımı olur" mantığı kurulur. - Stimulus üretimi: Birden fazla arabirimi aynı anda sürmek için paralel süreçler şarttır.
Ayrıca fork içine begin...end blokları koyarak iç içe (nested) yapılar oluşturabilirsiniz; bu durumda her begin...end bloğu kendi içinde sıralı çalışan tek bir paralel süreç olur.
Kaynak Kod
// =============================================================================
// GUN 4 - Konu 1: fork...join, fork...join_any, fork...join_none
// =============================================================================
module fork_join;
// Gorev tanimlari
task automatic work(string name, int delay);
$display(" [%0t] %s basladi (sure: %0dns)", $time, name, delay);
#delay;
$display(" [%0t] %s bitti", $time, name);
endtask
initial begin
$display("=== fork...join Turleri ===\n");
// --- fork...join: TUM surecler bitene kadar bekle ---
$display("--- fork...join (hepsi bitsin) ---");
$display(" [%0t] Baslangic", $time);
fork
work("Gorev_A", 30);
work("Gorev_B", 10);
work("Gorev_C", 20);
join
$display(" [%0t] fork...join tamamlandi (en uzun: 30ns)\n", $time);
// --- fork...join_any: HERHANGI BIRI bitince devam et ---
$display("--- fork...join_any (ilk biten) ---");
$display(" [%0t] Baslangic", $time);
fork
work("Gorev_D", 30);
work("Gorev_E", 10);
work("Gorev_F", 20);
join_any
$display(" [%0t] fork...join_any devam etti (ilk biten: 10ns)\n", $time);
#20; // Digerlerinin bitmesini bekle
// --- fork...join_none: HICBIRINI bekleme ---
$display("--- fork...join_none (beklemeden devam) ---");
$display(" [%0t] Baslangic", $time);
fork
work("Gorev_G", 30);
work("Gorev_H", 10);
work("Gorev_I", 20);
join_none
$display(" [%0t] fork...join_none hemen devam etti", $time);
#0; // Delta cycle - fork'lar baslasin
$display(" [%0t] #0 sonrasi (gorevler basladi)", $time);
#35; // Tumunun bitmesini bekle
// --- Çok satırlı fork ---
$display("\n--- Ic Ice Fork ---");
$display(" [%0t] Baslangic", $time);
fork
begin // Sirali blok 1
work("Sirali_1a", 5);
work("Sirali_1b", 5); // 1a'dan sonra baslar
end
begin // Sirali blok 2
work("Sirali_2a", 3);
work("Sirali_2b", 7);
end
join
$display(" [%0t] Ic ice fork tamamlandi\n", $time);
$display("=== fork...join Sonu ===");
$finish;
end
endmodule
Kodun Açıklaması
workgörevi,automaticolarak tanımlanmıştır. Bu çok önemlidir:forkiçinde aynı görev birden fazla kez paralel çağrıldığında,automaticher çağrı için ayrı bir yığın (stack) çerçevesi oluşturur venameiledelayargümanları birbirine karışmaz.fork...joinbölümü:Gorev_A(30ns),Gorev_B(10ns) veGorev_C(20ns) aynı anda başlar. Ana süreç en uzun sürenGorev_Abitene kadar, yani 30ns boyunca bekler.fork...join_anybölümü:Gorev_D,Gorev_E,Gorev_Fparalel başlar. En kısa sürenGorev_E(10ns) bitince ana süreç hemen devam eder. Hemen ardından gelen#20;ifadesi, hâlâ arka planda koşan diğer görevlerin bitmesine zaman tanımak için konmuştur.fork...join_nonebölümü: Süreçler başlatılır ama ana süreç beklemeden bir sonraki satıra geçer. Buradaki#0;(delta cycle gecikmesi) ilginçtir: ana süreç bir an için yürütmeyi bırakır ve böyleceforkile başlatılan süreçlerin ilk satırları çalışma fırsatı bulur. Sonraki#35;ise tüm görevlerin tamamlanmasını bekler.- İç içe
fork: Bloğun içinde iki ayrıbegin...endvardır. Her biri kendi içinde sıralı çalışır: örneğinSirali_1abittikten sonraSirali_1bbaşlar. Ancak ikibegin...endbloğu birbirine göre paraleldir.joinsayesinde ana süreç her iki bloğun da bitmesini bekler.
Önemli Noktalar
forkiçinde paralel çağrılan görevler/fonksiyonlarautomaticolmalıdır; aksi halde paylaşılan statik değişkenler yüzünden veriler birbirine karışır.join_nonekullandıktan hemen sonra başlatılan süreçlerin gerçekten çalışmaya başladığını görmek için#0veya bir gecikme gerekebilir; aksi halde ana süreç onlara fırsat vermeden ilerleyebilir.join_anysonrası arka planda hâlâ koşan süreçler vardır. Bunları açıkça beklemek (wait fork) veya iptal etmek (disable fork) gerekebilir, yoksa simülasyon beklenmedik biçimde uzayabilir.- Bir
forkbloğunun içinebegin...endkoymak, o blok içindeki ifadeleri tek bir paralel sürece dönüştürür; sıralı ve paralel davranışı bu şekilde harmanlayabilirsiniz. - Simülasyon zamanı (
$time) çıktısını izleyerek hangi sürecin ne zaman başlayıp bittiğini takip etmek, paralel davranışı anlamanın en sağlam yoludur. - Modül içindeki
task/function'lar varsayılan olarak static'tir; sınıf metodları ise varsayılan olarak automatic'tir. Bu yüzden sınıf tabanlı testbench'lerde (Gün 6–7)automaticyazmanız gerekmez, ama modül içi yardımcı task'larda mutlaka yazmalısınız. fork...join_noneile başlatılan alt süreçler, ana süreç bir bekleme noktasına (#,@,wait) gelmeden çalışmaya başlamaz. Koddaki#0tam da bu yüzden vardır; ancak#0kullanımı gerçek projelerde kaçınılması gereken bir kalıptır (delta-cycle yarışları yaratır). Tercih edilen yol, alt süreçlerin başladığınıeventveyamailboxile açıkça haber vermesidir.join_anyvejoin_nonesonrasında ana süreç biterse (örneğininitialbloğu sonlanırsa) alt süreçler ölmez, çalışmaya devam eder. Simülasyonun bitmesi için$finishya da tüm süreçlerin doğal olarak sonlanması gerekir.
Üç join Türü Bir Bakışta
join |
join_any |
join_none |
|
|---|---|---|---|
| Ana süreç ne zaman devam eder? | Tüm alt süreçler bitince | İlk alt süreç bitince | Hemen (hiç beklemez) |
| Örnekteki devam zamanı | 30 ns | 10 ns | 0 ns |
| Arka planda süreç kalır mı? | Hayır | Evet (bitmemiş olanlar) | Evet (hepsi) |
| Tipik kullanım | "Hepsi tamamlansın" (paralel reset dizileri, çoklu arayüz sürme) | Timeout/watchdog, "ilk cevap gelen kazanır" | Sürekli çalışan monitor/driver başlatma |
| Sonrasında sık kullanılan | — | disable fork |
wait fork |
Benzetme: Üç arkadaşınıza görev verdiğinizi düşünün.
join: "Hepiniz dönmeden masadan kalkmam."join_any: "İlk dönen haber versin, ben yola çıkayım; diğerleri arkadan gelsin."join_none: "Görevleri dağıttım, hiç beklemeden kendi işime bakıyorum."
Döngü İçinde fork: Klasik Tuzak
Aşağıdaki kod, join_none ile ilgili en meşhur hatayı içerir:
for (int i = 0; i < 3; i++)
fork
$display("Surec %0d", i); // HATA: hepsi "Surec 3" basabilir!
join_none
Alt süreçler ana süreç bloklanınca (döngü bittikten sonra) çalışmaya başlar ve o anda i çoktan 3 olmuştur. Çözüm, her sürece kendi kopyasını vermektir:
for (int i = 0; i < 3; i++)
fork
automatic int idx = i; // Her surec icin ayri kopya
$display("Surec %0d", idx);
join_none
Bu kalıp (automatic yerel kopya) çoklu arayüz sürme, paralel agent başlatma gibi her yerde karşınıza çıkar.
Sık Yapılan Hatalar
- Modül task'ını
automaticyapmamak: Paralel çağrılarda argümanlar birbirine karışır;Gorev_A10 ns beklerkenGorev_B'nin adı basılabilir. - Döngüde
join_noneile döngü değişkenini paylaşmak: Yukarıdaki tuzak.automatickopya alın. join_anysonrası arka plan süreçlerini unutmak: Bir sonrakifork'undisable fork'u, öncekijoin_any'den kalan süreçleri de öldürebilir. Sonraki ders bunun çözümünü gösterir.forkiçindeki tek bir ifadenin birden fazla satır olmasını istemek:begin...endile sarmalamadan yazılan her satır ayrı süreç olur; sıralı akış istiyorsanızbegin...endzorunludur.join_nonesonrası alt süreçlerin hemen başladığını sanmak: Ana süreç bekleme noktasına gelmeden başlamazlar.
Kendinizi Deneyin
worktask'ındanautomatickelimesini silip çalıştırın. Çıktıdaki isim/süre karışıklığını gözlemleyin ve nedenini açıklayın.join_nonebölümündeki#0;satırını silin. "#0 sonrasi" mesajı ile görevlerin "basladi" mesajlarının sırası nasıl değişti?- Yukarıdaki "döngü içinde fork" örneğini
automatic int idx = i;olmadan ve olduğu şekilde çalıştırıp farkı gözlemleyin. - İç içe fork bölümünü
join_anyile bitirin. Ana süreç hangi zamanda devam etti? Hangi görevlerin "bitti" mesajı daha sonra geldi?
Hızlı Kontrol: fork içinde üç görev 30, 10 ve 20 ns sürüyorsa join_any sonrasında ana süreç hangi zamanda devam eder?
10 ns sonra; ilk biten görev ana süreci serbest bırakır. 30 ve 20 ns'lik görevler arka planda çalışmaya devam eder; onları beklemek için wait fork, iptal etmek için disable fork gerekir.