Virtual Interface
Gün 5: Arayüzler, Assertion ve Coverage | Sınıflar içinden fiziksel arayüze erişim
Bu derste virtual interface (sanal arayüz) kavramını öğreneceğiz: nesne tabanlı doğrulama sınıflarının (Driver, Monitor) statik modül hiyerarşisindeki fiziksel arayüze nasıl bağlandığını göreceğiz.
Virtual Interface Neden Gerekli?
SystemVerilog'da interface ve module örnekleri statiktir; simülasyon başlamadan önce elaboration aşamasında oluşturulur ve sabit bir hiyerarşide yer alır. Sınıflar (class) ise dinamiktir; çalışma zamanında new() ile oluşturulur, kopyalanır, mailbox üzerinden taşınır.
Bir sınıf doğrudan fiziksel bir arayüz örneğini içeremez. İşte bu boşluğu virtual interface doldurur:
- Köprü görevi görür: Statik dünyadaki arayüz örneğine, dinamik sınıf dünyasından bir tutamaç (handle) ile erişim sağlar.
- Yeniden kullanılabilir bileşenler: Aynı
Driversınıfı, kendisine hangi sanal arayüz verilirse o fiziksel arayüzü sürer. Sınıf, hangi DUT'a bağlandığını bilmek zorunda kalmaz. - UVM'in temelidir: UVM driver ve monitor'leri tamamen bu mekanizma üzerine kuruludur;
uvm_config_dbile taşınan en yaygın nesne tipi virtual interface'tir.
Benzetme: Fiziksel interface binaya döşenmiş kablolamadır; yerinden oynamaz. Virtual interface ise elinizdeki uzaktan kumandadır: kabloya dokunmadan, hangi odada olursanız olun aynı cihazı kontrol edersiniz. Aynı kumandayı (Driver sınıfı) farklı bir eve (başka bir interface örneği) götürüp eşleştirmek yeterlidir.
Çalışma Mantığı
Sınıf içinde virtual simple_if.tb vif; şeklinde bir tutamaç tanımlanır. Top modülde fiziksel arayüz (sif) oluşturulur ve sınıf kurucusuna (new(sif)) aktarılır. Bu andan sonra sınıf, vif.cb.valid gibi ifadelerle gerçek sinyalleri sürebilir. Burada arayüzün tb modport'u ve içindeki clocking block (cb) kullanıldığı için sürüş ve örnekleme senkronizasyonu otomatik yönetilir.
Driver ve Monitor İçin Ayrı Clocking Block
Bu derste iki clocking block vardır ve bu bilinçli bir tasarımdır:
cb(driver için):validvedataoutput,readyinput. Driver yalnızca sürer veready'yi okur.mon_cb(monitor için): tüm sinyaller input. Monitor hiçbir şey sürmez; amavalidvedata'yı okuması gerekir.
Neden tek clocking block yetmez? Çünkü standart gereği bir clocking block'un output olarak tanımlanan sinyali okunamaz. Monitor, cb.valid'i okumaya kalksa derleyici bunu reddeder (veya tanımsız davranış üretir). Monitor'ün kendi "salt-okuma" penceresine sahip olması hem bu kuralı karşılar hem de monitor'ün pasif kalmasını derleyici düzeyinde garanti eder. Gün 6 projesindeki alu_if de aynı kalıbı (drv_cb / mon_cb) kullanır.
Kaynak Kod
// =============================================================================
// GUN 5 - Konu 3: Virtual Interface (Interface'e Class'tan Erisim)
// =============================================================================
interface simple_if(input logic clk);
logic valid;
logic ready;
logic [7:0] data;
// Driver clocking block: valid/data surulur, ready okunur
clocking cb @(posedge clk);
default input #1step output #0;
output valid, data;
input ready;
endclocking
// Monitor clocking block: her sey salt-okuma
// (Clocking block OUTPUT'lari okunamaz; monitor'un kendi input penceresi gerekir)
clocking mon_cb @(posedge clk);
default input #1step;
input valid, ready, data;
endclocking
modport tb (clocking cb, input clk);
modport mon (clocking mon_cb, input clk);
modport dut (input clk, valid, data, output ready);
endinterface
// --- Class: Virtual Interface kullanan Driver ---
class Driver;
virtual simple_if.tb vif; // Virtual interface handle (driver gorunumu)
int txn_count = 0;
function new(virtual simple_if.tb vif);
this.vif = vif;
endfunction
task drive(input logic [7:0] data);
@(vif.cb);
vif.cb.valid <= 1;
vif.cb.data <= data;
// ready'yi saat kenarina senkron bekle
do begin
@(vif.cb);
end while (vif.cb.ready !== 1'b1);
vif.cb.valid <= 0;
txn_count++;
$display(" [%0t][Driver] Gonderildi: data=0x%02h (#%0d)", $time, data, txn_count);
endtask
task reset();
vif.cb.valid <= 0;
vif.cb.data <= 0;
@(vif.cb);
$display(" [%0t][Driver] Reset uygulandi", $time);
endtask
endclass
// --- Class: Monitor ---
class Monitor;
virtual simple_if.mon vif; // Monitor gorunumu: yalnizca okur
int observed_count = 0;
logic [7:0] observed_data [$];
function new(virtual simple_if.mon vif);
this.vif = vif;
endfunction
task run();
$display(" [Monitor] Dinleme basladi");
forever begin
@(vif.mon_cb);
// === (case equality) kullan: X veya Z geldiginde yanlis pozitif olmasin
if (vif.mon_cb.valid === 1'b1 && vif.mon_cb.ready === 1'b1) begin
observed_count++;
observed_data.push_back(vif.mon_cb.data);
$display(" [%0t][Monitor] Gozlendi: data=0x%02h (#%0d)",
$time, vif.mon_cb.data, observed_count);
end
end
endtask
endclass
// Basit DUT
module simple_dut(simple_if.dut sif);
always @(posedge sif.clk) begin
sif.ready <= sif.valid; // ready, valid'in 1 cevrim gecikmeli kopyasi
end
endmodule
// Top-level
module virtual_interface;
logic clk = 0;
always #5 clk = ~clk;
simple_if sif(clk);
simple_dut dut(sif);
Driver drv;
Monitor mon;
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
initial begin
$display("=== Virtual Interface ===\n");
// Ayni fiziksel interface, iki farkli modport gorunumuyle iki sinifa verilir
drv = new(sif);
mon = new(sif);
// Monitor'u arka planda calistir
fork
mon.run();
join_none
// Reset dizisi
drv.reset();
#20;
// Veri gonder
for (int i = 0; i < 8; i++) begin
drv.drive(i * 16 + 8'hA0);
end
// Son el sikismanin monitor tarafindan goruldugu kenari bekle
#20;
$display("\n --- Sonuclar ---");
$display(" Driver gonderdi : %0d", drv.txn_count);
$display(" Monitor gozledi : %0d", mon.observed_count);
$display(" Gozlenen veri : %p", mon.observed_data);
$display("\n=== Virtual Interface Sonu ===");
$finish;
end
endmodule
Kodun Açıklaması
interface simple_if:valid,ready,datasinyallerini içerir. İki clocking block vardır:cbdriver içindir (output valid, data; input ready),mon_cbmonitor içindir (tüm sinyallerinput). Üç modport bu görünümleri dağıtır:tb(driver),mon(monitor) vedut(sentezlenebilir taraf).Driversınıfı:virtual simple_if.tb viftutamacını tutar. Kurucusunew(vif)ile bu tutamacı dışarıdan alır.drivetask'i@(vif.cb)ile senkronlanıpvif.cb.valid/vif.cb.data'yı sürer vedo ... while (vif.cb.ready !== 1'b1)ileready'yi bekler.txn_countgönderilen işlem sayısını tutar.resettask'i sinyalleri sıfırlar.Monitorsınıfı:virtual simple_if.mon vifile monitor görünümünü alır.runtask'iforeverdöngüsünde her@(vif.mon_cb)olayındavalid === 1'b1 && ready === 1'b1koşulunu kontrol eder; gözlenen veriyiobserved_datakuyruğuna ekler ve sayacı artırır. Monitor hiçbir sinyale yazamaz;mon_cb'de output yoktur.simple_dutmodülü: Her saattesif.ready <= sif.validyaparakready'yivalid'in bir saat gecikmeli kopyası olarak üretir.virtual_interface(top): Fizikselsimple_if sif(clk)örneklenir, DUT bağlanır.drv = new(sif)vemon = new(sif)ile aynı fiziksel arayüz, iki farklı modport görünümüyle iki sınıfa verilir; SystemVerilog interface örneğini uygunvirtual simple_if.tb/.montipine otomatik dönüştürür.fork ... join_noneile monitör arka planda başlatılır, ardından reset uygulanıp 8 veri gönderilir.
Bir İşlemin Zaman Çizelgesi
| Kenar | Driver (cb) |
DUT | Monitor (mon_cb, Preponed örnekleme) |
|---|---|---|---|
| E0 | valid<=1, data<=D |
valid henüz 0 → ready<=0 |
valid=0 → yok |
| E1 | ready (preponed) = 0 → bekle |
valid=1 → ready<=1 |
valid=1, ready=0 → yok |
| E2 | ready = 1 → çık, valid<=0, say |
valid=1 → ready<=1 |
valid=1, ready=1 → say |
| E3 | Sonraki drive: @(cb) → valid<=1 |
valid=0 → ready<=0 |
valid=0 → yok |
Her işlem 3 kenar sürer; driver E2'de, monitor da aynı kenarda sayar. Monitor Preponed bölgede örneklediği için driver'ın E2'deki valid<=0 sürüşünü değil, kenardan hemen önceki valid=1 değerini görür. Böylece 8 işlem → 8 gözlem olur ve iki sayaç eşittir. #20 bekleme, son işlemin E2 kenarının geçmesini garanti eder.
Önemli Noktalar
- Sınıf statik arayüz örneğini tutamaz:
virtualanahtar kelimesi tam da bu yüzden gereklidir; sınıfa fiziksel arayüze dinamik bir tutamaç verir. - Clocking block çıkışları okunamaz: Bu yüzden monitor için ayrı, tümü
inputolan bir clocking block (mon_cb) tanımlanır. Tek clocking block ile "hem sür hem oku" yapmaya çalışmak hem standarda aykırıdır hem de monitor'ün pasifliğini zedeler. ===(case equality) kullanın:valid/readykontrolünde==yerine===kullanmak,XveyaZdeğerleri geldiğinde yanlış pozitif eşleşmeleri engeller. Reset öncesi sinyallerXolduğu için bu kontrol gerçekten gereklidir.- Aynı arayüz birçok sınıfa verilebilir:
drvvemonaynısif'i paylaşır; sınıflar birbirinden bağımsız kalır ama aynı sinyalleri görür. - Senkronizasyonu clocking block yapar: Sürüş ve örnekleme
vif.cb/vif.mon_cbüzerinden yapıldığı için TB ile DUT arasındaki yarış koşulları engellenir. - Virtual interface
nullolabilir:new(sif)ile atanmamış birvifüzerinden@(vif.cb)yazmak çalışma zamanı hatasıdır. UVM'de bu yüzdenuvm_config_db::getsonucu kontrol edilir vevif == nulliseuvm_fatalbasılır. - Modport tipi uyumu zorunludur:
virtual simple_if.tbtipindeki bir handle'asimple_if.mongörünümü atanamaz. Bileşenin ihtiyacına göre doğru modport'u seçin.
Sık Yapılan Hatalar
- Monitor'ün clocking çıkışını okuması:
vif.cb.valid(output) okunamaz; monitor için ayrıinputclocking block kullanın. vif'i kurucuda atamayı unutmak:this.vif = vif;satırı eksikse handlenullkalır ve ilk@(vif.cb)çöker.- Monitor'ü
join_noneile başlattıktan sonradisable forkçağırmak: Önceki derste anlatılan geniş kapsam sorunu; monitor sessizce ölür ve sayım yarım kalır. ==ileXkontrolü:vif.mon_cb.valid == 1'b1ifadesivalidXikenXüretir,ifbunu yanlış sayar; genelde sorun çıkmaz ama!= 0gibi yazımlardaXyanlış pozitif verebilir.===alışkanlık olsun.- Interface'i sınıfın içinde örneklemeye çalışmak:
simple_if sif(clk);bir sınıfın içinde yazılamaz; interface'ler yalnızca modül/program/interface içinde örneklenir. Sınıf yalnızcavirtualtutamaç tutar.
Kendinizi Deneyin
Monitoriçindekivif.mon_cb.validokumalarınıvif.cb.validyapın vevirtual simple_if.tbtipine geçin. Derleyici ne diyor? Kabul eden bir araçta sayım nasıl değişiyor?mon_cb'nin skew'inidefault input #0yapın. Zaman çizelgesi tablosunu yeniden çıkarın: monitor hangi kenarda sayıyor, sayı hâlâ 8 mi?- İkinci bir
simple_if sif2(clk)vesimple_dut dut2(sif2)ekleyin; aynıDriversınıfındandrv2 = new(sif2)yaratıp iki DUT'u paralel sürün. Sınıf kodunu hiç değiştirmeniz gerekmediğini doğrulayın. drv = new(sif);satırını silip çalıştırın. Hangi satırda hangi hata mesajı geldi? UVM'invif == nullkontrolünü nedenbuild_phase'de yaptığını açıklayın.
Hızlı Kontrol: Bir sınıf neden doğrudan simple_if sif; şeklinde bir interface tutamayıp virtual simple_if kullanır?
Interface örnekleri statik hiyerarşinin parçasıdır ve elaboration'da yaratılır; sınıflar ise dinamiktir. Sınıfın içine fiziksel bir örnek konamaz; yalnızca ona işaret eden bir tutamaç tutulabilir. virtual anahtar kelimesi bu tutamacı tanımlar ve top modülde yaratılan gerçek örnek kurucu üzerinden atanır.