EDA Playground'da Dene

pre/post_randomize()

Gün 3: Rastgele Üretim ve Kısıtlamalar | Randomize öncesi ve sonrası otomatik çağrılan fonksiyonlar

Bu derste randomize() çağrısının hemen öncesinde ve sonrasında otomatik olarak tetiklenen iki özel kanca (hook) fonksiyonunu öğreneceğiz: pre_randomize() ve post_randomize(). Bunlar, rastgeleleştirme sürecine kod enjekte etmenin standart yoludur.

pre_randomize() ve post_randomize()

SystemVerilog, her randomize() çağrısında otomatik olarak şu sırayı izler:

  1. pre_randomize() çağrılır — henüz değişkenlere rastgele değer atanmadan önce.
  2. Çözücü, kısıtlamaları çözer ve rastgele değişkenleri günceller.
  3. post_randomize() çağrılır — rastgele değerler artık hazırdır.

Bu fonksiyonları sınıfınızda tanımlamanız zorunlu değildir; tanımlamazsanız boş gövdeli varsayılanları kullanılır. Tanımladığınızda ise randomize() onları sizin için otomatik çağırır; elle çağırmanıza gerek yoktur.

pre_randomize() ne için kullanılır?

  • Randomize öncesi durum hazırlığı, sayaç güncelleme, bayrak ayarlama.
  • Bazı değişkenleri geçici olarak sabitlemek veya ön koşullar kurmak.

post_randomize() ne için kullanılır?

  • Türetilmiş alanları hesaplamak: CRC, checksum, toplam boyut gibi rastgele olmayan ama rastgele değerlere bağlı alanlar.
  • Rastgele üretilmiş değerlerde küçük düzeltmeler yapmak (örneğin belirli bitleri zorlamak).
  • Üretilen veriyi loglamak veya doğrulamak.

Kaynak Kod

// =============================================================================
// GUN 3 - Konu 5: pre_randomize() ve post_randomize() Fonksiyonlari
// =============================================================================

class EthernetFrame;
  rand bit [47:0] dst_mac;
  rand bit [47:0] src_mac;
  rand bit [15:0] ethertype;
  rand bit [7:0]  payload [];
  rand int        payload_len;
  
  // Hesaplanan alanlar (rastgele degil)
  bit [31:0] crc;
  int        frame_size;
  int        frame_id;
  static int frame_count = 0;

  constraint c_payload {
    payload_len inside {[46:1500]};  // Ethernet min/max
    payload.size() == payload_len;
  }

  constraint c_ethertype {
    ethertype inside {16'h0800,      // IPv4
                      16'h0806,      // ARP
                      16'h86DD};     // IPv6
  }

  // randomize() oncesinde otomatik cagrilir
  function void pre_randomize();
    frame_id = frame_count++;
    $display("  [PRE]  Frame #%0d randomize basliyor...", frame_id);
  endfunction

  // randomize() sonrasinda otomatik cagrilir
  function void post_randomize();
    // CRC hesapla (basitlestirilmis)
    crc = 0;
    crc ^= dst_mac[31:0] ^ dst_mac[47:32];
    crc ^= src_mac[31:0] ^ src_mac[47:32];
    crc ^= {16'h0, ethertype};
    foreach (payload[i])
      crc ^= {24'h0, payload[i]};

    frame_size = 6 + 6 + 2 + payload_len + 4;  // dst+src+type+payload+CRC

    // MAC adreslerini duzelt: unicast (LSB=0) ve locally administered
    dst_mac[0] = 0;  // Unicast
    src_mac[0] = 0;  // Unicast
    src_mac[1] = 1;  // Locally administered

    $display("  [POST] Frame #%0d: CRC=0x%08h, Size=%0d bytes",
             frame_id, crc, frame_size);
  endfunction

  function void display();
    $display("  Frame #%0d:", frame_id);
    $display("    DST MAC : %012h", dst_mac);
    $display("    SRC MAC : %012h", src_mac);
    $display("    Type    : 0x%04h (%s)", ethertype, get_type_name());
    $display("    Payload : %0d bytes", payload_len);
    $display("    CRC     : 0x%08h", crc);
    $display("    Toplam  : %0d bytes", frame_size);
  endfunction

  function string get_type_name();
    case (ethertype)
      16'h0800: return "IPv4";
      16'h0806: return "ARP";
      16'h86DD: return "IPv6";
      default:  return "Bilinmeyen";
    endcase
  endfunction
endclass

module pre_post_randomize;
  initial begin
    EthernetFrame frame;

    $display("=== pre_randomize() ve post_randomize() ===\n");

    frame = new();

    repeat (4) begin
      $display("--- Randomize cagrisi ---");
      assert(frame.randomize()) else $fatal(1, "Randomize hata!");
      frame.display();
      $display();
    end

    // Ozel kisitlama ile
    $display("--- Sadece IPv4 paketleri ---");
    repeat (2) begin
      assert(frame.randomize() with {
        ethertype == 16'h0800;
        payload_len < 100;
      }) else $fatal(1, "Randomize hata!");
      frame.display();
      $display();
    end

    $display("=== pre/post_randomize Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • EthernetFrame sınıfı rastgele alanları (dst_mac, src_mac, ethertype, payload, payload_len) ve hesaplanan/rastgele olmayan alanları (crc, frame_size, frame_id, statik frame_count) içerir.
  • c_payload constraint'i payload_len'i Ethernet'in geçerli sınırları olan 46–1500 arasına koyar ve payload.size() == payload_len ile dizi boyutunu eşitler. c_ethertype ise ethertype'ı IPv4 (0x0800), ARP (0x0806) ve IPv6 (0x86DD) değerlerinden biriyle sınırlar.
  • pre_randomize() her randomize'dan önce çalışır; frame_id = frame_count++ ile her çerçeveye benzersiz bir kimlik atar ve [PRE] etiketli bir mesaj basar. Bu, rastgeleleştirmenin ne zaman başladığını gösterir.
  • post_randomize() rastgele değerler hazır olduktan sonra çalışır ve şunları yapar:
    • dst_mac, src_mac, ethertype ve tüm payload baytları üzerinden XOR ile basitleştirilmiş bir crc hesaplar.
    • frame_size'ı başlık + payload + CRC toplamı olarak (6 + 6 + 2 + payload_len + 4) hesaplar.
    • MAC adreslerini düzeltir: dst_mac[0]=0 ve src_mac[0]=0 ile unicast, src_mac[1]=1 ile "locally administered" yapar. Bu, rastgele üretilen değerlerin post-randomize ile düzeltilmesine güzel bir örnektir.
  • get_type_name() yardımcı fonksiyonu ethertype değerini okunabilir bir isme (IPv4, ARP, IPv6) çevirir.
  • module içinde önce 4 çerçeve serbestçe üretilir; her çağrıda PRE → POST sırası ekrana yansır. Ardından randomize() with { ethertype == 16'h0800; payload_len < 100; } ile yalnızca küçük IPv4 paketleri üretilir.

Önemli Noktalar

  • pre_randomize() ve post_randomize()'ı elle çağırmayın. randomize() bunları otomatik tetikler; elle çağırmak çift çalışmaya ve hatalı duruma yol açar.
  • Hesaplanan alanları post_randomize() içinde üretin. crc ve frame_size gibi rastgele olmayan ama rastgele değerlere bağlı alanlar için doğru yer burasıdır; çünkü o aşamada tüm rastgele değerler kesinleşmiştir.
  • post_randomize() içinde değer değiştirmek kısıtlamaları atlatabilir. MAC adres bitlerinin elle ayarlanması gibi düzeltmeler çözücünün dışında gerçekleşir; bu yüzden bu değişikliklerin hiçbir constraint'i ihlal etmediğinden emin olun.
  • pre_randomize() çözücüden önce çalışır, dolayısıyla orada atadığınız rand değişkenleri çözücü tarafından üzerine yazılabilir; bu fonksiyon daha çok rastgele olmayan hazırlık için uygundur.
  • Bu kancalar, kısıtlamalarla ifade edilemeyen prosedürel mantığı (karmaşık CRC, koşullu düzeltmeler) eklemenin temiz yoludur; ancak mümkün olan her kuralı önce constraint olarak ifade etmeye çalışın.
  • randomize() başarısız olursa post_randomize() çağrılmaz. Bu yüzden crc ve frame_size gibi türetilmiş alanlar eski değerlerinde kalır; assert(frame.randomize()) kontrolü bu durumda da sizi korur.
  • Kalıtımda bu kancalar virtual gibi davranır: alt sınıf post_randomize()'ı yeniden tanımlarsa üst sınıfınki otomatik çalışmaz. Üst sınıfın işini korumak için alt sınıfta super.post_randomize(); çağırmayı unutmayın.
  • İç içe nesnelerde (rand Payload p; gibi bir alan varsa) randomize() alt nesnenin de pre/post_randomize()'ını çağırır; sıra üst nesneden alt nesneye doğrudur.
  • Örnekteki MAC düzeltmesi aslında kısıtla da yazılabilirdi: constraint c_mac { dst_mac[0] == 0; src_mac[0] == 0; src_mac[1] == 1; }. Kısıt sürümü daha iyidir çünkü çözücü değeri baştan doğru üretir ve başka kısıtlarla tutarlılığı garanti eder. post_randomize() ile düzeltme, yalnızca kısıt diliyle ifade edilemeyen hesaplar (gerçek CRC-32, sıkıştırma, dosyadan okuma) için saklanmalıdır.

Rastgeleleştirme Yaşam Döngüsü

frame.randomize()
   │
   ├─ 1. pre_randomize()        → frame_id ata, sayaç artır, log bas
   │
   ├─ 2. Çözücü çalışır         → rand alanlar (dst_mac, src_mac, ethertype, payload...) atanır
   │       │
   │       └─ Çözüm yok?        → 0 döner, post_randomize() ATLANIR, alanlar değişmez
   │
   └─ 3. post_randomize()       → crc, frame_size hesapla; düzeltmeler; log bas
            │
            └─ 1 döner

Benzetme: pre_randomize() fotoğraf çekiminden önce ışıkları ayarlamak, çözücü deklanşöre basmak, post_randomize() ise çekilen kareye filtre uygulamaktır. Filtre, çekim başarısızsa (kare yoksa) uygulanmaz; ve filtreyle kareyi fazla değiştirirseniz "gerçeklik"ten (kısıtlardan) uzaklaşırsınız.

Sık Yapılan Hatalar

  • post_randomize() içinde rand alanı kısıtı bozacak şekilde değiştirmek: payload_len = 2000; yazarsanız artık Ethernet sınırı ihlal edilir ve hiçbir kısıt sizi uyarmaz. Yalnızca kısıtla ifade edilemeyen ve kısıtları bozmayan düzeltmeler yapın.
  • Kancaları elle çağırmak: frame.post_randomize(); çağrısı frame_count'u çift artırır ve CRC'yi iki kez hesaplar.
  • pre_randomize()'da rand alana değer vermek ve kalmasını beklemek: Çözücü üzerine yazar. Değerin kalmasını istiyorsanız kısıt (== ) veya rand_mode(0) kullanın.
  • Alt sınıfta super.post_randomize()'ı unutmak: Üst sınıfın CRC hesabı sessizce kaybolur.
  • Kanca içinde zaman tüketmek: pre/post_randomize() function'dır; #delay veya @ içeremez.

Kendinizi Deneyin

  1. MAC düzeltmelerini post_randomize()'dan silip aynı kuralları bir constraint c_mac { ... } bloğuna taşıyın. Çıktı aynı mı? Hangi yaklaşım daha güvenli, neden?
  2. assert(frame.randomize() with { payload_len == 2000; }) çağırın. [PRE] mesajı basıldı mı? [POST] basıldı mı? Bu gözlem yaşam döngüsü şemasının hangi dalını doğrular?
  3. EthernetFrame'den VlanFrame extends EthernetFrame türetin, rand bit [11:0] vlan_id; ekleyin ve post_randomize()'ı override ederek frame_size'a 4 bayt ekleyin. super.post_randomize() olmadan ne oluyor?
  4. pre_randomize() içine ethertype = 16'h0800; yazın. Çözücü bu değeri korudu mu? Sonucu rand_mode ile araştırın.
Hızlı Kontrol: randomize() başarısız olduğunda post_randomize() çalışır mı?

Hayır. post_randomize() yalnızca çözücü başarılı olduğunda çağrılır. pre_randomize() ise çözücüden önce çalıştığı için başarısız çağrılarda bile tetiklenir; örnekteki frame_count bu yüzden başarısız denemelerde de artar.