EDA Playground'da Dene

Events (Olaylar)

Gün 4: Süreçler Arası İletişim (IPC) | Olay tetikleme (->), bekleme (@, wait) ve senkronizasyon

Bu derste SystemVerilog'un event veri tipini öğreneceksiniz. Olaylar (events), paralel süreçleri birbirine veri vermeden ya da el sıkışma (handshake) yaparak senkronize etmenin en hafif ve en temel yoludur.

Event (Olay) Nedir?

Bir event, SystemVerilog'da herhangi bir veri taşımayan, yalnızca "bir şey oldu" sinyalini bir süreçten diğerine ileten özel bir tiptir. Üç temel işlemle kullanılır:

  • Tanımlama: event ad; ile bir olay nesnesi oluşturulur.
  • Tetikleme (trigger): -> ad; ifadesi olayı tetikler ve o an o olayı bekleyen tüm süreçleri uyandırır.
  • Bekleme: @(ad) veya wait(ad.triggered) ile bir süreç, olay tetiklenene kadar bloklanır.

Neden Önemli ve Nerede Kullanılır?

  • Senkronizasyon: "Reset bitmeden hiçbir bileşen çalışmaya başlamasın" gibi kuralları kolayca kurarsınız.
  • El sıkışma (handshake): Üretici (producer) "veri hazır" der, tüketici (consumer) işleyip "onay" geri gönderir. Bu çift yönlü senkronizasyon olaylarla zarif biçimde yazılır.
  • Faz kontrolü: Bir işin tüm aşamalarının bittiğini haber vermek için phase_complete gibi olaylar kullanılır.

@(olay) kenar tetiklemeli (edge-sensitive) çalışır: yani sürecin olayı yakalayabilmesi için, beklemeye olay tetiklenmeden önce geçmiş olması gerekir. wait(olay.triggered) ise olayın o zaman adımında tetiklenip tetiklenmediğini de görür; bu ince fark bir sonraki derste detaylıca incelenir.

Kaynak Kod

// =============================================================================
// GUN 4 - Konu 3: Event'ler - Olay Tetikleme ve Bekleme
// =============================================================================

module events;
  // Event tanimla
  event data_ready;
  event ack_received;
  event reset_done;
  event phase_complete;

  int shared_data;

  // --- Uretici (Producer) ---
  initial begin : producer
    $display("=== Events (Olay Tetikleme) ===\n");

    // Reset bekle
    @(reset_done);
    $display("  [%0t] Uretici: Reset tamamlandi, calismaya basliyor", $time);

    repeat (5) begin
      #10;
      shared_data = $urandom_range(1, 100);
      $display("  [%0t] Uretici: Veri uretildi = %0d", $time, shared_data);
      -> data_ready;  // Olayi tetikle

      @(ack_received);  // Tuketici onayini bekle
      $display("  [%0t] Uretici: Onay alindi", $time);
    end

    $display("\n  [%0t] Uretici: Tum veriler gonderildi", $time);
    -> phase_complete;
  end

  // --- Tuketici (Consumer) ---
  initial begin : consumer
    @(reset_done);
    $display("  [%0t] Tuketici: Reset tamamlandi", $time);

    forever begin
      @(data_ready);  // Veri hazir olayini bekle
      $display("  [%0t] Tuketici: Veri alindi = %0d", $time, shared_data);
      #5;  // Isleme suresi
      -> ack_received;  // Onay gonder
    end
  end

  // --- Reset sureci ---
  initial begin : reset_gen
    $display("  [%0t] Reset basladi...", $time);
    #20;
    $display("  [%0t] Reset tamamlandi!", $time);
    -> reset_done;
  end

  // --- wait() ile event bekleme ---
  initial begin
    wait(phase_complete.triggered);
    $display("\n  [%0t] Phase tamamlandi (wait ile algilandi)", $time);
  end

  // --- Triggered ozelligi ---
  initial begin
    #200;  // Zaman asimi
    $display("\n=== Events Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • Modülün başında dört olay tanımlanır: data_ready, ack_received, reset_done ve phase_complete. shared_data ise üretici ile tüketici arasında paylaşılan veridir.
  • reset_gen bloğu: 20ns boyunca reset uygular, ardından -> reset_done; ile reset bitişini duyurur. Hem producer hem consumer blokları başında @(reset_done) ile beklediği için, ikisi de ancak reset bittikten sonra çalışmaya başlar.
  • producer bloğu: Bir döngü içinde 5 kez veri üretir. Her seferinde shared_data'yı $urandom_range(1, 100) ile rastgele doldurur, -> data_ready; ile "veri hazır" der ve sonra @(ack_received) ile tüketicinin onayını bekler. Bu, klasik bir el sıkışma döngüsüdür.
  • consumer bloğu: forever döngüsü içinde @(data_ready) ile veri beklenir, shared_data okunur, #5 işleme süresi geçer ve -> ack_received; ile onay geri gönderilir. Üretici ve tüketici bu sayede birbirini kilitlemeden, sırayla ilerler.
  • wait(phase_complete.triggered): Ayrı bir initial blok, üreticinin tüm verileri gönderip tetiklediği phase_complete olayını bekler ve fazın bittiğini bildirir.
  • Son initial blok ise #200 zaman aşımıyla simülasyonu $finish ile sonlandırır.

Önemli Noktalar

  • @(olay) kenar tetiklemelidir: bekleyen süreç, olay tetiklenmeden önce beklemeye geçmiş olmalıdır. Aksi halde tetiklemeyi kaçırır ve sonsuza kadar bekleyebilir (race condition).
  • Üretici-tüketici el sıkışmasında data_ready ve ack_received olaylarının sırası kritiktir; her iki taraf da karşılıklı beklediği için döngü kilitlenmeden ilerler.
  • Birden çok süreç aynı reset olayını (reset_done) beklediğinde, -> reset_done; ile yapılan tek bir tetikleme hepsini aynı anda uyandırır.
  • wait(olay.triggered), olayın o zaman adımında tetiklenip tetiklenmediğini de algılar; bu yüzden phase_complete gibi tek seferlik bayraklarda @ yerine genellikle daha güvenlidir.
  • Olaylar veri taşımaz; aktarılacak gerçek veri için (burada shared_data gibi) ayrı bir paylaşılan değişken ya da mailbox kullanılır.
  • -> bloklamayan (blocking değil, anlık) bir tetiklemedir: tetikleyen süreç durmaz. Bir de ->> (nonblocking trigger) vardır: tetiklemeyi NBA bölgesine erteler; always @(posedge clk) içinden bir olayı tetiklerken yarış riskini azaltmak için kullanılır.
  • Bir event değişkeni başka bir event'e atanabilir (event e2 = e1;): ikisi aynı olayı paylaşır (handle gibi davranır). null atanmış bir event'i beklemek sonsuza kadar bloklar.
  • wait(ev.triggered) yalnızca aynı zaman adımında tetiklenen olayları görür; bir önceki zaman adımında tetiklenmiş olayı görmez. Yani .triggered bir "kalıcı bayrak" değil, "bu zaman diliminde oldu mu?" sorusudur. Kalıcı bir bayrak istiyorsanız ayrı bir bit kullanın.

IPC Araçlarını Seçme Rehberi

Araç Veri taşır mı? Bloklama Tipik kullanım
event ❌ @/wait bekler "Oldu" sinyali, senkronizasyon, el sıkışma
mailbox (sonraki ders) ✅ (FIFO) get/put bekler Bileşenler arası transaction akışı
semaphore ❌ (anahtar sayar) get bekler Paylaşılan kaynağa sıralı erişim
Paylaşılan değişken + wait(x == 1) ✅ (tek değer) Seviye kontrolü Basit bayraklar (ama yarışa açık)

Kural: "Bir şey oldu" → event; "Şunu al" → mailbox; "Sıra bende" → semaphore.

Benzetme: Event, bir kapı zilidir: ses çıkar, ama zilin kendisi size paket vermez. Üretici zile basar (-> data_ready), tüketici kapıyı açıp masadaki paketi (shared_data) alır ve karşı zile basarak (-> ack_received) "aldım" der. Zil çaldığında kapının önünde değilseniz (@ ile beklemiyorsanız) çalışı kaçırırsınız; bu da sonraki dersin konusudur.

Sık Yapılan Hatalar

  • Tetiklemeden sonra @ ile beklemek: Aynı zaman adımında önce -> e sonra @(e) yazarsanız süreç sonsuza kadar bekler. Durum sorusu için wait(e.triggered).
  • Paylaşılan değişkeni event olmadan okumak: Tüketici shared_data'yı data_ready tetiklenmeden okursa eski veriyi alır. Event ve veri yazımı arasındaki sırayı koruyun: önce veri, sonra tetik.
  • El sıkışmada tek yönlü event: Yalnızca data_ready kullanıp ack beklemezseniz üretici tüketiciden hızlı çalışır ve veri üzerine yazar. Çift yönlü el sıkışma ya da mailbox gerekir.
  • forever tüketicinin simülasyonu bitirmemesi: Consumer forever döngüsündedir; ayrı bir $finish olmadan simülasyon asla bitmez. Koddaki #200 bloğu bu yüzden vardır.

Kendinizi Deneyin

  1. Üreticideki #10; gecikmesini #1; yapın ve tüketicideki #5;'i koruyun. El sıkışma hâlâ doğru çalışıyor mu? Neden ack mekanizması hızlı üreticiyi frenledi?
  2. Tüketicideki -> ack_received; satırını silin. Simülasyon ne zaman takıldı? Hangi süreç nerede bekliyor?
  3. -> reset_done; yerine ->> reset_done; yazın. Çıktı değişti mi? Hangi durumlarda fark yaratır, araştırın.
  4. İkinci bir tüketici bloğu ekleyin; her iki tüketici de @(data_ready) beklesin. Tek bir tetikleme ikisini de uyandırdı mı? Bu "yayın" (broadcast) davranışının mailbox'tan farkını not edin.
Hızlı Kontrol: @(ev) ile wait(ev.triggered) arasındaki temel fark nedir?

@(ev) kenar bekler: yalnızca beklemeye geçtikten sonra gelen tetiklemeyi görür. wait(ev.triggered) ise seviye/durum kontrolüdür: olay aynı zaman adımında daha önce tetiklenmişse bile hemen devam eder. "Oldu mu?" sorusu için triggered, "bir sonrakini bekle" için @.