wait fork ve disable fork
Gün 4: Süreçler Arası İletişim (IPC) | Süreç bekleme, iptal etme ve timeout kalıbı
Bu derste paralel süreçleri yönetmenin iki temel aracını öğreneceksiniz: wait fork ile arka planda koşan tüm alt süreçlerin bitmesini beklemek ve disable fork ile bunları zorla sonlandırmak. Bu ikisini birleştirerek doğrulamada çok sık kullanılan timeout (zaman aşımı) kalıbını da kuracaksınız.
wait fork ve disable fork
Önceki derste join_none ile başlattığımız süreçlerin arka planda çalışmaya devam ettiğini görmüştük. Peki bu süreçleri sonradan nasıl kontrol ederiz? İşte bu noktada iki anahtar yapı devreye girer:
wait fork→ Çağrıldığı süreç, o ana kadar başlatılmış tüm alt süreçler (forklanmış process'ler) bitene kadar bekler.join_noneile başlatılıp daha sonra "hepsi tamamlandı mı?" kontrolü yapmak istediğinizde idealdir.disable fork→ Çağrıldığı sürecin başlattığı tüm aktif alt süreçleri anında iptal eder (öldürür).join_anysonrası "biri bitti, gerisini umursamıyorum" senaryolarında kullanılır.
Timeout (Zaman Aşımı) Kalıbı
Doğrulamada en kritik kalıplardan biri budur: Bir işin ya başarıyla tamamlanmasını ya da belirli bir süre içinde bitmemesi durumunda hata vermesini istersiniz. Bunun için:
forkiçine iki dal koyarsınız: biri asıl işi yapan görev, diğeri sadece bir gecikme (#timeout) bekleyen bir bekçi (watchdog).join_anyile hangisi önce biterse ana süreç devam eder.disableile geriye kalan (yavaş olan ya da bekçi olan) dal iptal edilir.
Bu kalıp, simülasyonların sonsuza kadar takılıp kalmasını (hang) engeller ve hatalı tasarımları erken yakalar. Adlandırılmış bloklar (fork : timeout_block) sayesinde disable timeout_block ile yalnızca o bloğu hedefli biçimde sonlandırabilirsiniz.
Kaynak Kod
// =============================================================================
// GUN 4 - Konu 2: wait fork ve disable fork
// =============================================================================
module wait_disable_fork;
task automatic slow_task(string name, int delay);
$display(" [%0t] %s basladi", $time, name);
#delay;
$display(" [%0t] %s bitti", $time, name);
endtask
// --- wait fork: Tum cocuk sureclerin bitmesini bekle ---
initial begin
$display("=== wait fork ve disable fork ===\n");
$display("--- wait fork ---");
$display(" [%0t] Ana surec basladi", $time);
fork
slow_task("Task_A", 20);
slow_task("Task_B", 40);
join_none
$display(" [%0t] join_none sonrasi, diger is yapiliyor...", $time);
#10;
$display(" [%0t] Ek is bitti, simdi fork'lari bekliyoruz", $time);
wait fork; // Task_A ve Task_B bitene kadar bekle
$display(" [%0t] wait fork: Tum gorevler tamamlandi!\n", $time);
end
// --- disable fork: Tum cocuk surecleri sonlandir ---
initial begin
#100;
$display("--- disable fork ---");
$display(" [%0t] Baslangic", $time);
fork
begin
slow_task("Uzun_Task", 100); // Cok uzun
end
begin
slow_task("Kisa_Task", 10);
end
join_any
$display(" [%0t] join_any: Biri bitti, digerini iptal ediyoruz", $time);
disable fork; // Kalan surecleri sonlandir
$display(" [%0t] disable fork: Tum bekleyen gorevler iptal edildi\n", $time);
end
// --- Zaman asimi (Timeout) kalibi ---
initial begin
#300;
$display("--- Timeout Kalibi ---");
$display(" [%0t] Baslangic", $time);
fork : timeout_block
begin
slow_task("Hedef_Task", 50);
$display(" [%0t] Hedef gorev basarili!", $time);
end
begin
#30; // 30ns timeout
$display(" [%0t] ZAMAN ASIMI! 30ns icinde bitmedi", $time);
end
join_any
disable timeout_block;
$display(" [%0t] Timeout blogu tamamlandi\n", $time);
#10;
$display("=== wait/disable fork Sonu ===");
$finish;
end
endmodule
Kodun Açıklaması
- Birinci
initialbloğu (wait fork):Task_A(20ns) veTask_B(40ns)join_noneile başlatılır, böylece ana süreç beklemeden devam eder. Ana süreç önce#10kadar başka bir iş yapar, ardındanwait fork;ile her iki görevin de bitmesini bekler. En uzun görevTask_B40ns sürdüğü için bekleme bu noktada tamamlanır. - İkinci
initialbloğu (disable fork): Bu blok#100zamanında başlar.Uzun_Task(100ns) veKisa_Task(10ns) paralel başlatılır.join_anysayesinde ilk bitenKisa_Taskana süreci serbest bırakır. Hemen ardından çağrılandisable fork;, hâlâ koşmakta olanUzun_Task'i zorla sonlandırır; bu yüzden onun "bitti" mesajını hiç görmeyiz. - Üçüncü
initialbloğu (Timeout kalıbı):#300zamanında çalışır ve adlandırılmış bir blok olantimeout_blockkullanır. İçindeHedef_Task(50ns) ile 30ns'lik bir bekçi dalı yarışır. Bekçi 30ns < 50ns olduğu için önce biter;join_anyana süreci serbest bırakır ve "ZAMAN AŞIMI!" mesajı yazılır. Ardındandisable timeout_block;ile hâlâ koşanHedef_Taskiptal edilir.
Önemli Noktalar
wait fork, çağrıldığı süreç tarafından başlatılmış alt süreçleri bekler; başka bloklardaki süreçleri kapsamaz. Bu kapsam farkına dikkat edin.disable forketki alanı geniştir ve mevcut süreçteki tüm alt forkları öldürür. Sadece belirli bir bloğu sonlandırmak için adlandırılmış blok (fork : isim) tanımlayıpdisable isim;kullanın.- Timeout kalıbında
join_anysonrasıdisableçağrısını unutmayın; aksi halde yavaş görev arka planda çalışmaya devam edebilir. disableile sonlandırılan bir görev, gecikmesinin (#delay) ortasında "öldürülür"; bu yüzden bitiş mesajları çalışmaz. Kaynak temizliği gerektiren durumlarda bu davranışı göz önünde bulundurun.- Bu üç blok farklı
#gecikmeleriyle (0/100/300) başladığı için birbirine karışmadan, çıktıda sırayla görüntülenir; bağımsız senaryoları aynı modülde göstermenin temiz bir yoludur. disable fork, çağrıldığı sürecin o ana kadar başlattığı tüm alt süreçleri öldürür; yalnızca sonforkbloğunu değil. Bir sınıf metodunda daha öncejoin_noneile başlatılmış bir monitor varsa, sonraki bir timeout kalıbındakidisable forkonu da sessizce öldürür. Bu, UVM driver'larında bile görülen gerçek bir hatadır.
Güvenli Timeout Kalıbı: İzole fork
disable fork'un geniş etki alanından korunmanın standart yolu, timeout kalıbını ek bir fork...join ile sarmalamaktır. Dıştaki fork yeni bir alt süreç yaratır; içteki disable fork yalnızca o sürecin çocuklarını öldürür:
task automatic run_with_timeout(int timeout_ns);
fork // Dis sarmal: izolasyon saglar
begin
fork // Ic fork: yaris
slow_task("Hedef", 50);
#timeout_ns;
join_any
disable fork; // Sadece ic fork'un cocuklarini oldurur
end
join // Dis blok tamamlanana kadar bekle
endtask
Bu kalıp, aynı sınıfta daha önce join_none ile başlatılmış monitor/scoreboard süreçlerine dokunmaz. Gerçek projelerde "timeout" dediğinizde yazmanız gereken şekil budur.
wait fork ve disable fork Yan Yana
wait fork |
disable fork |
disable <blok_adı> |
|
|---|---|---|---|
| Etki | Tüm alt süreçler bitene kadar bekler | Tüm alt süreçleri öldürür | Yalnızca adı verilen bloğu öldürür |
| Kapsam | Çağıran sürecin tüm çocukları | Çağıran sürecin tüm çocukları | Hiyerarşik ad ile hedeflenen blok |
| Tipik eş | join_none |
join_any |
fork : isim ... join_any |
| Riski | Sonsuz döngülü bir çocuk varsa asla dönmez | Yanlışlıkla gereken süreçleri öldürür | Blok adı çakışması |
Benzetme: Bir yarış düzenlediğinizi düşünün.
join_anyilk koşucunun bitiş çizgisini geçmesidir.disable forkhakem düdüğüdür: "Yarış bitti, herkes dursun!" — ama düdük, stadyumdaki tüm koşulara ses verir; sadece bu yarışın koşucularını durdurmak için koşuyu ayrı bir pistte (fork...joinsarmalı) yapmanız gerekir.
Sık Yapılan Hatalar
disable forkile monitor'ü öldürmek: Yukarıda anlatılan geniş kapsam sorunu. Çözüm: izolefork...joinsarmalı.wait forkile sonsuz süreç beklemek:foreverdöngülü bir driverjoin_noneile başlatılmışsa,wait forkhiç dönmez. Yalnızca biten süreçleri beklemek için kullanın.- Timeout bekçisinin kendisini iptal etmemek: Hedef görev bekçiden önce bitse bile
disableçağrılmazsa bekçi 30 ns sonra "ZAMAN AŞIMI" basar; yanlış hata raporu üretir. disableile öldürülen görevin kaynakları: Semaphore anahtarı almış ya da mailbox'tan veri çekmiş bir süreç öldürülürse anahtar geri verilmez / veri kaybolur. Kritik bölgeler için iptal yerineeventile "nazikçe durma" sinyali tercih edin.- Blok adını yanlış kapsamda kullanmak:
disable timeout_block;yalnızca aynı hiyerarşiden erişilebilen bir ad için çalışır; farklı modülden hiyerarşik yol gerekir.
Kendinizi Deneyin
- İkinci
initialbloğundakidisable fork;satırını silin.Uzun_Task bittimesajı artık görünüyor mu? Hangi zamanda? - Timeout kalıbında
#30yerine#80yazın: hangi dal önce bitti, hangi mesaj basıldı?disable timeout_blockbu kez hangi süreci öldürdü? - Yukarıdaki
run_with_timeouttask'ını modüle ekleyin; ondan öncejoin_noneile başlatılmış birforevermonitor süreci olsun. Sarmalfork...joinolmadan ve olduğu şekilde çalıştırın: monitor hangi durumda hayatta kaldı? - Birinci bloktaki
wait fork;yerine#100;yazın. Çıktı aynı mı?wait fork'un neden daha sağlam olduğunu açıklayın (ipucu: görev sürelerini değiştirip tekrar bakın).
Hızlı Kontrol: disable fork hangi süreçleri sonlandırır?
Çağıran sürecin o ana kadar başlattığı tüm aktif alt süreçleri — yalnızca son fork bloğundakileri değil. Bu yüzden join_none ile başlatılmış arka plan süreçleriniz varsa timeout kalıbını ayrı bir fork...join içine alarak izole edin.