EDA Playground'da Dene

rand ve randc

Gün 3: Rastgele Üretim ve Kısıtlamalar | Rastgele değişkenler: rand (rastgele) ve randc (döngüsel)

Bu derste SystemVerilog'un kısıtlanmış-rastgele (constrained-random) doğrulama altyapısının temel yapı taşı olan rastgele değişkenleri öğreneceğiz: rand ve randc niteleyicileri ile std::randomize fonksiyonunu, bunların farklarını ve nerede kullanılacağını ele alacağız.

Neden Rastgele Üretim?

Modern donanım doğrulamasında, bir tasarımın tüm köşe durumlarını (corner case) elle yazılmış yönlü (directed) testlerle yakalamak neredeyse imkânsızdır. Bunun yerine kısıtlanmış-rastgele doğrulama yaklaşımı kullanılır: değişkenlere rastgele ama anlamlı (kısıtlanmış) değerler atayarak çok sayıda senaryo otomatik olarak üretilir. Bu sayede mühendisin aklına gelmeyen kombinasyonlar da test edilir.

SystemVerilog'da bir sınıf üyesini rastgele yapmak için iki niteleyici vardır:

  • rand: Her randomize() çağrısında değişkene, izin verilen aralık içinden bağımsız ve rastgele bir değer atar. Aynı değer arka arkaya birden fazla kez gelebilir (tekrar olabilir).
  • randc (cyclic random / döngüsel rastgele): Olası tüm değerleri bir kez üretmeden hiçbirini tekrar etmez. Tüm değerler tükendiğinde döngü baştan başlar ve yeni bir rastgele sıra ile devam eder. Genellikle dar bit genişlikli alanlar (örneğin adres seçici, kanal numarası) için kullanılır.

rand vs randc

  • rand değişkenleri bellek/performans açısından ucuzdur ve çoğu durumda tercih edilir.
  • randc değişkenleri tüm değer uzayını tarama garantisi verir; ancak bu uzayın küçük olması beklenir (örneğin 3 bit = 8 değer). Çok geniş randc alanları kaçınılması gereken bir kalıptır.

std::randomize

Sınıf dışındaki yerel değişkenleri rastgeleleştirmek için sınıfa bağlı olmayan std::randomize() fonksiyonu kullanılır. Buna with { ... } ekleyerek satır içi (inline) kısıtlamalar da verilebilir.

Kaynak Kod

// =============================================================================
// GUN 3 - Konu 1: Rastgele Test ve rand/randc Degiskenleri
// =============================================================================

class Packet;
  // rand: Her randomize() cagrisinda rastgele deger uretir
  rand bit [7:0]  data;
  rand bit [3:0]  addr;
  rand bit [1:0]  burst_type;
  
  // randc: Tum olasi degerleri tuketmeden tekrar etmez (cyclic random)
  randc bit [2:0] prio;  // 0-7 arasi, her biri bir kez, sonra tekrar

  // Rastgele olmayan degiskenler
  int id;
  static int count = 0;

  function new();
    this.id = count++;
  endfunction

  function void display();
    $display("  Paket #%0d: data=0x%02h, addr=0x%01h, burst=%0d, priority=%0d",
             id, data, addr, burst_type, prio);
  endfunction
endclass

module rand_ve_randc;
  initial begin
    Packet pkt;
    Packet pkt2;//randc gösterimi için
    
    $display("=== rand ve randc Degiskenleri ===\n");

    pkt = new();
    pkt2 = new();

    // --- rand: Her seferinde rastgele ---
    $display("--- rand: 10 rastgele paket ---");
    repeat (10) begin
      if (!pkt.randomize())
        $fatal(1, "Randomize basarisiz!");
      pkt.display();
    end

    // --- randc: Dongusel rastgele ---
    $display("\n--- randc: priority (0-7 dongusel) ---");
    $display("  Ilk dongu (8 deger):");
    repeat (8) begin
      void'(pkt2.randomize());
      $display("    priority = %0d", pkt2.prio);
    end
    $display("  Ikinci dongu (tekrar 8 deger, farkli sira):");
    repeat (8) begin
      void'(pkt2.randomize());
      $display("    priority = %0d", pkt2.prio);
    end

    // --- std::randomize (sinif disinda) ---
    $display("\n--- std::randomize (Bagimsiz) ---");
    begin
      int x, y;
      repeat (5) begin
        if (!std::randomize(x, y) with { x inside {[1:100]}; y < x; })
          $fatal(1, "std::randomize basarisiz!");
        $display("  x = %0d, y = %0d", x, y);
      end
    end

    $display("\n=== rand ve randc Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • Packet sınıfında üç adet rand değişkeni (data, addr, burst_type) ve bir adet randc değişkeni (prio) tanımlanmıştır. id ve statik count ise rastgele olmayan, normal üyelerdir.
  • new() kurucusunda her nesne için count++ ile artan bir id atanır; bu, üretilen paketleri birbirinden ayırmak için kullanılır.
  • İlk repeat (10) döngüsünde pkt.randomize() çağrılır. Dönüş değeri kontrol edilir; başarısız olursa $fatal ile simülasyon durdurulur. Her çağrıda data, addr ve burst_type bağımsız rastgele değerler alır.
  • İkinci bölümde pkt2 üzerindeki randc değişkeni prio gösterilir. İlk 8 çağrıda 0–7 arası her değer tam bir kez üretilir. İkinci 8 çağrıda yine tüm değerler çıkar, ancak farklı bir sırada — döngüsel davranışın kanıtı budur.
  • void'(pkt2.randomize()) ifadesindeki void'(...) cast'i, fonksiyonun dönüş değerini bilinçli olarak yok saymak içindir.
  • Son bölümde std::randomize(x, y) with { x inside {[1:100]}; y < x; } kullanılarak sınıfa ait olmayan iki yerel int değişkeni satır içi kısıtlamalarla rastgeleleştirilir: x 1–100 arasında, y ise x'ten küçük olur.

Önemli Noktalar

  • randomize() dönüş değerini her zaman kontrol edin. Başarısız bir randomize (kısıtlama çakışması) sessizce eski değerleri bırakır; bu yüzden if (!...) veya assert(...) kullanmak şarttır.
  • randc alanlarını dar tutun. Geniş bit genişliği, tüm değer uzayının izlenmesi nedeniyle performansı ciddi şekilde düşürür.
  • randc, tüm değerler tükenene kadar tekrar üretmez; bu yüzden "her değeri bir kez gör" tipi senaryolarda idealdir.
  • void'(...) cast'i ile dönüş değerini yok saymak yalnızca randomize'ın başarılı olacağından emin olduğunuz durumlarda tercih edilmelidir.
  • std::randomize, hızlı testlerde veya sınıf tanımlamadan birkaç değişkeni rastgeleleştirmek istediğinizde pratiktir, ancak büyük doğrulama ortamlarında sınıf tabanlı yaklaşım daha sürdürülebilirdir.
  • Tekrarlanabilirlik (reproducibility): Rastgele üretim aslında sözde rastgeledir; aynı tohum (seed) ile aynı simülasyon birebir aynı değerleri üretir. Bir hata bulduğunuzda simülatörün yazdırdığı seed'i not alıp (+ntb_random_seed=, -svseed, +UVM_SEED gibi araca göre değişen seçeneklerle) hatayı yeniden üretebilirsiniz. "Rastgele test tekrar edilemez" düşüncesi bir efsanedir.
  • Rastgele sayı üreteci (RNG) her nesneye ve her sürece (thread) ayrı bağlıdır. Bu yüzden pkt.randomize() ile pkt2.randomize() birbirinden bağımsız dizilere sahiptir; pkt2 üzerindeki randc döngüsü pkt'nin çağrılarından etkilenmez. Sınıf tanımına yeni bir rand alan eklemek bile dizileri değiştirebilir; bunun için srandom(seed) metoduyla nesne tohumunu sabitleyebilirsiniz.

rand ve randc Yan Yana

rand randc
Tekrar Aynı değer art arda gelebilir Tüm değerler bitmeden tekrar etmez
Değer uzayı Sınırsız (tipin tamamı) Küçük tutulmalı (tipik ≤ 8–16 bit)
Kısıtlama Her tür constraint inside gibi basit kısıtlar; dist kullanılamaz
Çözüm sırası Diğer değişkenlerle birlikte Önce çözülür, sonra rand'lar ona göre ayarlanır
Tipik kullanım Veri, adres, uzunluk Kanal numarası, port seçici, "her birini bir kez dene" senaryoları
Bellek maliyeti Düşük Değer uzayı kadar (izleme listesi)

Benzetme: rand bir zar atmaktır: 6 kez attığınızda üçü 4 gelebilir. randc ise 1'den 6'ya numaralı kartları karıştırıp sırayla çekmektir: 6 çekişte her kart bir kez çıkar, deste bitince yeniden karıştırılır.

Sık Yapılan Hatalar

  • Dönüş değerini yok saymak: pkt.randomize(); tek başına yazıp başarısızlığı görmezden gelmek, eski değerlerle sessizce devam etmek demektir. if (!pkt.randomize()) $fatal(...) veya assert(...) kullanın.
  • randc değişkene dist yazmak: Derleme hatasıdır; randc zaten eşit dağılım garantisi verir.
  • Geniş randc alanı: randc bit [31:0] addr; 4 milyar değeri izlemeye çalışır; simülasyon belleği ve hızı çöker. randc'yi dar alanlara saklayın.
  • rand olmayan alanın rastgele değişmesini beklemek: id ve count gibi alanlar rand değildir; randomize() onlara dokunmaz. Bu bilinçli bir tercihtir (kimlik rastgele olmamalı).
  • Her çağrıda yeni nesne yaratmayı unutmak / gereksiz yaratmak: Aynı nesneyi tekrar tekrar randomize() etmek geçerlidir ve ucuzdur; ancak nesneyi bir kuyruğa koyup sonra tekrar randomize() ederseniz kuyruktaki "eski" paket de değişir (handle paylaşımı). Kuyruğa koyacaksanız yeni nesne yaratın veya kopya alın.

Kendinizi Deneyin

  1. prio alanını randc bit [1:0] yapın (4 değer) ve 12 çağrıda üç tam döngü gördüğünüzü doğrulayın.
  2. pkt2.randomize() yerine pkt.randomize() ile prio yazdırın; pkt'nin kendi randc döngüsünün bağımsız ilerlediğini gözlemleyin.
  3. Simülatörünüzün seed parametresini bulun ve aynı seed ile iki kez çalıştırın: çıktılar birebir aynı mı? Seed'i değiştirip tekrar bakın.
  4. std::randomize(x, y) with { x inside {[1:100]}; y < x; } içine y > 0; ekleyin ve y'nin artık negatif olamadığını gösterin. Önceki hâlde y neden çok büyük negatif değerler alabiliyordu?
Hızlı Kontrol: randc bit [2:0] prio için 8 ardışık randomize() çağrısında aynı değer iki kez gelebilir mi?

Hayır. randc, 0–7 arasındaki 8 değerin her birini bir kez üretmeden tekrar etmez. Dokuzuncu çağrıda yeni bir rastgele permütasyon başlar; ancak 8. ile 9. değer tesadüfen aynı olabilir (bir döngünün sonu ile diğerinin başı).