EDA Playground'da Dene

Gün 6 Ara Testi: Generator + Driver

Gün 6: Bitirme Projesi - Bölüm 1 | Monitor ve scoreboard olmadan altyapının çalıştığını doğrulayan ara testbench

Bu ders, Gün 6'nın sonunda elinizdeki parçaları (interface, DUT, transaction, generator, driver) ilk kez bir arada çalıştırır. Henüz monitor, scoreboard ve coverage yoktur; amaç "doğru mu çalışıyor?" sorusunu değil, "çalışıyor mu?" sorusunu yanıtlamaktır. Gerçek projelerde de testbench böyle kurulur: önce stimulus DUT'a ulaşsın, dalga formunda görünsün; kontrol mekanizmaları sonra eklenir.

Neden Bir Ara Test?

Tüm bileşenleri yazıp en sonda tek seferde çalıştırmak, hatayı bulmayı zorlaştırır: "Scoreboard uyuşmazlık veriyor" dediğinizde sorun generator'da mı, driver'da mı, interface bağlantısında mı, DUT'ta mı bilemezsiniz. Ara test bu belirsizliği parçalar:

  • Bağlantı doğrulaması: Interface, DUT portları ve clocking block gerçekten birbirine bağlı mı? $dumpvars ile dalga formunda in_valid, operand_a değişiyor mu?
  • Mailbox zinciri: Generator'ın ürettiği her transaction driver'a ulaşıyor mu? Uretilen == Surulen eşitliği bunu gösterir.
  • Zamanlama: Driver'ın output #1 skew ile sürdüğü değerleri DUT doğru kenarda örnekliyor mu? out_valid'in in_valid'den bir çevrim sonra yükseldiğini dalga formunda görürsünüz.
  • Reset dizisi: rst_n düşerken çıkışlar sıfırlanıyor mu?

Bu adımda drv2scb_mbx yaratılır ama hiç okunmaz; driver kopyaları içine koyar, mailbox sınırsız olduğu için put bloklanmaz. Gün 7'de scoreboard bu mailbox'ın diğer ucuna bağlanacak.

Kaynak Kod

// =============================================================================
// GUN 6 - Konu 5: Ara Testbench (Generator + Driver, henuz kontrol yok)
// =============================================================================
// Bu dosya Gun 6'nin sonunda elde edilen mimaridir.
// Gun 7'de Monitor, Scoreboard, Coverage ve Environment eklenecektir.
// =============================================================================

`include "alu_if.sv"            // Interface (Ders 36)
`include "alu.sv"               // DUT (Ders 36-1)
`include "6_2_transaction.sv"   // ALU_Transaction (Ders 37)
`include "6_3_generator.sv"     // ALU_Generator (Ders 38)
`include "6_4_driver.sv"        // ALU_Driver (Ders 39)

module tb_top_day6;

  // Saat uretimi: 10 ns periyot (100 MHz)
  logic clk = 0;
  always #5 clk = ~clk;

  // Interface
  alu_if aif(clk);

  // DUT baglantisi: pinler interface sinyallerine baglanir
  alu dut (
    .clk       (aif.clk),
    .rst_n     (aif.rst_n),
    .in_valid  (aif.in_valid),
    .operand_a (aif.operand_a),
    .operand_b (aif.operand_b),
    .opcode    (aif.opcode),
    .out_valid (aif.out_valid),
    .result    (aif.result),
    .flags     (aif.flags)
  );

  // Mailbox'lar
  mailbox #(ALU_Transaction) gen2drv_mbx;
  mailbox #(ALU_Transaction) drv2scb_mbx;   // Gun 7'de scoreboard okuyacak

  // Bilesenler
  ALU_Generator gen;
  ALU_Driver    drv;

  initial begin
    $display("============================================================");
    $display("  GUN 6: ALU Testbench - Altyapi Testi");
    $display("============================================================\n");

    // Mailbox olustur
    gen2drv_mbx = new();
    drv2scb_mbx = new();

    // Bilesenleri olustur ve bagla (aif -> virtual alu_if.driver donusumu otomatik)
    gen = new(gen2drv_mbx, 20);
    drv = new(aif, gen2drv_mbx, drv2scb_mbx);

    // Reset
    drv.reset();

    // Calistir: generator biter, driver sonsuz donguden hic cikmaz
    fork
      gen.run();
      drv.run();
    join_any

    // Generator bitene ve son transaction surulene kadar bekle
    wait(gen.done.triggered);
    wait(drv.driven_count == gen.generated_count);
    repeat (5) @(aif.drv_cb);   // Son out_valid'in dalga formunda gorunmesi icin

    $display("\n  --- Gun 6 Istatistikler ---");
    $display("  Uretilen : %0d", gen.generated_count);
    $display("  Surulen  : %0d", drv.driven_count);
    $display("  Bekleyen : %0d (drv2scb icinde, Gun 7'de okunacak)", drv2scb_mbx.num());

    $display("\n============================================================");
    $display("  GUN 6 TAMAMLANDI");
    $display("  (Gun 7'de Monitor, Scoreboard ve Coverage eklenecek)");
    $display("============================================================");
    $finish;
  end

  // Dalga formu kaydi
  initial begin
    $dumpfile("alu_tb.vcd");
    $dumpvars(0, tb_top_day6);
  end

endmodule

Kodun Açıklaması

  • **`include sırası**: Önce interface ve DUT (statik tasarım öğeleri), sonra sınıflar. Sınıflar virtual alu_if.driver tipine atıfta bulunduğu için interface'in önce derlenmesi en güvenli sıradır. Generator, ALU_Transaction'ı kullandığı için transaction dosyası ondan önce gelir.
  • Saat ve interface: always #5 clk = ~clk ile 10 ns periyot üretilir; alu_if aif(clk) interface'i bu saati alır ve clocking block'lar bu saatle tetiklenir.
  • DUT bağlantısı: Her port aif.<sinyal> ile eşlenir. İsimlerin aynı olması bağlamayı mekanik ve hatasız kılar.
  • Mailbox'lar ve bileşenler: gen2drv_mbx generator→driver kanalıdır. drv2scb_mbx şimdilik yalnızca doldurulur. gen = new(gen2drv_mbx, 20) 20 transaction üretecek şekilde, drv = new(aif, ...) ise interface'in driver modport görünümüyle kurulur.
  • Reset ve çalışma: drv.reset() 5 çevrim rst_n'i düşük tutar. fork ... join_any ile generator ve driver paralel başlar; driver forever döngüsünde olduğu için join_any generator bitince ana süreci serbest bırakır.
  • Bitiş senkronizasyonu: wait(gen.done.triggered) üretimin bittiğini, wait(drv.driven_count == gen.generated_count) ise son transaction'ın da sürüldüğünü garanti eder. Sabit bir #200 yerine sayaç eşitliği beklemek, transaction sayısı değişse bile kodun doğru kalmasını sağlar. repeat (5) @(aif.drv_cb) yalnızca dalga formunda son out_valid darbesinin görünmesi içindir.
  • İstatistikler: Üretilen ve sürülen adetlerin eşit olması altyapının çalıştığının kanıtıdır. drv2scb_mbx.num() ise driver'ın scoreboard için biriktirdiği kopyaları sayar; Gün 7'de bu sayı sıfıra inecek (scoreboard tüketecek).

Çıktıyı Okuma Rehberi

Gözlem Beklenen Değilse ilk bakılacak yer
[Driver] Reset tamamlandi Simülasyonun ~60 ns'sinde drv.reset() içindeki repeat(5) ve saat üretimi
Uretilen : 20, Surulen : 20 Eşit Mailbox handle'ları aynı mı? drv.run() fork'ta mı?
Bekleyen : 20 Sürülen kadar drv2scb.put(txn.copy()) çağrısı var mı?
Dalga formunda out_valid Her in_valid darbesinden bir çevrim sonra 1 çevrimlik darbe DUT always_ff, clocking skew
Dalga formunda result out_valid=1 iken değişir, sonra sabit kalır if (in_valid) koşulu

Önemli Noktalar

  • Küçük adımlarla kurun, her adımda çalıştırın: Gerçek projelerde bir testbench'in ilk sürümü yalnızca "reset at, bir transaction sür, dalga formuna bak" yapar. Bu ders o alışkanlığı modeller.
  • Kontrol olmayan test "geçer" ama bir şey kanıtlamaz: Bu testbench DUT yanlış sonuç üretse de "GUN 6 TAMAMLANDI" basar. Doğruluk kararı Gün 7'deki scoreboard'un işidir; burada yalnızca veri akışı doğrulanır.
  • Tüketilmeyen mailbox şişer: drv2scb_mbx sınırsız olduğu için 20 transaction sorun değildir; ama milyonlarca transaction'lı bir regresyonda okunmayan bir mailbox bellek sızıntısı gibi davranır. Her put'un bir get'i olmalıdır.
  • Sayaç tabanlı bitiş, zaman tabanlı bitişten iyidir: wait(drv.driven_count == gen.generated_count) ifadesi, Lab 4'teki #100 sihirli sayısının olgunlaşmış hâlidir.
  • Dalga formu bu aşamada en iyi dostunuzdur: Scoreboard yokken doğruluğu gözle kontrol edersiniz. $dumpvars(0, tb_top_day6) tüm hiyerarşiyi kaydeder; EDA Playground'da "Open EPWave after run" seçeneğini açın.

Sık Yapılan Hatalar

  • alu_if aif(); (saatsiz): Clocking block hiç tetiklenmez; drv.reset() içindeki ilk @(vif.drv_cb) sonsuza kadar bekler ve hiçbir mesaj basılmaz.
  • join kullanmak: Driver asla bitmediği için simülasyon sonsuza kadar sürer. join_any şarttır.
  • Interface'i sınıflardan sonra include etmek: Çoğu araç tolere eder, ama bazıları virtual alu_if.driver için "bilinmeyen tip" hatası verir. Statik öğeleri önce derleyin.
  • gen.generated_count yerine gen.num_transactions beklemek: Bu dosyada ikisi eşittir (20); ancak Gün 7'de köşe üreticiye geçildiğinde num_transactions anlamsızlaşır. Baştan doğru sayacı kullanın.
  • $finish olmadan bırakmak: always #5 clk bloğu simülasyonu sonsuza kadar sürdürür.

Kendinizi Deneyin

  1. gen = new(gen2drv_mbx, 20) değerini 200 yapın. Sabit bir #200 beklemesi olsaydı istatistikler ne gösterirdi? Sayaç tabanlı bekleme ile hâlâ 200/200 çıktığını doğrulayın.
  2. Dalga formunu açın ve tek bir transaction için in_valid, operand_a, out_valid, result sinyallerini aynı pencerede inceleyin. in_valid ile out_valid arasındaki gecikmeyi ve output #1 skew'ini ölçün.
  3. Driver'ın drive_transaction çağrısından sonra $display ile aif.result değerini basın. Hangi transaction'ın sonucunu görüyorsunuz: şu anki mi, bir önceki mi? Neden?
  4. DUT'ta bilinçli hata yapın (OP_ADD → operand_a - operand_b). Bu testbench hatayı fark etti mi? Bu gözlemi Gün 7'nin motivasyonu olarak not edin.
Hızlı Kontrol: Bu testbench DUT'un doğru çalıştığını kanıtlar mı?

Hayır. Yalnızca stimulus'un üretilip DUT'a kadar ulaştığını (veri akışını) kanıtlar. DUT yanlış sonuç üretse bile "Uretilen == Surulen" eşitliği bozulmaz. Doğruluk kararı için monitor + referans model + scoreboard gerekir; bunlar Gün 7'nin konusudur.