EDA Playground'da Dene

Lab 3: Generator Sınıfı

Gün 3: Rastgele Üretim ve Kısıtlamalar | Senaryoya uygun rastgele veri üreten Generator tasarımı

Bu lab çalışmasında, Gün 3 boyunca öğrendiğimiz tüm constrained-random tekniklerini tek bir uygulamada birleştiriyoruz: kısıtlamalı bir transaction sınıfı ve onu farklı senaryolarda sürebilen bir Generator sınıfı tasarlayacağız. Bu yapı, gerçek doğrulama ortamlarının (testbench) çekirdeğini oluşturur.

Generator Deseni

Bir Generator (üretici), test sırasında tasarıma uygulanacak rastgele transaction'ları üreten bileşendir. Tipik olarak:

  • İçinde bir veya daha fazla transaction nesnesi tutar.
  • Belirli sayıda transaction'ı, belirli bir senaryoya göre rastgeleleştirir.
  • Senaryolar arasında geçiş yaparken constraint_mode() ve satır içi with kısıtlamalarını kullanır.

Bu derste birleştirdiğimiz kavramlar:

  • rand / randc değişkenler ve enum tipleri.
  • dist ile gerçekçi komut ve hata dağılımları.
  • implication (->) ile komut tipine bağlı kurallar.
  • constraint_mode() ile senaryoya özel kısıtlama açma/kapatma.
  • post_randomize() ile türetilmiş alan hesaplama ve hata enjeksiyonu.

Senaryo Tabanlı Doğrulama

Aynı transaction sınıfını birden çok senaryoda yeniden kullanmak, doğrulama ortamlarında temel bir verimlilik kaynağıdır. Generator, her senaryo için ayrı bir task tanımlayarak (normal trafik, sadece okuma, yüksek adres, hata enjeksiyonu) tek bir veri modelinden çok çeşitli test durumları üretir.

Adım Adım Görev Listesi

Çözümü okumadan önce bu adımları kendiniz tamamlamayı deneyin:

  1. Transaction: cmd_e ve status_e enum'larını, rand alanları (command, address, data, burst_length, byte_strobe, id, status) ve crc, txn_id, static count alanlarını içeren BusTransaction sınıfını yazın.
  2. Temel kısıtlar: burst_length için implication kuralları (tekil komutlarda 1, burst komutlarında 2–16) ve c_valid (hizalı adres, status == VALID, byte_strobe != 0).
  3. Adres bölgeleri: Üç ayrık bölge kısıtı yazın; kurucuda ikisini kapatın.
  4. Dağılımlar: Komut ve hata durumu için dist kısıtları; hata dağılımı kurucuda kapalı olsun.
  5. post_randomize(): crc hesaplayın ve status'a göre adres/veriyi bozun.
  6. Generator: Dört senaryo task'ı yazın; her biri değiştirdiği kısıt modunu sonunda geri alsın.
  7. Çalıştırın: Her senaryoda 8 transaction üretip çıktıyı inceleyin.

Kontrol noktası: Hata enjeksiyonu senaryosunda ADDR_ERROR satırlarının adresi ...3 ile bitmeli (hizalama bozuldu), DATA_ERROR satırlarının verisi ise tersine çevrilmiş olmalı.

Kaynak Kod

// =============================================================================
// GUN 3 - Lab 3: Generator Sinifi - Senaryoya Uygun Rastgele Veri Uretimi
// =============================================================================

class BusTransaction;
  typedef enum logic [1:0] {READ=0, WRITE=1, BURST_READ=2, BURST_WRITE=3} cmd_e;
  typedef enum {VALID, ADDR_ERROR, DATA_ERROR, PROTOCOL_ERROR} status_e;

  rand cmd_e         command;
  rand logic [31:0]  address;
  rand logic [31:0]  data;
  rand int unsigned  burst_length;
  rand bit [3:0]     byte_strobe;
  rand bit [2:0]     id;
  rand status_e      status;
  
  bit [31:0] crc;
  int txn_id;
  static int count = 0;

  // --- Temel kisitlamalar ---
  constraint c_base {
    burst_length inside {[1:16]};
    (command inside {READ, WRITE}) -> burst_length == 1;
    (command inside {BURST_READ, BURST_WRITE}) -> burst_length inside {[2:16]};
  }

  // --- Gecerli paket ---
  constraint c_valid {
    status == VALID;
    address[1:0] == 2'b00;  // 4-byte hizali
    byte_strobe != 0;
  }

  // --- Adres araliklari ---
  constraint c_addr_region_A {
    address inside {[32'h0000_0000:32'h0000_FFFF]};
  }
  constraint c_addr_region_B {
    address inside {[32'h1000_0000:32'h1000_FFFF]};
  }
  constraint c_addr_region_C {
    address inside {[32'hFFFF_0000:32'hFFFF_FFFF]};
  }

  // --- Komut dagilimi ---
  constraint c_cmd_dist {
    command dist { READ := 30, WRITE := 30, BURST_READ := 20, BURST_WRITE := 20 };
  }

  // --- Hata enjeksiyonu ---
  constraint c_inject_error {
    status dist { VALID := 80, ADDR_ERROR := 10, DATA_ERROR := 5, PROTOCOL_ERROR := 5 };
  }

  function new();
    txn_id = count++;
    c_addr_region_B.constraint_mode(0);
    c_addr_region_C.constraint_mode(0);
    c_inject_error.constraint_mode(0);
  endfunction

  function void post_randomize();
    crc = address ^ data ^ {29'h0, command};
    if (status == ADDR_ERROR)
      address[1:0] = 2'b11;  // Hizalamayi boz
    else if (status == DATA_ERROR)
      data = ~data;
  endfunction

  function void display();
    $display("  TXN#%03d | %-11s | Addr=0x%08h | Data=0x%08h | Burst=%2d | BE=%04b | ID=%0d | %s",
             txn_id, command.name(), address, data, burst_length, 
             byte_strobe, id, status.name());
  endfunction
endclass

class Generator;
  BusTransaction txn;
  int num_transactions;
  string scenario_name;

  function new(int num = 10);
    this.num_transactions = num;
    this.txn = new();
  endfunction

  // Senaryo 1: Normal trafik
  task run_normal_traffic();
    scenario_name = "Normal Trafik";
    $display("\n=== Senaryo: %s (%0d txn) ===", scenario_name, num_transactions);
    repeat (num_transactions) begin
      assert(txn.randomize()) else $fatal(1, "Randomize hata!");
      txn.display();
    end
  endtask

  // Senaryo 2: Sadece okuma
  task run_read_only();
    scenario_name = "Sadece Okuma";
    $display("\n=== Senaryo: %s ===", scenario_name);
    repeat (num_transactions) begin
      assert(txn.randomize() with { command inside {BusTransaction::READ, BusTransaction::BURST_READ}; })
        else $fatal(1, "Randomize hata!");
      txn.display();
    end
  endtask

  // Senaryo 3: Yuksek adres bolgesi
  task run_high_address();
    scenario_name = "Yuksek Adres Bolgesi";
    $display("\n=== Senaryo: %s ===", scenario_name);
    txn.c_addr_region_A.constraint_mode(0);
    txn.c_addr_region_C.constraint_mode(1);
    repeat (num_transactions) begin
      assert(txn.randomize()) else $fatal(1, "Randomize hata!");
      txn.display();
    end
    txn.c_addr_region_A.constraint_mode(1);
    txn.c_addr_region_C.constraint_mode(0);
  endtask

  // Senaryo 4: Hata enjeksiyonu
  task run_error_injection();
    scenario_name = "Hata Enjeksiyonu";
    $display("\n=== Senaryo: %s ===", scenario_name);
    txn.c_valid.constraint_mode(0);
    txn.c_inject_error.constraint_mode(1);
    repeat (num_transactions) begin
      assert(txn.randomize()) else $fatal(1, "Randomize hata!");
      txn.display();
    end
    txn.c_valid.constraint_mode(1);
    txn.c_inject_error.constraint_mode(0);
  endtask
endclass

module lab3_generator;
  initial begin
    Generator gen;

    $display("============================================================");
    $display("  LAB 3: Generator Sinifi - Senaryolu Rastgele Uretim");
    $display("============================================================");

    gen = new(8);

    gen.run_normal_traffic();
    gen.run_read_only();
    gen.run_high_address();
    gen.run_error_injection();

    $display("\n============================================================");
    $display("  LAB 3 TAMAMLANDI");
    $display("============================================================");
    $finish;
  end
endmodule

Kodun Açıklaması

  • BusTransaction sınıfı iki enum kullanır: komut tipi için cmd_e (READ, WRITE, BURST_READ, BURST_WRITE) ve durum için status_e (VALID, ADDR_ERROR, DATA_ERROR, PROTOCOL_ERROR). Rastgele alanlar arasında command, address, data, burst_length, byte_strobe, id ve status yer alır.
  • Kısıtlamalar:
    • c_base, burst_length'i 1–16 arasına koyar ve implication ile tekil komutlarda (READ, WRITE) uzunluğu 1'e, burst komutlarında 2–16 arasına zorlar.
    • c_valid, geçerli paketler için status == VALID, 4-byte hizalı adres (address[1:0] == 2'b00) ve byte_strobe != 0 kurar.
    • c_addr_region_A/B/C, üç farklı adres bölgesi tanımlar; bunlar constraint_mode() ile teker teker açılıp kapatılacak şekilde kurgulanmıştır.
    • c_cmd_dist ve c_inject_error, sırasıyla komut ve hata durumu için dist ile olasılık dağılımları tanımlar.
  • new() kurucusu txn_id atar ve çakışmayı önlemek için c_addr_region_B, c_addr_region_C ile c_inject_error'ı baştan kapatır — yani varsayılan davranış: A bölgesi, geçerli paketler.
  • post_randomize(), türetilmiş crc alanını hesaplar ve hata durumlarına göre değer bozar: ADDR_ERROR'da adres hizasını bozar (address[1:0] = 2'b11), DATA_ERROR'da veriyi tersler (data = ~data). Bu, hatanın rastgeleleştirme sonrası enjekte edilmesinin örneğidir.
  • Generator sınıfı içinde bir BusTransaction nesnesi (txn) ve dört senaryo task'ı bulunur:
    • run_normal_traffic() varsayılan kısıtlamalarla normal trafik üretir.
    • run_read_only(), satır içi with { command inside {BusTransaction::READ, BusTransaction::BURST_READ}; } ile yalnızca okuma komutları üretir.
    • run_high_address(), c_addr_region_A'yı kapatıp c_addr_region_C'yi açar, senaryo bitince eski duruma döndürür.
    • run_error_injection(), c_valid'i kapatıp c_inject_error'ı açarak dağılımlı hata durumları üretir, sonra geri alır.
  • module içinde Generator 8 transaction ile oluşturulur ve dört senaryo sırayla çalıştırılır.

Önemli Noktalar

  • Senaryo task'ları durumu geri yükler. run_high_address() ve run_error_injection(), değiştirdikleri constraint modlarını sonunda eski haline getirir; bu, senaryoların birbirini etkilememesi için kritik bir pratiktir.
  • Enum değerlerine sınıf dışından erişirken kapsam belirtin. Satır içi kısıtlamada BusTransaction::READ gibi sinif::deger biçimi kullanılır.
  • Hata enjeksiyonunu post_randomize() içinde yapmak güçlüdür. Kısıtlamalarla geçerli bir paket üretip ardından bilinçli olarak bozmak, hem geçerli hem hatalı senaryoları tek modelden türetmeyi sağlar.
  • Çakışan adres bölgesi kısıtlamalarını aynı anda açmayın. A, B, C bölgeleri ayrık aralıklardır; aynı anda birden fazlası açıkken randomize() çözümsüz kalır.
  • dist ağırlıkları gerçekçi trafik modellemek içindir. c_cmd_dist ve c_inject_error ile üretilen dağılım, gerçek sistem davranışına yakın test senaryoları üretmenizi sağlar; istatistiksel anlamlılık için yeterli sayıda transaction üretin.
  • Bu Generator deseni, ileride UVM sequence/sequencer yapılarına geçişin temelini oluşturur; mantık aynıdır, yalnızca altyapı standartlaşır.
  • İnce bir nokta — c_valid kapalıyken hizalama: Hata senaryosunda c_valid kapatıldığı için çözücü address[1:0]'ı zaten rastgele seçer; post_randomize() ise ADDR_ERROR durumunda bunu kesin 2'b11 yapar. Yani "hatalı adres" tanımı deterministiktir ve scoreboard bunu kolayca tanıyabilir. VALID durumundaki paketlerde hizalama artık garanti değildir; gerçek bir projede c_valid'i tamamen kapatmak yerine yalnızca status == VALID satırını ayrı bir bloğa taşımak daha doğru olur. Bunu ek görevlerde yapacaksınız.
  • Generator tek bir txn nesnesini her senaryoda yeniden randomize() eder. Bu, nesne yaratma maliyetini sıfırlar; ama transaction'ları bir kuyruğa koyup saklamanız gerekirse (Lab 4'te olacağı gibi) her seferinde yeni nesne ya da copy() gerekir — aksi halde kuyruktaki tüm elemanlar aynı nesneyi gösterir.

Ek Görevler (Zorluk Artan)

  1. Kolay: run_normal_traffic() içinde komut dağılımını saymak için int hist[string] kullanın ve senaryo sonunda yüzdeleri basın. 8 yerine 400 transaction ile dist hedeflerine (30/30/20/20) yaklaşıldığını gösterin.
  2. Orta: c_valid'i ikiye bölün: c_status_valid { status == VALID; } ve c_align { address[1:0] == 0; byte_strobe != 0; }. Hata senaryosunda yalnızca c_status_valid'i kapatın; VALID kalan paketlerin hâlâ hizalı olduğunu doğrulayın.
  3. Orta: run_region_b() adında yeni bir senaryo ekleyin (B bölgesi, yalnızca WRITE/BURST_WRITE). İki farklı mekanizmayı birlikte kullanın: constraint_mode (bölge) + with (komut).
  4. Zor: Generator'a BusTransaction history[$] kuyruğu ekleyin ve her üretilen transaction'ın kopyasını saklayın (copy() metodu yazmanız gerekecek). Senaryolar bittiğinde kuyruktaki status != VALID olanları find with ile süzüp sayın. Kopya almayı unutursanız ne gözlemlersiniz?
Hızlı Kontrol: Hata enjeksiyonu senaryosunda c_valid neden kapatılmalıdır?

Çünkü c_valid içinde status == VALID kuralı vardır; bu açıkken c_inject_error'daki ADDR_ERROR := 10 gibi değerler hiç seçilemez (çelişki → randomize() başarısız). Hata üretebilmek için VALID'i zorlayan kuralın devre dışı kalması gerekir.