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):
gen2drvmailbox'ından transaction alır. - Alt taraf (sinyal seviyesi):
virtual alu_if.driver vifaracı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_validel 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 vifDUT sinyallerine erişimi sağlar;gen2drvgenerator'dan girişi,drv2scbscoreboard'a çıkışı taşır;driven_countsü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üdegen2drv.get(txn)ile bir transaction bekler,drive_transaction(txn)ile pinlere uygular, ardındandrv2scb.put(txn.copy())ile kopyasını scoreboard'a yollar vedriven_count++yapar.drive_transaction(txn):@(vif.drv_cb)ile saat kenarını bekler,in_valid <= 1yapıp operandları veopcode'u sürer. Bir sonraki kenardain_valid <= 0ile geçerli darbeyi tek çevrimlik tutar.reset():vif.rst_n = 0ile reset'i etkinleştirir, tüm girişleri sıfırlar,repeat(5) @(vif.drv_cb)ile beş çevrim bekler, sonravif.rst_n = 1ile reset'ten çıkar.
Önemli Noktalar
drv2scb.put(txn.copy())kopyalama şarttır: Aynıtxnnesnesi 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_validdarbesi yalnızca bir çevrim yüksek tutulur; DUT'un pipeline mantığı bu el sıkışmaya göre çıkış üretir.rst_ndoğrudan atanır (=), clocking block üzerinden değil; çünkü reset, clocking semantiğinin dışında kontrol edilmek istenen bir sinyaldir ve modport'taoutputolarak ayrılmıştır.run()sonsuz döngüdür; simülasyonun durması, generator'ın bitişi ve environment'taki bekleme/join_anymantığıyla yönetilir, driver kendi başına durmaz.- Operandlar
in_validdüştükten sonra da pinlerde kalır.drive_transactionyalnızcain_valid'i sıfırlar;operand_a/bveopcodebir sonraki transaction'a kadar eski değerlerini tutar. DUTin_valid=0iken bu değerleri yok saydığı için sorun yoktur; ama monitor bu yüzdenout_validfiltresine 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 yeniin_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 biritxn.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) ilevif.drv_cb.in_valid <= 1(clocking) farklı zamanlarda etki eder; ikisini aynı testbench'te karıştırmak yarış koşulu üretir. Veri sinyallerini daimadrv_cbüzerinden sürün. - Reset sırasında
drv_cbsürüşlerinin ertelenmesini bilmemek:reset()içindekivif.drv_cb.in_valid <= 0ataması bir saat kenarında yapılmıyorsa bir sonraki kenara ertelenir;rst_nise anında düşer. Bu farkrepeat(5) @(vif.drv_cb)beklemesiyle kapatılır. getileputarasında zaman tüketmemek: Driver sürme süresidrive_transactioniçindedir; onufunction'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, amawait(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
drive_transaction'daki ikinci@(vif.drv_cb); in_valid <= 0;adımını kaldırın:in_validsü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?drv2scb.put(txn.copy())yerinedrv2scb.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 drivertxn'i sürdükten sonra bir daha değiştirmiyor).- Driver'a
int gap_cycles = 0;alanı ekleyin ve her transaction'dan sonrarepeat (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. reset()'e "reset sırasındain_validbir an 1 olsun" hatası ekleyin (vif.drv_cb.in_valid <= 1bir çevrim). DUT reset'ten çıkıncaout_validne 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.