wait(event.triggered) ve @(event) Karşılaştırması
Gün 4: Süreçler Arası İletişim (IPC) | Olay tetikleme (->), bekleme (@, wait) ve senkronizasyon
Özellikle karmaşık doğrulama ortamlarında (örneğin UVM tabanlı bir VIP geliştirirken veya katı standartlara tabi projelerin testbench'lerinde), simülatörün delta döngüleri (delta cycles) üzerinde tam kontrolümüz yoktur. Farklı bileşenlerin (sequence, monitor, scoreboard vb.) aynı zaman adımında (time step) hangi sırayla çalışacağı belirsizdir (non-deterministic). Bu belirsizlik, @(event) kullanıldığında "race condition" (yarış durumu) yaratır ve simülasyonların nedensiz yere kilitlenmesine (hang) yol açar.
Altın Kural:
- State / Durum Kontrolü: "Bu olay oldu mu? (Olduysa devam et, geride kalmayayım)" mantığı varsa -> wait(event.triggered)
- Zamanlama / Gecikme: "Bir sonraki tetiklemeye kadar (örneğin sonraki clock cycle) dur ve kesinlikle bekle" mantığı varsa -> @(event)
UVM mimarisinde daha gelişmiş componentler arası haberleşme için standart SystemVerilog event'leri yerine uvm_event ve uvm_event_pool kullanımı da çok yaygındır. Bu konuya da ilerideki derslerde değineceğiz.
İki Bekleme Yönteminin Farkı
Bir olayı (event) beklemenin iki yolu vardır ve aralarındaki fark race condition'ların kaynağıdır:
@(event)(kenar tetiklemeli): Sürecin olayı yakalayabilmesi için tetiklemeden önce beklemeye geçmiş olması gerekir. Aynı zaman adımında (time step) önce tetikleyip sonra@ile beklerseniz, tetiklemeyi kaçırırsınız ve süreç asılı kalır.wait(event.triggered)(durum/seviye kontrolü): Olayın o zaman adımında tetiklenip tetiklenmediğini sorgular. Tetiklemewait'ten hemen önce, hatta aynı anda gerçekleşse bile bunu algılar ve süreç bloklanmadan devam eder.
Neden Önemli?
Karmaşık testbench'lerde hangi sürecin aynı zaman adımında önce çalışacağı belirsizdir (non-deterministic). Eğer bir bileşen "olay zaten oldu mu?" sorusunu @(event) ile sorarsa, olayı bir delta döngüsü farkıyla kaçırıp simülasyonu kilitleyebilir. Bu yüzden durum kontrolü için wait(event.triggered), belirgin bir sonraki tetiklemeyi beklemek için @(event) tercih edilir.
Kaynak Kod
module wait_triggered_usage;
event sync_event;
initial begin
#10;
// 1) Once olayi tetikle
$display("[%0t] Olay tetikleniyor.", $time);
-> sync_event;
// 2) wait(.triggered): olay ayni zaman adiminda tetiklenmis olsa bile gorur
wait(sync_event.triggered);
$display("[%0t] wait(sync_event.triggered) olayi basariyla yakaladi.", $time);
// 3) Ikinci tetikleme: AYNI surecte, AYNI zaman adiminda
-> sync_event; // (A) DENEME: bu satiri yoruma alip asagidaki fork'u acin
/*
fork
begin
#5;
$display("[%0t] Paralel surec: olay yeniden tetikleniyor.", $time);
-> sync_event; // (B) Baska bir surecten ve GELECEKTE tetikleme
end
join_none
*/
// 4) @ ile bekle: kenar bekler, gecmise bakmaz
$display("[%0t] Simdi @(sync_event) ile bekleniyor...", $time);
@(sync_event);
// (A) aktifken bu satir ASLA calismaz: olay @'ten once, ayni surecte ve
// ayni zaman adiminda tetiklendi; @ o kenari kacirdi ve surec asili kaldi.
// (B) aktifken olay 5 ns sonra baska bir surecten gelir ve @ onu yakalar.
$display("[%0t] @(sync_event) ikinci tetiklemeyi yakaladi!", $time);
$finish;
end
endmodule
Kodun Açıklaması
- Modül tek bir
sync_eventolayı ve tek birinitialblok içerir; böylece tüm işlemler aynı süreçte ve çoğu aynı zaman adımında gerçekleşir. #10gecikmesinden sonra-> sync_event;ile olay tetiklenir.- Hemen ardından
wait(sync_event.triggered);çağrılır. Olay aynı zaman adımında tetiklendiği halde,.triggeredözelliği bunu algılar ve süreç takılmadan devam eder; "olayi basariyla yakaladi" mesajı yazılır. İştewait(...triggered)'ın gücü budur. - Sonra (A) satırında ikinci kez
-> sync_event;tetiklenir ve hemen@(sync_event);ile beklemeye geçilir. Kritik nokta şudur: tetikleme@'ten önce ve aynı zaman adımında olduğu için@operatörü bu tetiklemeyi yakalayamaz. Süreç bu satırda sonsuza kadar bloklanır; simülasyon ancak araç zaman aşımıyla ya da sizin müdahalenizle biter. - Bu yüzden son
$displaysatırı ("ikinci tetiklemeyi yakaladi") asla çalışmaz. Deneme için (A) satırını yoruma alıp (B)'yi içerenfork...join_nonebloğunu etkinleştirin: olay artık paralel bir süreçten ve 5 ns sonra tetiklenir;@beklemeye geçtiğinde gelecekteki bu kenarı yakalar, mesaj basılır ve$finishçalışır.
Zaman Çizelgesiyle Görelim
Zaman 10 ns, tek bir zaman adımı içinde:
-> sync_event ┐
wait(.triggered) ✔ │ ayni adimda "tetiklendi mi?" → EVET, devam
-> sync_event (A) │
@(sync_event) ✘ ┘ bir SONRAKI kenari bekler; o kenar asla gelmez
(B) ile:
10 ns: @(sync_event) beklemeye girer
15 ns: paralel surec -> sync_event → kenar geldi, @ uyanir ✔
Aynı zaman adımı içinde -> e ile @(e) sırasının ters olması bir "delta döngüsü yarışı"dır. Gerçek testbench'lerde bu sırayı siz değil, simülatörün süreç zamanlaması belirler; bu yüzden "oldu mu?" sorusunu her zaman .triggered ile sorun.
Önemli Noktalar
- Aynı süreçte aynı zaman adımında bir olayı önce tetikleyip sonra
@ile beklemek klasik bir hatadır;@o tetiklemeyi kaçırır ve süreç asılı kalır. - "Bu olay zaten oldu mu?" mantığı için her zaman
wait(event.triggered)kullanın; durum kontrolünde delta döngüsü yarışlarına karşı güvenlidir. @(event)'i, olayı başka bir süreçten ve gelecekte tetiklenecek şekilde beklerken kullanın (örneğin saat kenarı, monitör sinyali).- Koddaki yorumlu
fork...join_noneörneği, tetiklemeyi paralel bir sürece taşıyarak@'in nasıl çalışır hale geldiğini göstermek için bilinçli olarak konmuştur; deneyerek farkı kendiniz gözlemleyin. - Gerçek doğrulama ortamlarında bu tür kilitlenmeler saatler kaybettirebilir; bekleme yöntemini niyetinize (durum mu, kenar mı?) göre seçmek en iyi pratiktir.
.triggered'ın bir sınırı vardır: yalnızca aynı zaman adımını görür. Olay 10 ns'de tetiklenmiş ve siz 20 ns'dewait(e.triggered)yazmışsanız beklersiniz. Zamandan bağımsız kalıcı bir "oldu" bayrağı gerekiyorsabit done;tutupwait(done);yazın ya da UVM'dekiuvm_event'inwait_trigger()/wait_ptrigger()veis_on()metodlarını kullanın.
Hangi Durumda Hangisi?
| Senaryo | Doğru seçim | Neden |
|---|---|---|
| "Reset bitti mi? Bittiyse hemen başla" | wait(reset_done.triggered) |
Reset benden önce bitmiş olabilir |
| "Bir sonraki saat kenarını bekle" | @(posedge clk) |
Açıkça gelecekteki kenar isteniyor |
| "Monitor her örnekleme yaptığında uyan" | @(sample_ev) |
Sürekli tekrar eden, ileride gelen tetikler |
| "Üretici tüm paketleri gönderdi mi?" | wait(gen_done.triggered) (aynı adımda) ya da bit bayrak |
Tek seferlik, geçmişte kalmış olabilir |
| İki süreç karşılıklı el sıkışıyor | @ çiftleri + dikkatli sıralama veya mailbox |
Hangi tarafın önce geleceği belirsizse mailbox daha güvenli |
Kendinizi Deneyin
- Kodu olduğu gibi çalıştırın ve simülasyonun bitmediğini gözlemleyin (EDA Playground'da çalışma zaman aşımı mesajı alırsınız). Sonra (A)'yı kapatıp (B)'yi açın ve çıktıyı karşılaştırın.
- (B)'deki
#5;satırını#0;yapın.@hâlâ yakalıyor mu? Delta döngüsü sırasını açıklayın. wait(sync_event.triggered)satırını#1;gecikmesinden sonraya taşıyın. Artık bekliyor mu?.triggered'ın yalnızca aynı zaman adımını gördüğünü doğrulayın.- Modüle
bit done;ekleyin; tetiklemeyle birliktedone = 1yapın ve 20 ns sonrawait(done)ile bekleyin. Bu yaklaşımın.triggered'dan hangi farkı var?
Hızlı Kontrol: 10 ns'de tetiklenen bir olayı 20 ns'de wait(e.triggered) ile beklerseniz ne olur?
Beklersiniz. .triggered yalnızca içinde bulunulan zaman adımındaki tetiklemeyi görür; geçmiş adımları hatırlamaz. Zamandan bağımsız bir "oldu" bilgisi için kalıcı bir bayrak (bit) kullanın.