EDA Playground'da Dene

Constraint Blokları

Gün 3: Rastgele Üretim ve Kısıtlamalar | inside, dist, if-else, implication (->) kısıtlamaları

Bu derste constraint bloklarının en sık kullanılan yapı taşlarını öğreneceğiz: değer kümesi sınırlayan inside, olasılık dağılımı belirleyen dist, koşullu kısıtlama kuran if-else ve bir koşuldan sonuç türeten implication (->) operatörü.

Constraint Bloğu Nedir?

Bir constraint bloğu, bir sınıfın rastgele değişkenlerinin hangi değerleri alabileceğini tanımlayan kurallar kümesidir. randomize() çağrıldığında çözücü (solver), tüm aktif constraint bloklarını aynı anda sağlayan bir değer kombinasyonu bulmaya çalışır. Kısıtlamalar bildirimseldir (declarative): "ne olması gerektiğini" söylersiniz, "nasıl çözüleceğini" değil.

inside

inside operatörü bir değişkeni belirli bir değer kümesi veya aralık(lar)ı ile sınırlar. Birden fazla aralık verilebilir ve ! ile olumsuzlanarak (!(x inside {...})) bir kümenin dışında kalması da istenebilir.

dist — Olasılık Dağılımı

dist, değerlerin hangi olasılıkla seçileceğini belirler. İki ağırlık operatörü vardır:

  • := → ağırlık her bir değere ayrı ayrı uygulanır.
  • :/ → ağırlık, belirtilen aralıktaki tüm değerlere eşit olarak bölüştürülür.

if-else ve implication (->)

  • if-else kısıtlamaları, bir alanın değerine göre başka alanlara koşullu kurallar uygulamayı sağlar.
  • Implication (->) ise "A ise B de olmalı" mantığını ifade eder. (koşul) -> (sonuç) biçiminde yazılır; koşul doğruysa sonuç da zorlanır, koşul yanlışsa sonuç üzerinde herhangi bir kısıtlama olmaz.

Kaynak Kod

// =============================================================================
// GUN 3 - Konu 3: Kisitlama Bloklari: inside, dist, if-else, implication
// =============================================================================

class ConfigPacket;
  typedef enum logic [1:0] {IDLE, READ, WRITE, BURST} cmd_e;

  rand cmd_e         command;
  rand logic [15:0]  address;
  rand logic [31:0]  data;
  rand int unsigned  length;
  rand bit [7:0]     prio;
  rand bit           valid;

  // --- inside: Deger kumesi kisitlamasi ---
  constraint c_addr_inside {
    address inside {[16'h0000:16'h0FFF],    // Bolge 1
                    [16'h2000:16'h2FFF],    // Bolge 2
                    [16'hF000:16'hFFFF]};   // Bolge 3
  }

  // --- dist: Olasilik dagilimi ---
  constraint c_cmd_dist {
    command dist {
      READ   := 40,   // %40 okuma
      WRITE  := 40,   // %40 yazma
      BURST  := 15,   // %15 burst
      IDLE   := 5     // %5  bosta
    };
  }

  constraint c_priority_dist {
    prio dist {
      [0:3]   :/ 60,   // Dusuk oncelik - %60 (:/ esit dagilir: her biri %15)
      [4:6]   :/ 30,   // Orta oncelik - %30
      7       := 10    // Yuksek oncelik - %10
    };
  }

  // --- if-else: Kosullu kisitlama ---
  constraint c_data_ifelse {
    if (command == READ) {
      data == 32'h0;                        // Okumada veri sifir
      length inside {[1:4]};
    } else if (command == WRITE) {
      data != 32'h0;                        // Yazmada veri sifir olamaz
      length inside {[1:8]};
    } else if (command == BURST) {
      length inside {[4:16]};               // Burst uzun
    } else {
      length == 0;                          // IDLE
      data == 0;
    }
  }

  // --- implication (->): Sonuc kisitlamasi ---
  constraint c_valid_impl {
    (command != IDLE) -> (valid == 1);      // IDLE degilse gecerli olmali
    (command == IDLE) -> (valid == 0);
  }

  constraint c_burst_addr {
    (command == BURST) -> (address[3:0] == 4'h0);  // Burst hizali adres
  }

  // --- Sabit deger dislama ---
  constraint c_no_zero_addr {
    address != 16'h0000;
  }

  function void display();
    $display("  CMD=%-5s | Addr=0x%04h | Data=0x%08h | Len=%2d | Pri=%0d | V=%0b",
             command.name(), address, data, length, prio, valid);
  endfunction
endclass

module constraint_bloklari;
  initial begin
    ConfigPacket pkt;
    int cmd_counts [string];

    $display("=== Constraint Bloklari ===\n");
    pkt = new();

    // --- Kisitlamali rastgele uretim ---
    $display("--- 20 Rastgele Paket ---");
    cmd_counts["IDLE"]  = 0;
    cmd_counts["READ"]  = 0;
    cmd_counts["WRITE"] = 0;
    cmd_counts["BURST"] = 0;

    repeat (20) begin
      assert(pkt.randomize()) else $fatal(1, "Randomize hata!");
      pkt.display();
      cmd_counts[pkt.command.name()]++;
    end

    // --- Dagilim istatistikleri ---
    $display("\n--- Komut Dagilimi (20 deneme) ---");
    foreach (cmd_counts[key])
      $display("  %s: %0d kez (%%%0d)", key, cmd_counts[key], cmd_counts[key]*5);

    // --- inside dislama (~inside) ---
    $display("\n--- Adres Dislama (~inside) ---");
    repeat (5) begin
      assert(pkt.randomize() with {
        !(address inside {[16'h0000:16'h00FF]});
      });
      $display("  Addr = 0x%04h (0x0000-0x00FF disinda)", pkt.address);
    end

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

Kodun Açıklaması

  • ConfigPacket sınıfı, cmd_e adında bir enum (IDLE, READ, WRITE, BURST) ve command, address, data, length, prio, valid rastgele üyelerini içerir.
  • c_addr_inside constraint'i address'i üç ayrı bölgeyle sınırlar: 0x0000–0x0FFF, 0x2000–0x2FFF ve 0xF000–0xFFFF. Bu, çoklu aralıklı inside kullanımının tipik örneğidir.
  • c_cmd_dist, command enum'una bir dağılım atar: READ ve WRITE %40'ar, BURST %15, IDLE %5. Burada := operatörü kullanıldığı için her ağırlık o tek değere uygulanır.
  • c_priority_dist, :/ operatörünü gösterir: [0:3] :/ 60 ifadesinde %60'lık ağırlık dört değere eşit bölünür (her biri ~%15). 7 := 10 ise tek değere %10 verir.
  • c_data_ifelse, command değerine göre koşullu kurallar uygular. Örneğin READ durumunda data == 0 ve length inside {[1:4]}; WRITE durumunda data != 0 ve length inside {[1:8]} olur.
  • c_valid_impl implication kullanır: (command != IDLE) -> (valid == 1) ve (command == IDLE) -> (valid == 0). Böylece IDLE dışındaki komutlarda valid her zaman 1, IDLE'da 0 olur.
  • c_burst_addr, BURST komutunda address[3:0] == 4'h0 zorlayarak hizalı (aligned) adres garantisi verir.
  • c_no_zero_addr, address != 16'h0000 ile sıfır adresi tamamen dışlar.
  • module içinde 20 paket üretilir ve cmd_counts adlı string-indeksli ilişkisel dizide her komutun kaç kez çıktığı sayılır; ardından dağılım yüzdeleri (cmd_counts[key]*5) yazdırılır.
  • Son bölümde randomize() with { !(address inside {[16'h0000:16'h00FF]}); } ile satır içi olarak belirli bir adres aralığı dışlanır.

Önemli Noktalar

  • := ile :/ farkını karıştırmayın. := ağırlığı her değere ayrı uygularken, :/ ağırlığı aralıktaki değerlere eşit böler. Yanlış operatör beklenmedik dağılımlara yol açar.
  • Implication (->) tek yönlüdür. (A) -> (B), yalnızca A doğruyken B'yi zorlar; A yanlışsa B serbesttir. Çift yönlü ilişki için her iki yönü de yazmak gerekir (kodda IDLE için yapıldığı gibi).
  • Tüm aktif constraint blocları aynı anda sağlanır. Çözücü, bloklar arasında çelişki varsa randomize'ı başarısız kılar; bu yüzden if-else, inside ve implication kurallarının birbiriyle tutarlı olduğundan emin olun.
  • Dağılım istatistiklerini doğrulamak için yeterli örnek üretin. 20 deneme dağılımın genel eğilimini gösterir, ancak küçük örneklemde gerçek yüzdelerden sapma normaldir.
  • inside aralıklarının kümülatif olarak en az bir geçerli değer içermesine dikkat edin; aksi halde diğer kısıtlamalarla birleştiğinde çözümsüzlük oluşabilir.
  • dist yalnızca bir tercihtir, garanti değil. command dist { READ := 40, ... } yazmanız, başka bir kısıt READ'i dışladığında çözücünün yine de geçerli bir komut seçmesine engel olmaz; yüzdeler yalnızca kalan geçerli değerler arasında uygulanır.
  • Kısıtlar çift yönlüdür: c_data_ifelse yalnızca "komut READ ise veri 0" demez; çözücü data'yı önce 0 dışı bir değere karar verdiyse command'ın READ olamayacağını da çıkarır. Bu yüzden if-else ve implication'ı prosedürel kod gibi değil, cebirsel denklemler gibi okuyun.

Kısıt Operatörleri Özet Tablosu

Yapı Anlamı Örnek
inside { } Küme/aralık üyeliği addr inside {[0:15], 32, 64};
!( inside ) Kümenin dışında !(addr inside {[0:255]});
dist { := } Her değere ayrı ağırlık op dist { RD := 3, WR := 1 };
dist { :/ } Ağırlık aralığa eşit bölünür len dist { [1:4] :/ 80, [5:8] :/ 20 };
-> Tek yönlü koşul (implication) (op == RD) -> (data == 0);
if ... else Koşullu kısıt bloğu if (op == BURST) { len > 1; }
<-> Çift yönlü eşdeğerlik (op == IDLE) <-> (valid == 0);
soft Çelişirse sessizce vazgeçilir soft len == 4;
solve a before b Çözüm sırası (dağılımı etkiler) solve op before data;
unique { } Elemanlar birbirinden farklı unique { a, b, c };
foreach Dizi elemanlarına kısıt Sonraki ders

İki pratik ekleme:

  • <-> (eşdeğerlik): Koddaki c_valid_impl bloğundaki iki implication tek satırda yazılabilir: (command != IDLE) <-> (valid == 1);. Çift yönlü ilişki için bu daha okunaklıdır.
  • soft kısıtlar: soft length == 4; yazarsanız çözücü mümkün olduğunda uzunluğu 4 seçer, ama bir with { length == 8; } ile çelişirse hata vermeden soft'u göz ardı eder. Varsayılan davranış tanımlamanın en zarif yoludur; UVM sequence'lerinde çok kullanılır.

Benzetme: Kısıt bloğu, bir restoranda garsona verilen sipariş notu gibidir: "et yemeği olsun (inside), %40 ihtimalle balık (dist), vejetaryen seçildiyse et olmasın (->), mümkünse acılı (soft)". Garson (çözücü) tüm notlara aynı anda uyan bir tabak getirir; birbirine zıt iki not yazarsanız mutfaktan boş döner.

Sık Yapılan Hatalar

  • := ile :/'yi karıştırmak: [0:3] := 60 yazmak her bir değere 60 ağırlık verir (toplam 240); [0:3] :/ 60 ise 4 değere toplam 60 (her biri 15). Dağılım tamamen değişir.
  • Implication'ı çift yönlü sanmak: (a) -> (b) ifadesi b doğruyken a hakkında hiçbir şey söylemez. Çift yön için <-> kullanın.
  • Çelişen implication'lar: (cmd == READ) -> (data == 0) ile data != 0 sabit kısıtı birlikte varsa READ hiç üretilemez; dist yüzdeniz sessizce bozulur.
  • Kısıt içinde prosedürel ifade kullanmak: constraint c { if (x > 5) y = 3; } geçersizdir; kısıtlar atama değil eşitlik ifade eder (y == 3).
  • Yüzdeleri az örnekle doğrulamak: 20 denemede %40'ı tam göremezsiniz; dağılım testlerinde en az birkaç yüz örnek kullanın.

Kendinizi Deneyin

  1. c_valid_impl bloğunu (command != IDLE) <-> (valid == 1); tek satırına indirgeyin ve davranışın aynı kaldığını gösterin.
  2. c_priority_dist'teki :/'leri := yapın; 200 paket üretip prio dağılımını bir ilişkisel dizide sayın. Yüzdeler nasıl değişti?
  3. soft length == 4; kısıtı ekleyin; normal üretimde length'in hep 4 çıktığını, with { length == 10; } ile çağrıldığında ise hata vermeden 10 olduğunu gösterin.
  4. repeat (20)'yi repeat (1000) yapın ve cmd_counts yüzdelerinin hedef dağılıma (40/40/15/5) ne kadar yaklaştığını not edin.
Hızlı Kontrol: x dist { [1:4] :/ 80, 5 := 20 } ifadesinde 2 değerinin seçilme olasılığı yaklaşık yüzde kaçtır?

Yaklaşık %20. :/ ile 80 ağırlık dört değere eşit bölünür (her biri 20); 5 değeri ise := ile tek başına 20 alır. Toplam 100 ağırlık içinde 2'nin payı 20/100'dür.