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.