EDA Playground'da Dene

Semaphore

Gün 4: Süreçler Arası İletişim (IPC) | Kaynak yönetimi ve senkronizasyon: mutex, çok portlu erişim

Bu derste SystemVerilog'un yerleşik semaphore sınıfını öğreneceksiniz. Semaphore, sınırlı sayıda paylaşılan kaynağa (örneğin bir bus ya da bellek portları) aynı anda kaç sürecin erişebileceğini kontrol etmenin standart yoludur.

Semaphore Nedir?

Bir semaphore, içinde belirli sayıda anahtar (key) tutan bir sayaçtır. Bir kaynağa erişmek isteyen süreç anahtar ister; anahtar varsa alır ve devam eder, yoksa anahtar serbest kalana kadar bekler. İş bitince anahtarı geri verir. Temel metotları:

  • new(N) → N anahtarla bir semaphore oluşturur. Başlangıçtaki anahtar sayısı eş zamanlı erişim sınırını belirler.
  • get(n) → n anahtar almaya çalışır; yeterli anahtar yoksa bloklar (bekler).
  • put(n) → n anahtarı havuza geri koyar ve bekleyen süreçleri uyandırabilir.
  • try_get(n) → Bloklamadan dener; anahtar varsa alıp 1, yoksa 0 döner.

Mutex ve Çok Portlu Erişim

  • Mutex (karşılıklı dışlama): new(1) ile oluşturulan tek anahtarlı semaphore bir mutex'tir. Aynı anda yalnızca tek bir süreç kaynağa erişebilir; bu, paylaşılan bir bus'ı korumanın klasik yoludur.
  • Çoklu anahtar: new(2) gibi birden çok anahtar, aynı anda birden fazla sürecin erişmesine izin verir (örneğin 2 portlu bir bellek). Üçüncü süreç, biri portu serbest bırakana kadar beklemek zorunda kalır.

Neden Önemli ve Nerede Kullanılır?

Birden fazla süreç aynı paylaşılan kaynağa kontrolsüzce eriştiğinde yarış durumları (race conditions) ve veri bozulması oluşur. Semaphore, bus arbitrasyonu, sınırlı portlu belleğe erişim, lisans/kaynak havuzu yönetimi gibi senaryolarda erişimi düzenli ve güvenli kılar.

Kaynak Kod

// =============================================================================
// GUN 4 - Konu 5: Semaphore - Senkronizasyon ve Kaynak Yonetimi
// =============================================================================

module semaphore_ornek;

  semaphore bus_lock;     // Paylasilan bus erisimi
  semaphore mem_lock;     // Bellek erisimi (2 port)
  int shared_bus_data;

  // Bus'a erisen ajan
  task automatic bus_agent(string name, int num_txn, int delay);
    for (int i = 0; i < num_txn; i++) begin
      $display("  [%0t] %s: Bus erisimi istiyor...", $time, name);
      bus_lock.get(1);  // 1 anahtar al (yoksa bekle)
      $display("  [%0t] %s: Bus KILITLENDI, islem #%0d basliyor", $time, name, i);
      
      shared_bus_data = $urandom;
      #delay;  // Islem suresi
      
      $display("  [%0t] %s: Islem #%0d tamamlandi (data=0x%08h)",
               $time, name, i, shared_bus_data);
      bus_lock.put(1);  // 1 anahtar geri ver
      $display("  [%0t] %s: Bus SERBEST", $time, name);
      #2;  // Islemler arasi bekleme
    end
  endtask

  // Bellege erisen ajan
  task automatic mem_agent(string name, int delay);
    for (int i = 0; i < 3; i++) begin
      if (mem_lock.try_get(1)) begin  // Bloklamadan dene
        $display("  [%0t] %s: Bellek erisimi SAGLANDI", $time, name);
        #delay;
        mem_lock.put(1);
        $display("  [%0t] %s: Bellek serbest", $time, name);
      end else begin
        $display("  [%0t] %s: Bellek MESGUL, atliyor", $time, name);
      end
      #5;
    end
  endtask

  initial begin
    $display("=== Semaphore Kullanimi ===\n");

    // --- Tek anahtarli semaphore (mutex) ---
    $display("--- Bus Mutex (1 anahtar) ---");
    bus_lock = new(1);  // 1 anahtar = mutex

    fork
      bus_agent("Master_A", 3, 10);
      bus_agent("Master_B", 3, 8);
      bus_agent("Master_C", 2, 12);
    join

    // --- Cok anahtarli semaphore ---
    $display("\n--- Bellek (2 port = 2 anahtar) ---");
    mem_lock = new(2);  // 2 es zamanli erisim

    fork
      mem_agent("Port_1", 8);
      mem_agent("Port_2", 6);
      mem_agent("Port_3", 10);
    join

    $display("\n=== Semaphore Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • Modülde iki semaphore tanımlanır: paylaşılan bus için bus_lock ve bellek için mem_lock. shared_bus_data ise bus üzerinden taşınan ortak veridir.
  • bus_agent görevi: Her işlem öncesi bus_lock.get(1) ile bir anahtar almaya çalışır; anahtar yoksa bekler. Anahtarı alınca "Bus KILITLENDI" yazar, #delay kadar işlem yapar ve bus_lock.put(1) ile anahtarı geri verir. Böylece aynı anda yalnızca bir ajan bus'ı kullanabilir.
  • Bus mutex bölümü: bus_lock = new(1) ile tek anahtar, yani bir mutex oluşturulur. fork içinde Master_A, Master_B ve Master_C paralel başlatılır. Üçü de bus'a erişmek ister ama tek anahtar olduğu için sırayla erişirler; çıktıdaki "Bus erisimi istiyor" / "KILITLENDI" / "SERBEST" sırası bu karşılıklı dışlamayı gösterir. join ile hepsinin bitmesi beklenir.
  • mem_agent görevi: mem_lock.try_get(1) ile bloklamadan dener. Anahtar varsa belleğe erişir ve #delay sonrası mem_lock.put(1) ile bırakır; anahtar yoksa beklemek yerine "Bellek MESGUL, atliyor" deyip geçer. Bu, get ile try_get arasındaki farkın güzel bir örneğidir.
  • Çoklu port bölümü: mem_lock = new(2) ile iki anahtar verilir; yani 2 ajan aynı anda belleğe erişebilir. Port_1, Port_2, Port_3 paralel çalışır ve üçüncüsü çoğunlukla anahtar bulamayıp atlar.

Önemli Noktalar

  • new(1) ile oluşturulan semaphore bir mutex gibi davranır ve paylaşılan tek bir kaynağı korumanın en doğru yoludur.
  • Aldığınız anahtarı mutlaka geri verin (put); aksi halde anahtar havuzu tükenir ve diğer süreçler sonsuza kadar bloklanır (deadlock).
  • get bloklar, try_get bloklamaz: işlemin beklemesini istiyorsanız get, "kaynak meşgulse atla" davranışı istiyorsanız try_get kullanın.
  • Anahtar sayısı (new(N)) doğrudan eş zamanlı erişim sınırını belirler; donanımdaki port/kaynak sayısına göre seçilmelidir.
  • get(n)/put(n) ile birden fazla anahtar alıp verebilirsiniz; ancak aldığınız ve verdiğiniz anahtar sayılarının dengeli olmasına dikkat edin.
  • Semaphore yalnızca erişimi düzenler, veri taşımaz; aktarılacak veri için (burada shared_bus_data gibi) ayrı bir paylaşılan değişken kullanılır.
  • put(n) ile başlangıçta verilenden fazla anahtar koymak mümkündür (new(1) sonra iki kez put(1) → 2 anahtar). Semaphore bunu denetlemez; "aldığın kadar ver" disiplini tamamen size aittir. Bu, mutex'in kazara iki sürece açılmasının klasik nedenidir.
  • get(2) isteyen bir süreç, havuzda 1 anahtar varken bekler; bu sırada get(1) isteyen başka bir süreç o tek anahtarı alabilir. Yani semaphore FIFO adaleti garanti etmez; büyük istekler sürekli küçük isteklerin gerisinde kalabilir (starvation).

IPC Üçlüsünü Ayırt Etmek

Soru Araç Örnek cümle
"Bir şey oldu mu?" event "Reset bitti."
"Şu veriyi al." mailbox "İşte sürülecek transaction."
"Sıra bende mi?" semaphore "Bus'ı kullanma izni."

Üçünü birleştiren gerçekçi bir sahne: driver, generator'dan mailbox ile transaction alır, bus'ı sürmek için semaphore anahtarı ister, işi bitince monitor'e event ile "paket gitti" der.

Benzetme: Semaphore, bir kütüphanede ödünç verilen okuma odası anahtarlarıdır. 2 anahtar varsa aynı anda 2 kişi çalışabilir; üçüncü kişi ya kapıda bekler (get) ya da "doluysa kafeye giderim" der (try_get). Anahtarı iade etmeyen biri (put unutuldu) odayı sonsuza kadar kilitler.

Doğrulamada Nerede Kullanılır?

  • Çok master'lı bus modeli: Birden fazla driver aynı interface'i sürüyorsa, bir transaction'ın ortasında başka driver'ın araya girmesini new(1) ile engellersiniz.
  • Sınırlı kaynak taklidi: DUT'un 4 kanalı varsa new(4) ile aynı anda en fazla 4 işlem yürütür, beşincisini bekletirsiniz.
  • UVM'de karşılığı: Sequencer'ın lock()/grab() mekanizması, bir sequence'in sequencer'ı tek başına kullanmasını sağlar; mantık tamamen semaphore mantığıdır. UVM'de ayrıca uvm_pool/semaphore kombinasyonları paylaşılan kaynak yönetiminde kullanılır.

Sık Yapılan Hatalar

  • put unutmak: Özellikle return ile erken çıkan ya da disable ile öldürülen süreçlerde anahtar geri verilmez → deadlock. Kritik bölgeyi kısa tutun ve her çıkış yolunda put olduğundan emin olun.
  • Fazla put: Mutex iki kez açılır, iki süreç aynı anda kritik bölgeye girer; veri bozulur ama hata mesajı alınmaz.
  • try_get sonucunu kontrol etmemek: void'(mem_lock.try_get(1)); yazıp devam eden süreç, anahtar alamadığı hâlde korumalı bölgeye girer.
  • Semaphore'u new() etmeden kullanmak: null handle üzerinden get çağrısı çalışma zamanı hatasıdır; new(N) çağrısını bileşen kurulumunda yapın.
  • Semaphore ile veri taşımaya çalışmak: Anahtar sayısı bir sayaçtır, mesaj değildir. Veri için mailbox kullanın.

Kendinizi Deneyin

  1. bus_lock = new(1) yerine new(2) yapın. Çıktıda iki master'ın aynı anda "KILITLENDI" yazdığı anları bulun; shared_bus_data'nın hangi master'a ait olduğu belirsizleşti mi?
  2. bus_agent içindeki bus_lock.put(1); satırını silin. Simülasyon nerede takıldı? Hangi master'lar hiç bus alamadı?
  3. mem_agent'ta try_get yerine get kullanın. "MESGUL, atliyor" mesajları kayboldu mu? Zamanlama nasıl değişti?
  4. Master_A için bus_lock.get(1) yerine bus_lock.get(2) yazın (mutex'te 2 anahtar istemek). Ne oldu? Bu davranış starvation kavramını nasıl gösteriyor?
Hızlı Kontrol: semaphore s = new(1); ile oluşturulan yapıya ne ad verilir ve neden?

Mutex (karşılıklı dışlama kilidi). Tek anahtar olduğu için aynı anda yalnızca bir süreç get(1) ile kritik bölgeye girebilir; diğerleri anahtar put(1) ile iade edilene kadar bekler.