EDA Playground'da Dene

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ı Driver sı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_db ile 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): valid ve data output, ready input. Driver yalnızca sürer ve ready'yi okur.
  • mon_cb (monitor için): tüm sinyaller input. Monitor hiçbir şey sürmez; ama valid ve data'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, data sinyallerini içerir. İki clocking block vardır: cb driver içindir (output valid, data; input ready), mon_cb monitor içindir (tüm sinyaller input). Üç modport bu görünümleri dağıtır: tb (driver), mon (monitor) ve dut (sentezlenebilir taraf).
  • Driver sınıfı: virtual simple_if.tb vif tutamacını tutar. Kurucusu new(vif) ile bu tutamacı dışarıdan alır. drive task'i @(vif.cb) ile senkronlanıp vif.cb.valid/vif.cb.data'yı sürer ve do ... while (vif.cb.ready !== 1'b1) ile ready'yi bekler. txn_count gönderilen işlem sayısını tutar. reset task'i sinyalleri sıfırlar.
  • Monitor sınıfı: virtual simple_if.mon vif ile monitor görünümünü alır. run task'i forever döngüsünde her @(vif.mon_cb) olayında valid === 1'b1 && ready === 1'b1 koşulunu kontrol eder; gözlenen veriyi observed_data kuyruğuna ekler ve sayacı artırır. Monitor hiçbir sinyale yazamaz; mon_cb'de output yoktur.
  • simple_dut modülü: Her saatte sif.ready <= sif.valid yaparak ready'yi valid'in bir saat gecikmeli kopyası olarak üretir.
  • virtual_interface (top): Fiziksel simple_if sif(clk) örneklenir, DUT bağlanır. drv = new(sif) ve mon = new(sif) ile aynı fiziksel arayüz, iki farklı modport görünümüyle iki sınıfa verilir; SystemVerilog interface örneğini uygun virtual simple_if.tb / .mon tipine otomatik dönüştürür. fork ... join_none ile 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: virtual anahtar 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ü input olan 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/ready kontrolünde == yerine === kullanmak, X veya Z değerleri geldiğinde yanlış pozitif eşleşmeleri engeller. Reset öncesi sinyaller X olduğu için bu kontrol gerçekten gereklidir.
  • Aynı arayüz birçok sınıfa verilebilir: drv ve mon aynı 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 null olabilir: new(sif) ile atanmamış bir vif üzerinden @(vif.cb) yazmak çalışma zamanı hatasıdır. UVM'de bu yüzden uvm_config_db::get sonucu kontrol edilir ve vif == null ise uvm_fatal basılır.
  • Modport tipi uyumu zorunludur: virtual simple_if.tb tipindeki bir handle'a simple_if.mon gö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ı input clocking block kullanın.
  • vif'i kurucuda atamayı unutmak: this.vif = vif; satırı eksikse handle null kalır ve ilk @(vif.cb) çöker.
  • Monitor'ü join_none ile başlattıktan sonra disable fork çağırmak: Önceki derste anlatılan geniş kapsam sorunu; monitor sessizce ölür ve sayım yarım kalır.
  • == ile X kontrolü: vif.mon_cb.valid == 1'b1 ifadesi valid X iken X üretir, if bunu yanlış sayar; genelde sorun çıkmaz ama != 0 gibi yazımlarda X yanlış 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ızca virtual tutamaç tutar.

Kendinizi Deneyin

  1. Monitor içindeki vif.mon_cb.valid okumalarını vif.cb.valid yapın ve virtual simple_if.tb tipine geçin. Derleyici ne diyor? Kabul eden bir araçta sayım nasıl değişiyor?
  2. mon_cb'nin skew'ini default input #0 yapın. Zaman çizelgesi tablosunu yeniden çıkarın: monitor hangi kenarda sayıyor, sayı hâlâ 8 mi?
  3. İkinci bir simple_if sif2(clk) ve simple_dut dut2(sif2) ekleyin; aynı Driver sınıfından drv2 = new(sif2) yaratıp iki DUT'u paralel sürün. Sınıf kodunu hiç değiştirmeniz gerekmediğini doğrulayın.
  4. drv = new(sif); satırını silip çalıştırın. Hangi satırda hangi hata mesajı geldi? UVM'in vif == null kontrolünü neden build_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.