EDA Playground'da Dene

Scoreboard

Gün 7: Bitirme Projesi - Bölüm 2 | Referans model ile DUT çıkışını karşılaştırma

Scoreboard (puan tablosu), doğrulamanın "hakem"idir. ALU mimarimizde driver'ın gönderdiği beklenen işlemle, monitor'ün gözlediği gerçek DUT çıkışını karşılaştırır ve bir bağımsız referans modeliyle DUT'un doğru çalışıp çalışmadığına karar verir.

Scoreboard ve Mimarideki Yeri

Scoreboard iki mailbox'tan beslenir:

  • drv2scb: Driver'dan gelen beklenen (uygulanan) transaction.
  • mon2scb: Monitor'den gelen gerçek (gözlenen) DUT çıkışı.

Her iki kaynaktan birer transaction alıp eşleştirir. Beklenen değer DUT'tan değil, scoreboard'un kendi içindeki referans modelinden (compute_reference) hesaplanır. Bu sayede DUT'un çıkışı bağımsız bir "altın model"e (golden model) karşı sınanır.

Referans Model (Golden Model)

compute_reference, ALU davranışını saf yazılımda yeniden modeller. Donanımdan tamamen bağımsız bu hesap, "doğru cevabın" ne olması gerektiğini söyler. DUT'un sonucu bu beklenen değerle uyuşmazsa hata raporlanır.

Referans modelin uyması gereken sözleşme Ders 36-1'de tanımlanmıştır: Z (result == 0), N (result[15]), C (ADD/SUB için result[8], diğerlerinde 0), P (^result). Model bu sözleşmeyi DUT koduna bakmadan, spesifikasyondan yazmalıdır; aksi halde aynı yorum hatasını iki yerde tekrarlayıp görünmez kılarsınız.

Kapsamla (Coverage) İşbirliği

Scoreboard, gözlenen her transaction'ı (actual) cov.sample(actual) ile coverage bileşenine de iletir. Böylece hangi senaryoların gerçekten DUT'a ulaştığı ölçülür. cov nesnesi scoreboard tarafından yaratılmaz; environment tek bir ALU_Coverage örneği oluşturur ve yapıcı argümanıyla scoreboard'a verir (dependency injection). Böylece environment'ın raporladığı kapsam ile scoreboard'un beslediği kapsam aynı nesnedir.

Kaynak Kod

// =============================================================================
// GUN 7 - Konu 2: Scoreboard - Referans Model ile DUT Karsilastirmasi
// =============================================================================
class ALU_Scoreboard;
  mailbox #(ALU_Transaction) drv2scb;  // Driver'dan beklenen (girisler)
  mailbox #(ALU_Transaction) mon2scb;  // Monitor'den gercek (DUT cikisi)

  int pass_count  = 0;
  int fail_count  = 0;
  int total_count = 0;

  ALU_Coverage cov;   // Environment tarafindan verilir (paylasilan tek ornek)

  function new(mailbox #(ALU_Transaction) drv_mbx,
               mailbox #(ALU_Transaction) mon_mbx,
               ALU_Coverage               cov);
    this.drv2scb = drv_mbx;
    this.mon2scb = mon_mbx;
    this.cov     = cov;
  endfunction

  task run();
    ALU_Transaction expected, actual;
    logic [15:0] ref_result;
    logic [3:0]  ref_flags;

    $display("[Scoreboard] Baslatildi");

    forever begin
      drv2scb.get(expected);
      mon2scb.get(actual);
      total_count++;

      // Referans model: beklenen sonucu ve bayraklari girislerden hesapla
      compute_reference(expected.opcode, expected.operand_a, expected.operand_b,
                        ref_result, ref_flags);

      // Karsilastirma: hem sonuc hem bayraklar (=== ile x/z de yakalanir)
      if (actual.result === ref_result && actual.flags === ref_flags) begin
        pass_count++;
      end else begin
        fail_count++;
        $display("[Scoreboard] HATA #%0d!", total_count);
        $display("  Islem    : %s", expected.opcode.name());
        $display("  A=0x%02h, B=0x%02h", expected.operand_a, expected.operand_b);
        $display("  Beklenen : result=0x%04h, flags=%04b (P C N Z)", ref_result, ref_flags);
        $display("  Gercek   : result=0x%04h, flags=%04b (P C N Z)", actual.result, actual.flags);
      end

      // Gozlenen (actual) degerler kapsam toplayiciya gonderilir
      cov.sample(actual);
    end
  endtask

  // Referans model: ALU'nun SPESIFIKASYONUNU yazilimda modelle
  // Bayrak sozlesmesi (Ders 36-1): [0]=Z, [1]=N, [2]=C (yalnizca ADD/SUB), [3]=P
  function void compute_reference(
    ALU_Transaction::opcode_e opcode,
    logic [7:0] a, logic [7:0] b,
    output logic [15:0] result,
    output logic [3:0]  flags
  );
    case (opcode)
      ALU_Transaction::OP_ADD: result = {8'h00, a} + {8'h00, b};  // 16-bit toplam, bit 8 = carry
      ALU_Transaction::OP_SUB: result = {8'h00, a} - {8'h00, b};  // a<b ise 16'hFFxx (borrow)
      ALU_Transaction::OP_AND: result = {8'h00, a & b};
      ALU_Transaction::OP_OR:  result = {8'h00, a | b};
      ALU_Transaction::OP_XOR: result = {8'h00, a ^ b};
      ALU_Transaction::OP_NOT: result = {8'h00, ~a};
      ALU_Transaction::OP_SHL: result = {8'h00, a} << b[2:0];
      ALU_Transaction::OP_SHR: result = {8'h00, a} >> b[2:0];
      default:                 result = 16'h0000;
    endcase

    flags    = 4'b0000;
    flags[0] = (result == 16'h0000);                                   // Z: sifir
    flags[1] = result[15];                                             // N: isaret biti
    flags[2] = (opcode inside {ALU_Transaction::OP_ADD,
                               ALU_Transaction::OP_SUB}) ? result[8] : 1'b0;  // C: carry/borrow
    flags[3] = ^result;                                                // P: parite
  endfunction

  function void report();
    $display("\n  ========== SCOREBOARD RAPORU ==========");
    $display("  Toplam  : %0d", total_count);
    $display("  Basarili: %0d", pass_count);
    $display("  Basarisiz: %0d", fail_count);
    if (fail_count == 0)
      $display("  *** TUM TESTLER BASARILI! ***");
    else
      $display("  !!! %0d HATA TESPIT EDILDI !!!", fail_count);
    $display("  ========================================");
    
  endfunction
endclass

Kodun Açıklaması

  • Üyeler: drv2scb ve mon2scb mailbox'ları beklenen ve gerçek transaction'ları taşır. pass_count, fail_count, total_count istatistikleri tutar. cov coverage nesnesidir; yapıcıya dışarıdan verilir, scoreboard kendisi yaratmaz.
  • new(...): İki mailbox ve coverage handle'ını alır. Coverage'ın enjekte edilmesi, environment'ın cov.report() ile raporladığı nesnenin scoreboard'un cov.sample() ile beslediği nesneyle aynı olmasını garanti eder.
  • run(): Sonsuz döngüde drv2scb.get(expected) ve mon2scb.get(actual) ile bir beklenen ve bir gerçek transaction'ı eşleştirir, total_count++ yapar.
  • Referans hesabı: compute_reference(...) ile beklenen ref_result ve ref_flags hesaplanır.
  • Karşılaştırma: actual.result === ref_result && actual.flags === ref_flags ile hem sonuç hem bayraklar denetlenir. === operatörü x/z durumlarını da kesin karşılaştırır. Eşleşmezse hata detayları (opcode.name(), operandlar, beklenen ve gerçek değerler) basılır ve fail_count++ yapılır.
  • cov.sample(actual): Gözlenen değerler her işlemde kapsam toplamaya gönderilir.
  • compute_reference(...): Önce result hesaplanır: ADD/SUB için operandlar 16-bit'e genişletilerek toplanır/çıkarılır (böylece 9. bit carry/borrow'u taşır), mantıksal ve kaydırma işlemleri {8'h00, ...} ile 16-bit'e tamamlanır. Ardından bayraklar sözleşmeye göre üretilir: Z = (result == 0), N = result[15], C = ADD/SUB ise result[8] değilse 0, P = ^result. Model yalnızca spesifikasyonu bilir; DUT'un always_comb bloğunu kopyalamaz.
  • report(): Toplam/başarılı/başarısız sayıları ve geçti/kaldı özetini ekrana basar.

Önemli Noktalar

  • Referans modelin DUT'tan bağımsız olması esastır; aksi halde aynı hatayı iki yerde yapıp hatayı göremezsiniz. Model, "spesifikasyon ne diyor?" sorusuna göre yazılır; "DUT ne yapıyor?" sorusuna göre değil.
  • Karşılaştırmada === kullanımı, sonuçtaki olası x/z durumlarını da yakalar; == bu durumlarda yanıltıcı olabilir.
  • Bayraklar da karşılaştırılır. Bir ALU'da bayraklar çoğu zaman sonuçtan daha hataya açıktır (carry'nin yanlış işlemde set olması, zero'nun 16-bit yerine 8-bit'e bakması gibi). Yalnızca result'ı kontrol eden bir scoreboard bu hataları hiç görmez.
  • Bayrak sözleşmesini yanlış yorumlamak klasik bir tuzaktır. Bu dersin önceki sürümünde referans model flags[3]'ü overflow olarak hesaplıyordu, oysa DUT bu bitte parity üretir; model N bayrağını da result[7]'ye bakarak hesaplıyordu. Bayraklar karşılaştırılmadığı için bu uyuşmazlık görünmüyordu. Ders şimdi hem sözleşmeyi (Ders 36-1) hem modeli hizalar; bu hikâye, "karşılaştırmadığınız alan size hata vermez ama doğru da değildir" ilkesinin canlı örneğidir.
  • Driver ve monitor akışlarının hizalı kalması kritiktir; her beklenen transaction tam bir gözlemle eşleşmelidir, yoksa get çağrıları kayar ve tüm sonraki karşılaştırmalar bozulur.
  • compute_reference bir function'dır, zaman tüketmez; referans model karmaşıklaşsa (bellek, durum makinesi) bile function kalması, scoreboard'un get ritmini bozmamasını sağlar.

Scoreboard Mimarisi Seçenekleri

Bu derste "sıralı eşleştirme" (in-order) kullanılır: her beklenenle bir gözlenen, geliş sırasına göre eşleşir. Gerçek projelerde başka desenler de görürsünüz:

Desen Nasıl çalışır? Ne zaman?
Sıralı (bu ders) get(expected); get(actual); compare DUT sırayı korur, her girişe tam bir çıkış
Kuyruk tabanlı Beklenenler kuyruğa; gözlenen gelince pop_front ile eşleştir Driver monitor'den hızlıysa (geri-basınç)
Sırasız (out-of-order) Beklenenler ilişkisel dizide (id/adres anahtarlı); gözlenen gelince anahtarla ara DUT çıkışları farklı sırada üretebiliyorsa (çok kanallı, önbellekli)
Tahmin edici (predictor) Monitor girişi gözler → model çıktıyı tahmin eder → çıkış monitor'ü ile karşılaştır Driver'dan hiç veri almak istemiyorsanız (UVM'in önerdiği yapı)

Son satır önemlidir: UVM'de scoreboard genellikle driver'dan değil, giriş monitor'ünden beslenir; böylece doğrulama tamamen pin seviyesinde gözlenen gerçeklere dayanır. Bu projede driver kopyası kullanmak öğrenmeyi kolaylaştırır; UVM derslerinde "predictor" yaklaşımını göreceksiniz.

Benzetme: Scoreboard bir sınav gözetmenidir: elinde cevap anahtarı (referans model) vardır, öğrencinin kâğıdını (DUT çıkışı) sorularla (driver'ın gönderdiği girişler) eşleştirir ve puanlar. Cevap anahtarını öğrencinin kâğıdından kopyalarsanız (DUT koduna bakarak model yazmak) herkes 100 alır.

Sık Yapılan Hatalar

  • Referans modeli DUT kodunu kopyalayarak yazmak: Aynı hata iki yerde → her zaman PASS.
  • Yalnızca result karşılaştırmak: Bayrak hataları görünmez kalır.
  • get sırasını değiştirmek: mon2scb.get önce yazılırsa monitor henüz veri üretmediği için scoreboard bloklanır; sonuç yine doğru ama hata ayıklama zorlaşır. Sıra "beklenen → gözlenen" kalsın.
  • Coverage'ı scoreboard içinde new() etmek: Environment'ın raporladığı nesneyle farklı olur; rapor hep %0 gösterir. Bu dersin önceki sürümündeki hata tam olarak buydu.
  • Hata mesajında yalnızca "FAIL" basmak: Girişler, beklenen ve gözlenen değerler olmadan hata ayıklanamaz; mesaj her zaman bu üçünü içermeli.

Kendinizi Deneyin

  1. DUT'ta bilinçli hata yapın: always_comb içinde next_flags[FLAG_C]'yi her işlem için next_result[8] yapın. Scoreboard hangi işlemlerde hata verdi? Yalnızca result karşılaştıran eski sürüm bunu yakalar mıydı?
  2. compute_reference içinde flags[1] = result[15] | result[7]; yazın (eski hatalı sürüm). Hangi işlemlerde ve hangi operand değerlerinde uyuşmazlık çıktı? 0x80 + 0x00 örneğini elle hesaplayın.
  3. Scoreboard'u kuyruk tabanlı hale getirin: drv2scb.get ile gelenleri expected_q kuyruğuna koyan ayrı bir süreç, mon2scb.get ile gelenleri pop_front ile eşleştiren başka bir süreç yazın (fork içinde iki forever).
  4. compare() metodunu ALU_Transaction'a taşıyın (Ders 37 ek görevi) ve scoreboard'da if (actual.compare(expected_with_ref)) biçiminde kullanın. Hangi tasarım daha temiz?
Hızlı Kontrol: Referans model neden DUT'un RTL koduna bakılarak yazılmamalıdır?

Çünkü o zaman DUT'taki bir yorum hatası modele de aynen geçer; iki taraf aynı yanlış cevabı üretir ve scoreboard her şeyi PASS sayar. Referans model spesifikasyondan yazılmalı, DUT'tan bağımsız ikinci bir "görüş" olmalıdır. Bu bağımsızlık, doğrulamanın tasarımdan ayrı bir disiplin olmasının temel nedenidir.