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.
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.