EDA Playground'da Dene

Driver Modülü

Gün 6: Bitirme Projesi - Bölüm 1 | Transaction'ları DUT pin seviyesine süren modül

Driver (sürücü), soyut transaction nesnelerini gerçek pin seviyesindeki sinyal hareketlerine çeviren bileşendir. ALU mimarimizde generator ile DUT arasında köprü kurar: mailbox'tan aldığı ALU_Transaction'ı alu_if üzerinden DUT girişlerine uygular.

Driver ve Mimarideki Yeri

Driver'ın iki yüzü vardır:

  • Üst taraf (transaction seviyesi): gen2drv mailbox'ından transaction alır.
  • Alt taraf (sinyal seviyesi): virtual alu_if.driver vif aracılığıyla clocking block üzerinden DUT pinlerini sürer.

Ayrıca driver, sürdüğü her transaction'ın bir kopyasını (txn.copy()) drv2scb mailbox'ı ile scoreboard'a iletir. Böylece scoreboard "ne uyguladık" bilgisini doğrudan kaynağından öğrenir ve referans modeliyle karşılaştırır.

Clocking Block ile Senkron Sürüş

Sinyaller vif.drv_cb clocking block'u üzerinden ve <= (nonblocking) ile sürülür. @(vif.drv_cb) ifadesi bir sonraki saat kenarını bekler. Bu yaklaşım:

  • Yarış koşullarını önler (sinyaller doğru skew ile uygulanır).
  • DUT'un in_valid el sıkışmasıyla senkron çalışmasını sağlar.

Reset Görevi

reset() task'ı rst_n'i düşürür, tüm girişleri sıfırlar, birkaç saat çevrimi bekler ve rst_n'i tekrar yükseltir. Reset sırasında rst_n doğrudan (clocking block dışından) atanır, çünkü modport'ta output rst_n olarak tanımlıdır.

Kaynak Kod

// =============================================================================
// GUN 6 - Konu 4: Driver - Uyariciyi DUT'ye Suren Modul
// =============================================================================

class ALU_Driver;
  virtual alu_if.driver vif;
  mailbox #(ALU_Transaction) gen2drv;
  mailbox #(ALU_Transaction) drv2scb;
  int driven_count = 0;

  function new(virtual alu_if.driver vif,
               mailbox #(ALU_Transaction) in_mbx,
               mailbox #(ALU_Transaction) out_mbx);
    this.vif     = vif;
    this.gen2drv = in_mbx;
    this.drv2scb = out_mbx;
  endfunction

  task run();
    ALU_Transaction txn;
    $display("[Driver] Baslatildi");
    
    forever begin
      gen2drv.get(txn);
      drive_transaction(txn);
      drv2scb.put(txn.copy());  // Scoreboard'a kopya gonder
      driven_count++;
    end
  endtask

  task drive_transaction(ALU_Transaction txn);
    @(vif.drv_cb);
    vif.drv_cb.in_valid  <= 1;
    vif.drv_cb.operand_a <= txn.operand_a;
    vif.drv_cb.operand_b <= txn.operand_b;
    vif.drv_cb.opcode    <= txn.opcode;
    
    @(vif.drv_cb);
    vif.drv_cb.in_valid <= 0;
  endtask

  task reset();
    $display("[Driver] Reset baslatiliyor...");
    vif.rst_n = 0;
    vif.drv_cb.in_valid  <= 0;
    vif.drv_cb.operand_a <= 0;
    vif.drv_cb.operand_b <= 0;
    vif.drv_cb.opcode    <= 0;
    repeat(5) @(vif.drv_cb);
    vif.rst_n = 1;
    @(vif.drv_cb);
    $display("[Driver] Reset tamamlandi");
  endtask
endclass

Kodun Açıklaması

  • Üyeler: virtual alu_if.driver vif DUT sinyallerine erişimi sağlar; gen2drv generator'dan girişi, drv2scb scoreboard'a çıkışı taşır; driven_count sürülen işlem sayısını tutar.
  • new(...): Yapıcı, virtual interface'i ve iki mailbox'ı dışarıdan alır (dependency injection), bunları kendi üyelerine bağlar.
  • run(): Sonsuz döngüde gen2drv.get(txn) ile bir transaction bekler, drive_transaction(txn) ile pinlere uygular, ardından drv2scb.put(txn.copy()) ile kopyasını scoreboard'a yollar ve driven_count++ yapar.
  • drive_transaction(txn): @(vif.drv_cb) ile saat kenarını bekler, in_valid <= 1 yapıp operandları ve opcode'u sürer. Bir sonraki kenarda in_valid <= 0 ile geçerli darbeyi tek çevrimlik tutar.
  • reset(): vif.rst_n = 0 ile reset'i etkinleştirir, tüm girişleri sıfırlar, repeat(5) @(vif.drv_cb) ile beş çevrim bekler, sonra vif.rst_n = 1 ile reset'ten çıkar.

Önemli Noktalar

  • drv2scb.put(txn.copy()) kopyalama şarttır: Aynı txn nesnesi paylaşılırsa, döngünün sonraki turunda değişebilir ve scoreboard yanlış beklenen değerle karşılaştırma yapar.
  • Tüm sürüşler clocking block (vif.drv_cb) üzerinden nonblocking (<=) yapılır; bu, DUT ile aradaki yarış koşullarını ortadan kaldırır.
  • in_valid darbesi yalnızca bir çevrim yüksek tutulur; DUT'un pipeline mantığı bu el sıkışmaya göre çıkış üretir.
  • rst_n doğrudan atanır (=), clocking block üzerinden değil; çünkü reset, clocking semantiğinin dışında kontrol edilmek istenen bir sinyaldir ve modport'ta output olarak ayrılmıştır.
  • run() sonsuz döngüdür; simülasyonun durması, generator'ın bitişi ve environment'taki bekleme/join_any mantığıyla yönetilir, driver kendi başına durmaz.
  • Operandlar in_valid düştükten sonra da pinlerde kalır. drive_transaction yalnızca in_valid'i sıfırlar; operand_a/b ve opcode bir sonraki transaction'a kadar eski değerlerini tutar. DUT in_valid=0 iken bu değerleri yok saydığı için sorun yoktur; ama monitor bu yüzden out_valid filtresine muhtaçtır.
  • Her transaction tam 2 saat çevrimi sürer: @(drv_cb) + sür, @(drv_cb) + in_valid<=0. Bu, DUT'un tek çevrimlik pipeline'ı için yeterlidir; ama arka arkaya işlem (back-to-back, her çevrim yeni in_valid) hiç test edilmez. Ek görevlerde bunu deneyeceksiniz.

Sürüşün Çevrim Çevrim Akışı

Kenar Driver (drv_cb, output #1) DUT (always_ff) Pinlerde görünen
E0 in_valid<=1, operandlar, opcode in_valid henüz 0 E0+1ns: in_valid=1, A, B, OP
E1 in_valid<=0 in_valid=1 → result<=, out_valid<=1 E1+1ns: in_valid=0; E1 sonrası out_valid=1, result hazır
E2 Sonraki txn: in_valid<=1, yeni operandlar in_valid=0 → out_valid<=0, result korunur E2'den hemen önce monitor E1 sonucunu okur

output #1 skew sayesinde driver'ın sürdüğü değerler saat kenarından 1 ns sonra değişir; DUT aynı kenarda eski (kararlı) değeri örnekler. Bu, Gün 5'teki clocking block dersinin tam uygulamasıdır.

Benzetme: Driver bir tercümandır: generator'ın soyut cümlesini ("A ile B'yi topla") DUT'un anladığı dile (pin seviyesinde 1'ler ve 0'lar, doğru saat kenarında) çevirir. İyi bir tercüman mesajın içeriğini değiştirmez; bu yüzden scoreboard'a gönderdiği kopya, sürdüğünün birebir aynısıdır.

Sık Yapılan Hatalar

  • drv2scb.put(txn) (kopyasız): Generator ve driver aynı nesneyi paylaşmıyor ama scoreboard beklenen kuyruğunda bu nesneyi tutar; ileride biri txn.result'ı doldurursa "beklenen" de değişir. Kopya alışkanlığı ucuzdur, hata pahalıdır.
  • Ham sinyal ile clocking block'u karıştırmak: vif.in_valid <= 1 (ham) ile vif.drv_cb.in_valid <= 1 (clocking) farklı zamanlarda etki eder; ikisini aynı testbench'te karıştırmak yarış koşulu üretir. Veri sinyallerini daima drv_cb üzerinden sürün.
  • Reset sırasında drv_cb sürüşlerinin ertelenmesini bilmemek: reset() içindeki vif.drv_cb.in_valid <= 0 ataması bir saat kenarında yapılmıyorsa bir sonraki kenara ertelenir; rst_n ise anında düşer. Bu fark repeat(5) @(vif.drv_cb) beklemesiyle kapatılır.
  • get ile put arasında zaman tüketmemek: Driver sürme süresi drive_transaction içindedir; onu function'a çevirmeye çalışmak derleme hatasıdır (@ içerir).
  • driven_count'u kopyadan sonra değil önce artırmak: Sayaç mantığı açısından fark etmez, ama wait(drv.driven_count == N) ile senkronize eden bir test, kopya henüz scoreboard'a gitmemişken uyanabilir. Sayaç artışını son adım olarak bırakın.

Kendinizi Deneyin

  1. drive_transaction'daki ikinci @(vif.drv_cb); in_valid <= 0; adımını kaldırın: in_valid sürekli 1 kalır ve her çevrim yeni transaction sürülür (back-to-back). Gün 7'deki monitor ve scoreboard bu hızda hâlâ doğru çalışıyor mu?
  2. drv2scb.put(txn.copy()) yerine drv2scb.put(txn) yazın ve Gün 7'de scoreboard hatası görüp görmediğinizi not edin. Göremezseniz neden "şans eseri" çalıştığını açıklayın (ipucu: bu projede driver txn'i sürdükten sonra bir daha değiştirmiyor).
  3. Driver'a int gap_cycles = 0; alanı ekleyin ve her transaction'dan sonra repeat (gap_cycles) @(vif.drv_cb); ile boşluk bırakın. Environment'tan farklı değerler verip DUT'un davranışının değişmediğini gösterin.
  4. reset()'e "reset sırasında in_valid bir an 1 olsun" hatası ekleyin (vif.drv_cb.in_valid <= 1 bir çevrim). DUT reset'ten çıkınca out_valid ne oluyor? Bu bir DUT hatası mı, test hatası mı?
Hızlı Kontrol: Driver scoreboard'a neden txn yerine txn.copy() gönderir?

Mailbox nesneyi değil handle'ı taşır. Aynı nesne iki yerde (driver döngüsü ve scoreboard kuyruğu) paylaşılırsa, bir tarafın yaptığı değişiklik diğerinde "beklenen değer" olarak görünür. Bağımsız bir kopya, scoreboard'un karşılaştırma yaptığı anda veriyi dondurur.