EDA Playground'da Dene

Fonksiyonel Kapsam

Gün 5: Arayüzler, Assertion ve Coverage | covergroup, coverpoint, bins, cross coverage

Bu derste fonksiyonel kapsam (functional coverage) ile testlerimizin tasarımın ne kadarını gerçekten denediğini nasıl ölçeceğimizi öğreneceğiz. covergroup, coverpoint, bins ve cross yapılarını bir ALU örneği üzerinden inceleyeceğiz.

Fonksiyonel Kapsam Neden Önemli?

Rastgele test (constrained random) ile binlerce işlem üretebilirsiniz, ama "her senaryoyu denedim mi?" sorusunun cevabı belirsiz kalır. Fonksiyonel kapsam, doğrulama planındaki senaryoların gerçekten gerçekleşip gerçekleşmediğini sayısal olarak ölçer:

  • Doğrulamanın bittiğine karar verdirir: Kapsam %100'e ulaşmadan doğrulama tamamlanmış sayılmaz.
  • Test boşluklarını gösterir: Hangi bin'in hiç dolmadığını görüp eksik senaryoları belirlersiniz.
  • Kod kapsamından farklıdır: Kod kapsamı "kod satırı çalıştı mı?" sorusunu, fonksiyonel kapsam ise "anlamlı senaryo test edildi mi?" sorusunu yanıtlar.

Temel Kavramlar

  • covergroup: Birbiriyle ilişkili kapsam noktalarını gruplayan yapıdır. with function sample(...) ile dışarıdan veri alarak örnekleme yapabilir.
  • coverpoint: İzlenecek bir değişkeni/ifadeyi temsil eder.
  • bins: Bir coverpoint'in değer aralıklarını kovalara böler. Her kovanın en az bir kez dolması beklenir.
  • illegal_bins: Asla oluşmaması gereken değerleri işaretler; oluşursa hata verir.
  • cross: İki coverpoint'in kombinasyonlarını ölçer (örn. her işlem × her operand aralığı). Köşe durumlarını yakalamada güçlüdür.

Kaynak Kod

// =============================================================================
// GUN 5 - Konu 5: Fonksiyonel Kapsam (Functional Coverage)
// =============================================================================

class ALU_Transaction;
  typedef enum logic [2:0] {ADD, SUB, AND, OR, XOR, SHL, SHR} op_e;

  rand op_e          operation;
  rand logic [7:0]   operand_a;
  rand logic [7:0]   operand_b;
  logic [15:0]       result;

  constraint c_operands {
    operand_a inside {[0:255]};
    operand_b inside {[0:255]};
  }

  function void compute();
    case (operation)
      ADD: result = operand_a + operand_b;
      SUB: result = operand_a - operand_b;
      AND: result = operand_a & operand_b;
      OR:  result = operand_a | operand_b;
      XOR: result = operand_a ^ operand_b;
      SHL: result = operand_a << operand_b[2:0];
      SHR: result = operand_a >> operand_b[2:0];
    endcase
  endfunction
endclass

module functional_coverage;

  ALU_Transaction txn;

  // =========================================================================
  // COVERGROUP: Kapsam grubu tanimi
  // =========================================================================
  covergroup alu_cg with function sample(
    ALU_Transaction::op_e op,
    logic [7:0] a, logic [7:0] b, logic [15:0] res
  );

    // --- COVERPOINT: Islem kapsanmasi ---
    cp_operation: coverpoint op {
      bins add_bin = {ALU_Transaction::ADD};
      bins sub_bin = {ALU_Transaction::SUB};
      bins and_bin = {ALU_Transaction::AND};
      bins or_bin  = {ALU_Transaction::OR};
      bins xor_bin = {ALU_Transaction::XOR};
      bins shl_bin = {ALU_Transaction::SHL};
      bins shr_bin = {ALU_Transaction::SHR};
    }

    // --- COVERPOINT: Operand A araliklari ---
    cp_op_a: coverpoint a {
      bins zero    = {0};
      bins low     = {[1:63]};
      bins mid     = {[64:191]};
      bins high    = {[192:254]};
      bins max_val = {255};
    }

    // --- COVERPOINT: Operand B araliklari ---
    cp_op_b: coverpoint b {
      bins zero    = {0};
      bins low     = {[1:63]};
      bins mid     = {[64:191]};
      bins high    = {[192:254]};
      bins max_val = {255};
    }

    // --- COVERPOINT: Sonuc araliklari ---
    cp_result: coverpoint res {
      bins zero     = {0};
      bins small    = {[1:255]};
      bins medium   = {[256:65534]};
      bins overflow = {65535};
    }

    // --- COVERPOINT: Kose durumlari ---
    // Yalnizca alti sinir degeri ayri ayri izlenir; diger degerler kapsam
    // hesabina katilmaz (ignore). DIKKAT: burada illegal_bins KULLANILMAZ,
    // cunku rastgele testte a'nin bu alti deger disinda olmasi tamamen normaldir.
    cp_corner: coverpoint a {
      bins boundaries[] = {0, 1, 127, 128, 254, 255};
      ignore_bins others = default;
    }

    // --- COVERPOINT: illegal_bins ornegi ---
    // Kaydirma islemlerinde b[7:3] kullanilmaz; bu bitlerin set olmasi
    // "DUT'un asla gormemesi gereken" bir durum olsaydi hata uretirdi.
    // (Buradaki rastgele uretim b'yi sinirlamadigi icin yalnizca GOSTERIM amaclidir
    //  ve bins yorumda birakilmistir; acarsaniz her ihlalde simulator hata basar.)
    cp_shift_amt: coverpoint b[2:0] iff (op inside {ALU_Transaction::SHL, ALU_Transaction::SHR}) {
      bins amt[] = {[0:7]};
      // illegal_bins bad = {[8:$]};  // b[2:0] zaten 0-7 arasi; ornek amacli
    }

    // --- CROSS: Islem x Operand A araligi ---
    cx_op_a: cross cp_operation, cp_op_a;

    // --- CROSS: Islem x Operand B araligi ---
    cx_op_b: cross cp_operation, cp_op_b;

  endgroup

  // =========================================================================
  alu_cg cg;

  initial begin
    $display("=== Fonksiyonel Kapsam (Functional Coverage) ===\n");

    txn = new();
    cg  = new();

    // Rastgele test dongusu
    $display("--- 200 Rastgele Islem ---");
    repeat (200) begin
      assert(txn.randomize()) else $fatal(1, "Randomize hata!");
      txn.compute();
      cg.sample(txn.operation, txn.operand_a, txn.operand_b, txn.result);
    end

    // Yonlendirilmis testler (kose durumlari)
    $display("--- Yonlendirilmis Kose Testleri ---");
    begin
      logic [7:0] corners [] = '{0, 1, 127, 128, 254, 255};
      foreach (corners[i]) begin
        foreach (corners[j]) begin
          assert(txn.randomize() with {
            operand_a == corners[i];
            operand_b == corners[j];
          }) else continue;
          txn.compute();
          cg.sample(txn.operation, txn.operand_a, txn.operand_b, txn.result);
        end
      end
    end

    // Kapsam raporu
    $display("\n--- Kapsam Raporu ---");
    $display("  Toplam Kapsam     : %.1f%%", cg.get_coverage());
    $display("  cp_operation      : %.1f%%", cg.cp_operation.get_coverage());
    $display("  cp_op_a           : %.1f%%", cg.cp_op_a.get_coverage());
    $display("  cp_op_b           : %.1f%%", cg.cp_op_b.get_coverage());
    $display("  cp_result         : %.1f%%", cg.cp_result.get_coverage());

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

Kodun Açıklaması

  • ALU_Transaction sınıfı: op_e enum'u (ADD, SUB, AND, OR, XOR, SHL, SHR) ile işlem türünü tanımlar. operation, operand_a, operand_b rastgele üretilir; c_operands kısıtı operandları 0-255 aralığına çeker. compute fonksiyonu seçilen işleme göre result'u hesaplar (kaydırmalarda operand_b[2:0] kullanılır).
  • covergroup alu_cg: with function sample(...) ile işlem, iki operand ve sonucu dışarıdan alır.
    • cp_operation: Yedi ALU işleminin her birini ayrı bir bin ile izler.
    • cp_op_a / cp_op_b: Operandları anlamlı aralıklara böler: zero, low [1:63], mid [64:191], high [192:254], max_val 255.
    • cp_result: Sonucu zero, small, medium, overflow aralıklarına ayırır.
    • cp_corner: bins boundaries[] = {0, 1, 127, 128, 254, 255} ile köşe değerleri tek tek izler ([] köşeli parantez, her değer için ayrı bin üretir); ignore_bins others = default ise bu altı değer dışındaki örnekleri kapsam hesabından düşer. Böylece cp_corner'ın yüzdesi yalnızca "altı sınır değerinden kaçı görüldü?" sorusunu yanıtlar.
    • cp_shift_amt: iff (...) koşulu ile yalnızca kaydırma işlemlerinde örneklenir; b[2:0] kaydırma miktarının 0–7 arası her değerini izler. Yorumdaki illegal_bins satırı, "oluşması yasak" değerlerin nasıl işaretleneceğini gösterir.
    • cx_op_a / cx_op_b: İşlem ile operand aralıklarının çapraz kombinasyonlarını (cross) ölçer.
  • functional_coverage (top): txn ve cg nesneleri oluşturulur. Önce repeat(200) döngüsünde rastgele işlemler üretilip compute edilir ve cg.sample(...) ile örneklenir. Ardından corners dizisi üzerinde iç içe foreach ile yönlendirilmiş köşe testleri çalıştırılır (randomize() with {...} ile operandlar sabitlenir). Son olarak get_coverage() çağrılarıyla toplam ve coverpoint bazlı kapsam raporlanır.

Önemli Noktalar

  • ignore_bins ile illegal_bins farklı amaçlara hizmet eder: ignore_bins değeri kapsam hesabından çıkarır ("bu değerler beni ilgilendirmiyor"); illegal_bins ise değer örneklendiğinde hata üretir ("bu değer asla oluşmamalı"). cp_corner'da rastgele operandların altı sınır değeri dışında kalması tamamen normaldir; bu yüzden ignore_bins doğru seçimdir. illegal_bins yalnızca protokol ihlali anlamına gelen değerler için (örneğin tanımsız bir opcode) kullanılmalıdır; aksi halde her rastgele örnekte sahte hata alırsınız.
  • default bin tuzağı: default bin, kalan tüm değerleri toplar ama kapsam yüzdesine sayılmaz. Anlamlı senaryoları açıkça adlandırılmış bins'lerle ifade edin; "diğerleri" için ignore_bins ... = default yazmak niyeti netleştirir.
  • iff ile koşullu örnekleme: coverpoint b[2:0] iff (op inside {SHL, SHR}) yalnızca koşul doğruyken örnek alır. Bu, "AND işleminde kaydırma miktarı" gibi anlamsız kombinasyonların kapsamı kirletmesini önler ve cross'taki ignore_bins'e alternatiftir.
  • cross ile köşe kombinasyonları: Tek tek coverpoint'ler %100 olsa bile, cx_op_a/cx_op_b çaprazları "her işlemi her aralıkla denedim mi?" sorusunu yanıtlar; gerçek boşluklar çoğunlukla burada görünür.
  • Rastgele + yönlendirilmiş birlikte: Rastgele test geniş alanı tarar, ama 255 veya 0 gibi köşe değerlerine nadiren isabet eder. Kod, bunları randomize() with ile bilinçli olarak zorlar.
  • sample()'ı doğru anda çağırın: Kapsam ancak sample çağrıldığında güncellenir; veri kararlıyken (burada compute sonrası) örneklemek doğru sonuç verir.
  • Anlamlı bins aralıkları seçin: Kovaları rastgele değil, tasarımın davranışının değiştiği sınırlara göre bölmek (örn. taşma için overflow = {65535}) kapsamı anlamlı kılar.
  • bins x[] = {...} ile bins x = {...} farkı: Köşeli parantez her değere ayrı bin açar (altı değer → altı bin, her biri ayrı ayrı dolmalı); parantezsiz yazım tüm değerleri tek bin'de toplar (altı değerden herhangi biri görülünce bin dolar). Köşe değerlerin her birini ayrı ayrı görmek istiyorsanız [] şarttır.
  • Otomatik bin'ler ve auto_bin_max: Hiç bins yazmazsanız SystemVerilog değer uzayını otomatik olarak en fazla 64 bin'e böler (option.auto_bin_max). 32-bit bir veri için bu, her biri ~67 milyon değerlik 64 anlamsız kova demektir; her zaman kendi bin'lerinizi tanımlayın.

Kapsam Türleri Bir Bakışta

Tür Soru Kim toplar? Sınırı
Kod kapsamı (line/branch/toggle) "Hangi RTL satırları çalıştı?" Simülatör otomatik Satır çalışmış ama doğru çalışmış mı bilmez
Fonksiyonel kapsam (covergroup) "Plandaki senaryolar denendi mi?" Sizin yazdığınız coverpoint/cross Yalnızca yazdığınız kadar ölçer; eksik bin = kör nokta
Assertion kapsamı (cover property) "Bu zamansal senaryo yaşandı mı?" SVA Protokol düzeyi, veri değerlerine kör

Doğrulama "bitti" demek için üçü de hedefe ulaşmalı ve regresyon hatasız olmalıdır. Yalnızca fonksiyonel kapsamı %100 yapmak, planda unuttuğunuz senaryoları görünmez kılar.

Benzetme: Fonksiyonel kapsam, bir sınavın konu listesidir: "integral çıktı mı, türev çıktı mı, limit çıktı mı?" Her konu bir bin'dir. cross ise "integral ve zor seviye aynı soruda çıktı mı?" sorusudur. Sınavdan 100 almak (testlerin geçmesi) yetmez; soruların konuları kapsamadığını fark etmezseniz öğrenci bilmediği konudan hiç sınanmamış olur.

Sık Yapılan Hatalar

  • Rastgele değişkende illegal_bins = default: Her rastgele örnek hata üretir; ignore_bins kullanın.
  • sample()'ı yanlış anda çağırmak: compute() öncesinde örneklerseniz result eski değeri taşır ve cp_result yanlış dolar.
  • cross'ta anlamsız kombinasyonları dışlamamak: "NOT işlemi × operand_b = 255" gibi kombinasyonlar NOT tek operandlı olduğu için anlamsızdır; ignore_bins ile dışlanmazsa %100'e ulaşmak imkânsız görünür ya da gereksiz test yazarsınız.
  • Covergroup'u new() etmemek: cg = new(); olmadan cg.sample() çalışma zamanı hatası verir.
  • Kapsamı yalnızca stimulus'tan toplamak: Driver'ın gönderdiğini örneklemek "ne gönderdik?" sorusunu yanıtlar, "DUT ne gördü?" sorusunu değil. Gerçek projelerde kapsam monitor gözleminden toplanır (Gün 7 projesinde scoreboard actual ile besler).

Kendinizi Deneyin

  1. cp_corner'daki ignore_bins'i illegal_bins yapın ve çalıştırın. Kaç hata mesajı aldınız? Neden bu kullanım yanlıştır?
  2. bins boundaries[]'den []'yi silin; cp_corner kapsamı tek bir örnekten sonra kaç yüzde oldu?
  3. cx_op_a cross'una ignore_bins not_a = binsof(cp_operation) intersect {ALU_Transaction::NOT} && binsof(cp_op_a.zero); benzeri bir dışlama ekleyin ve kapsam yüzdesinin nasıl değiştiğini gözlemleyin (ipucu: NOT için operand_b anlamsızdır; cx_op_b'de dışlamak daha mantıklı).
  4. repeat (200)'ü repeat (20) yapın. Hangi bin'ler boş kaldı? cg.cp_op_a.get_coverage() ile yönlendirilmiş köşe testleri olmadan ve olduğu şekilde karşılaştırın.
Hızlı Kontrol: ignore_bins ile illegal_bins arasındaki fark nedir?

ignore_bins değerleri kapsam hesabından çıkarır; örneklendiğinde hiçbir şey olmaz. illegal_bins ise değer örneklendiğinde hata üretir: "bu asla olmamalıydı" demektir. Rastgele üretimde doğal olarak oluşabilen değerler için ignore_bins, gerçek protokol ihlalleri için illegal_bins kullanılır.