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.

Ç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.

Kaynak Kod

// =============================================================================
// GUN 5 - Konu 3: Virtual Interface (Interface'e Class'tan Erişim)
// =============================================================================

interface simple_if(input logic clk);
  logic       valid;
  logic       ready;
  logic [7:0] data;

  // Clocking block sinyalleri clock edge'i ile senkronize eder
  clocking cb @(posedge clk);
    default input #1step output #0;
    output valid, data;
    input  ready;
  endclocking

  modport tb  (clocking 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
  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;
    
    // Senkron bir şekilde ready'i bekle
    do begin
      @(vif.cb);
    end while (vif.cb.ready !== 1'b1);
    
    vif.cb.valid <= 0;
    txn_count++;
    $display("  [%0t][Driver] Sent: 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 applied", $time);
  endtask
endclass

// --- Class: Monitor ---
class Monitor;
  virtual simple_if.tb vif;
  int observed_count = 0;
  logic [7:0] observed_data [$];

  function new(virtual simple_if.tb vif);
    this.vif = vif;
  endfunction

  task run();
    $display("  [Monitor] Listening started");
    forever begin
      @(vif.cb);
      // === equality check kullan! X ve Z geldiğinde false positive durum olabilir
      if (vif.cb.valid === 1'b1 && vif.cb.ready === 1'b1) begin
        observed_count++;
        observed_data.push_back(vif.cb.data);
        $display("  [%0t][Monitor] Observed: data=0x%02h (#%0d)",
                 $time, vif.cb.data, observed_count);
      end
    end
  endtask
endclass

// Simple DUT
module simple_dut(simple_if.dut sif);
  always @(posedge sif.clk) begin
    sif.ready <= sif.valid;  // Ready - 1 cycle delay
  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");

    drv = new(sif);
    mon = new(sif);

    // Monitor'ü çalıştır
    fork
      mon.run();
    join_none

    // Reset sequence
    drv.reset();
    #20;

    // Send data
    for (int i = 0; i < 8; i++) begin
      drv.drive(i * 16 + 'hA0);
    end

    // Monitor'un son transaction'ı da algılaması için biraz beklet!
    // ÖNEMLİ NOT: Beklesek bile algılamayacak, sizce neden?
    // fixed.sv'de cevabı var, bu dosyanın içeriğini silip onu yapıştırabilirsin.
    #50;
    $display("\n  --- Results ---");
    $display("  Driver sent     : %0d", drv.txn_count);
    $display("  Monitor observed: %0d", mon.observed_count);
    $display("  Observed data   : %p", mon.observed_data);

    $display("\n=== End of Virtual Interface ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • interface simple_if: valid, ready, data sinyallerini içerir. İçindeki clocking cb bloğu sinyalleri saat kenarına senkronize eder (output valid, data; input ready). İki modport vardır: tb (clocking block ve clk görünümü) 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ı: Yine vif tutamacı üzerinden çalışır. run task'i forever döngüsünde her clocking olayında vif.cb.valid === 1'b1 && vif.cb.ready === 1'b1 koşulunu kontrol eder; gözlenen veriyi observed_data kuyruğuna push_back ile ekler ve observed_count'u artırır.
  • 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 her iki sınıfa aynı fiziksel arayüz sanal arayüz olarak verilir. fork ... join_none ile monitör arka planda başlatılır, ardından reset uygulanıp 8 veri gönderilir.

Ö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.
  • === (case equality) kullanın: Koddaki yorumun da vurguladığı gibi, valid/ready kontrolünde == yerine === kullanmak, X veya Z değerleri geldiğinde yanlış pozitif eşleşmeleri engeller.
  • 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 üzerinden yapıldığı için TB ile DUT arasındaki yarış koşulları engellenir.
  • Son işlem gözlem sorunu: Dosyadaki yorumun işaret ettiği gibi, monitör son transaction'ı bu kurguda algılamayabilir; bu, clocking block örnekleme zamanlaması ve simülasyonun sonlanma anıyla ilgili bilinçli bir tartışma noktasıdır.