EDA Playground'da Dene

Transaction Sınıfı

Gün 6: Bitirme Projesi - Bölüm 1 | ALU transaction paketi: constraint'ler ve metodlar

Transaction (işlem paketi), testbench içinde dolaşan tek bir ALU işlemini soyutlayan veri sınıfıdır. Generator onu üretir, driver pinlere sürer, monitor DUT'tan toplar ve scoreboard karşılaştırır; yani ALU_Transaction, tüm bileşenler arasında ortak konuşulan "dil"dir.

Transaction Nedir ve Neden Gereklidir

Sinyal seviyesinde düşünmek yerine, doğrulama dünyasında işlemleri nesne olarak ele alırız. Bir ALU_Transaction nesnesi tek bir ALU işleminin hem girişlerini hem de beklenen/gözlenen çıkışlarını taşır:

  • Girişler (rand ile rastgeleleştirilir): opcode, operand_a, operand_b
  • Çıkışlar (DUT'tan okunur): result, flags, out_valid
  • Meta veri: Her işleme benzersiz bir id ve toplam sayacı count

Bu sayede bir transaction, mailbox'lar üzerinden bileşenden bileşene tek parça halinde aktarılabilir.

Constraint'ler (Kısıtlamalar) ile Akıllı Rastgelelik

Saf rastgelelik genellikle ilginç durumları yeterince üretmez. Bu sınıf üç kısıtlama kullanır:

  • c_opcode_dist: İşlem kodlarının dağılımını ağırlıklandırır; toplama/çıkarma daha sık (:= 20), mantıksal işlemler daha seyrek seçilir.
  • c_corner_cases: Operandların köşe değerlerini (0, 255, 8'h80) bilinçli olarak öne çıkarır; bunlar taşma/işaret hatalarının en sık görüldüğü değerlerdir.
  • c_shift_range: Kaydırma işlemlerinde operand_b'yi 0-7 aralığına sınırlar; çünkü 8-bit veride 8'den büyük kaydırma anlamsızdır.

Yardımcı Metotlar

  • display(): İşlemi okunabilir formatta basar; hata ayıklamada vazgeçilmezdir.
  • copy(): Nesnenin derin bir kopyasını üretir. Driver, scoreboard'a transaction'ın kopyasını gönderir; böylece aynı nesnenin paylaşılmasından doğan referans hataları engellenir.

Kaynak Kod

// =============================================================================
// GUN 6 - Konu 2: Transaction (Islem) Paketi Sinifi
// =============================================================================

class ALU_Transaction;
  // Islem kodlari (DUT ile uyumlu)
  typedef enum logic [2:0] {
    OP_ADD = 3'b000,
    OP_SUB = 3'b001,
    OP_AND = 3'b010,
    OP_OR  = 3'b011,
    OP_XOR = 3'b100,
    OP_NOT = 3'b101,
    OP_SHL = 3'b110,
    OP_SHR = 3'b111
  } opcode_e;

  // Rastgele giris degiskenleri
  rand opcode_e    opcode;
  rand logic [7:0] operand_a;
  rand logic [7:0] operand_b;

  // Cikis degiskenleri (DUT'tan okunacak)
  logic [15:0]     result;
  logic [3:0]      flags;
  logic            out_valid;

  // Meta veriler
  int              id;
  static int       count = 0;

  // --- Kisitlamalar ---
  constraint c_opcode_dist {
    opcode dist {
      OP_ADD := 20,
      OP_SUB := 20,
      OP_AND := 10,
      OP_OR  := 10,
      OP_XOR := 10,
      OP_NOT := 10,
      OP_SHL := 10,
      OP_SHR := 10
    };
  }

  // Kose degerler (0, 0x80, 0xFF) one cikarilir; araliklar birbiriyle
  // CAKISMAZ: 0x80 degeri [1:127] ve [129:254] araliklarinin disinda tutulur.
  constraint c_corner_cases {
    operand_a dist {
      0          := 10,
      [1:127]    :/ 35,
      8'h80      := 10,
      [129:254]  :/ 35,
      255        := 10
    };
    operand_b dist {
      0          := 10,
      [1:127]    :/ 35,
      8'h80      := 10,
      [129:254]  :/ 35,
      255        := 10
    };
  }

  // Shift isleminde B sadece 0-7
  constraint c_shift_range {
    (opcode inside {OP_SHL, OP_SHR}) -> operand_b inside {[0:7]};
  }

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

  function void display(string prefix = "");
    $display("  %sTXN#%03d | %s | A=0x%02h | B=0x%02h | Res=0x%04h | Flags=%04b | V=%0b",
             prefix, id, opcode.name(), operand_a, operand_b, result, flags, out_valid);
  endfunction

  function ALU_Transaction copy();
    ALU_Transaction t = new();
    t.opcode    = this.opcode;
    t.operand_a = this.operand_a;
    t.operand_b = this.operand_b;
    t.result    = this.result;
    t.flags     = this.flags;
    t.out_valid = this.out_valid;
    t.id        = this.id;
    return t;
  endfunction
endclass

Kodun Açıklaması

  • opcode_e enum'u: OP_ADD'den OP_SHR'a kadar 8 işlemi logic [2:0] olarak kodlar. Değerler DUT'taki localparam opcode'larıyla birebir aynıdır, böylece doğrudan pinlere sürülebilir.
  • rand değişkenler: opcode, operand_a, operand_b rastgeleleştirilebilir alanlardır. randomize() çağrıldığında kısıtlamalara uygun değerler üretilir.
  • Çıkış alanları: result, flags, out_valid rastgele değildir; DUT'tan veya referans modelden gelen değerlerle doldurulur.
  • static int count: Tüm nesneler arasında paylaşılan sayaç. new() içinde this.id = count++ ile her transaction'a artan benzersiz bir kimlik verilir.
  • c_opcode_dist: dist operatörüyle opcode olasılık ağırlıklarını belirler (OP_ADD := 20 gibi).
  • c_corner_cases: operand_a ve operand_b için := (her değere ayrı ağırlık) ve :/ (aralığa toplam ağırlık) operatörlerini birlikte kullanarak köşe değerleri öne çıkarır. Toplam ağırlık 100'dür: üç köşe değer (0, 0x80, 0xFF) %10'ar, iki ara bölge %35'er. Aralıkların çakışmaması önemlidir: 0x80 değeri [1:127] ve [129:254] dışında bırakılmıştır; aynı değer hem bir aralıkta hem tek başına listelenseydi standart bunu hata olarak kabul eder ve ağırlığı belirsizleşirdi.
  • c_shift_range: (opcode inside {OP_SHL, OP_SHR}) -> operand_b inside {[0:7]} ile koşullu (implication) kısıtlama uygular.
  • copy(): Yeni bir nesne oluşturup tüm alanları tek tek kopyalar ve döndürür; sığ kopyalamanın yan etkilerinden kaçınır.

Önemli Noktalar

  • Transaction enum'u DUT opcode'larıyla senkron olmalıdır; uyumsuzluk tüm karşılaştırmaları sessizce bozar.
  • assert(txn.randomize()) her zaman kontrol edilmelidir; constraint'ler çözülemezse randomize başarısız olur ve sessizce yanlış veriyle devam edilmemelidir.
  • copy() kullanımı kritiktir: Aynı nesne referansı birden çok bileşene gönderilirse, sonradan yapılan değişiklikler beklenen değeri bozabilir. Derin kopya bu tuzağı önler.
  • dist ağırlıkları kapsamı (coverage) doğrudan etkiler; nadir senaryolara yeterli ağırlık vermek fonksiyonel kapsamı hızlandırır.
  • c_shift_range gibi koşullu kısıtlamalar, generator'daki yönlendirilmiş üretimle uyumlu olmalıdır; aksi halde solver çakışması (conflict) yaşanır.
  • dist listesinde aynı değer iki kez görünmemelidir. Bu yüzden ara bölgeler 0x80 etrafında ikiye bölünmüştür. Kısıt yazarken "bu değer kaç yerde geçiyor?" sorusunu sormak, çözücü hatalarını ve belirsiz ağırlıkları önler.
  • Çıkış alanları rand değildir ama copy() tarafından kopyalanır. Driver sürdüğü anda result/flags henüz boştur; scoreboard bunları referans modelden hesaplar. Monitor ise DUT'tan okuduğu değerlerle yeni bir transaction üretir. Aynı sınıfın hem "beklenen" hem "gözlenen" rolünde kullanılması, karşılaştırmayı kolaylaştırır.

Transaction'ın Yaşam Döngüsü

Generator           Driver                 DUT            Monitor            Scoreboard
──────────          ──────────             ───            ──────────         ──────────
new() + randomize ─▶ get(txn)
                     drive pins  ─────────▶ hesapla
                     put(txn.copy()) ────────────────────────────────────────▶ expected
                                            out_valid ─▶ new() + pinlerden   ─▶ actual
                                                          doldur, put(txn)
                                                                               compare(expected, actual)

Aynı ALU_Transaction sınıfı üç farklı yerde yaratılır: generator'da (girişler rastgele), driver'da copy() ile (beklenen kayıt), monitor'de (gözlenen çıkış). Sınıfın hem giriş hem çıkış alanlarını taşıması bu yüzden gereklidir.

Sık Yapılan Hatalar

  • Enum değerlerini DUT ile farklı sıralamak: OP_AND = 3'b011 yazılırsa DUT OR yapar, scoreboard AND bekler; her transaction'da hata alırsınız ama kaynağı bulmak saatler alabilir.
  • copy()'ye yeni alan eklemeyi unutmak: Sınıfa bir alan ekleyip copy()'yi güncellemezseniz scoreboard'a giden kopyada o alan varsayılan değerde kalır.
  • static count'u copy() içinde artırmak: new() çağrısı count++ yapar; kopyalar da kimlik tüketir. Bu projede id zaten copy() içinde eski değere eşitlenir; ama count yine atlar. Kritik değildir, ancak "üretilen transaction sayısı = count" varsayımı yapmayın.
  • dist içinde örtüşen aralıklar: Yukarıda açıklanan hata.

Kendinizi Deneyin

  1. 1000 transaction randomize() edip operand_a için 0, 0x80, 0xFF ve "diğer" sayılarını bir ilişkisel dizide toplayın. Dağılım 10/10/10/70'e yaklaştı mı?
  2. c_shift_range kısıtını geçici olarak kapatın (txn.c_shift_range.constraint_mode(0)) ve OP_SHL ile operand_b = 8'h09 üretin. DUT ne yapar (ipucu: operand_b[2:0])? Bu, bir "tasarım hatası" mı, "sözleşme" mi?
  3. display() çıktısına %s ile opcode.name() yerine %0d ile ham değeri bastırın; hangisi hata ayıklamada daha kullanışlı?
  4. Sınıfa function bit compare(ALU_Transaction other); ekleyin: result ve flags eşitliğini === ile karşılaştırsın. Gün 7'de scoreboard'u bu metodu kullanacak şekilde sadeleştirmeyi deneyin.
Hızlı Kontrol: dist içinde aynı değer neden iki ayrı terimde yer almamalıdır?

Standart, bir dist listesinde her değerin en fazla bir terimde görünmesini ister; aksi durum hata ya da belirsiz ağırlık üretir. Bu projede 0x80 köşe değeri bu yüzden [1:127] ve [129:254] aralıklarının dışında bırakılarak tek başına listelenir.