EDA Playground'da Dene

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_none ile 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_any sonrası "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:

  1. fork iç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).
  2. join_any ile hangisi önce biterse ana süreç devam eder.
  3. disable ile 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 initial bloğu (wait fork): Task_A (20ns) ve Task_B (40ns) join_none ile başlatılır, böylece ana süreç beklemeden devam eder. Ana süreç önce #10 kadar başka bir iş yapar, ardından wait fork; ile her iki görevin de bitmesini bekler. En uzun görev Task_B 40ns sürdüğü için bekleme bu noktada tamamlanır.
  • İkinci initial bloğu (disable fork): Bu blok #100 zamanında başlar. Uzun_Task (100ns) ve Kisa_Task (10ns) paralel başlatılır. join_any sayesinde ilk biten Kisa_Task ana süreci serbest bırakır. Hemen ardından çağrılan disable fork;, hâlâ koşmakta olan Uzun_Task'i zorla sonlandırır; bu yüzden onun "bitti" mesajını hiç görmeyiz.
  • Üçüncü initial bloğu (Timeout kalıbı): #300 zamanında çalışır ve adlandırılmış bir blok olan timeout_block kullanır. İçinde Hedef_Task (50ns) ile 30ns'lik bir bekçi dalı yarışır. Bekçi 30ns < 50ns olduğu için önce biter; join_any ana süreci serbest bırakır ve "ZAMAN AŞIMI!" mesajı yazılır. Ardından disable timeout_block; ile hâlâ koşan Hedef_Task iptal 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 fork etki 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ıp disable isim; kullanın.
  • Timeout kalıbında join_any sonrası disable çağrısını unutmayın; aksi halde yavaş görev arka planda çalışmaya devam edebilir.
  • disable ile 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 son fork bloğunu değil. Bir sınıf metodunda daha önce join_none ile başlatılmış bir monitor varsa, sonraki bir timeout kalıbındaki disable fork onu 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_any ilk koşucunun bitiş çizgisini geçmesidir. disable fork hakem 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...join sarmalı) yapmanız gerekir.

Sık Yapılan Hatalar

  • disable fork ile monitor'ü öldürmek: Yukarıda anlatılan geniş kapsam sorunu. Çözüm: izole fork...join sarmalı.
  • wait fork ile sonsuz süreç beklemek: forever döngülü bir driver join_none ile başlatılmışsa, wait fork hiç 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.
  • disable ile ö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 yerine event ile "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

  1. İkinci initial bloğundaki disable fork; satırını silin. Uzun_Task bitti mesajı artık görünüyor mu? Hangi zamanda?
  2. Timeout kalıbında #30 yerine #80 yazın: hangi dal önce bitti, hangi mesaj basıldı? disable timeout_block bu kez hangi süreci öldürdü?
  3. Yukarıdaki run_with_timeout task'ını modüle ekleyin; ondan önce join_none ile başlatılmış bir forever monitor süreci olsun. Sarmal fork...join olmadan ve olduğu şekilde çalıştırın: monitor hangi durumda hayatta kaldı?
  4. 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.