EDA Playground'da Dene

Lab 5: Interface + Coverage

Gün 5: Arayüzler, Assertion ve Coverage | Interface, Virtual Interface, Driver ve Coverage entegrasyonu

Bu lab, Gün 5 boyunca öğrendiğimiz tüm parçaları (interface, clocking block, virtual interface, transaction sınıfı, driver ve functional coverage) tek bir mini doğrulama ortamında birleştiriyor. Hedef, basit bir bellek DUT'unu mailbox tabanlı bir driver ile sürmek ve sürülen işlemlerin kapsamını ölçmek.

Lab'ın Mimarisi

Bu test ortamı, gerçek bir UVM ortamının sadeleştirilmiş bir taslağıdır. Akış şöyle ilerler:

  • Interface + clocking block (mem_if): DUT ile testbench arasındaki sinyalleri gruplar; cb clocking block'u senkronizasyonu sağlar. tb ve dut modport'ları iki tarafın bakış açısını ayırır.
  • Transaction (MemTransaction): Tek bir bellek işlemini (yazma/okuma, adres, veri) temsil eden, rastgele üretilebilen bir veri nesnesidir.
  • Driver (MemDriver): Bir mailbox üzerinden gelen transaction'ları alır ve virtual interface aracılığıyla DUT'a sürer.
  • Coverage (MemCoverage): Üretilen işlemlerin kapsamını covergroup ile ölçer.
  • Top (lab5_interface_coverage): Tüm bileşenleri bağlar, driver'ı arka planda çalıştırır ve işlemleri üretip mailbox'a koyar.

Producer–Consumer ve Mailbox

Bu labın can damarı mailbox ile kurulan üretici–tüketici (producer–consumer) modelidir. Üst modül işlemleri üretip mbx.put(txn) ile kuyruğa koyar; driver ise mbx.get(txn) ile bunları sırayla alıp sürer. mailbox, iki süreç arasında güvenli ve senkron veri aktarımı sağlar; tüketici hazır olana dek üreticiyi doğal biçimde dengeler. Bu ayrıştırma, stimulus üretimini sinyal seviyesinden bağımsız kılar — UVM'deki sequencer/driver ayrımının temelidir.

Kaynak Kod

// =============================================================================
// GÜN 5 - Lab 5: Interface + Virtual Interface + Driver + Coverage (Kapsam)
// =============================================================================

// Arayüz (Interface) tanımı
interface mem_if(input logic clk);
  logic        rst_n;
  logic        cs;       // çip seçme (chip select)
  logic        we;       // yazma yetkisi (write enable)
  logic [7:0]  addr;
  logic [31:0] wdata;
  logic [31:0] rdata;
  logic        ack;

  clocking cb @(posedge clk);
    default input #1step output #0;
    output cs, we, addr, wdata;
    input  rdata, ack;
  endclocking

  modport tb  (clocking cb, output rst_n, input clk);
  modport dut (input clk, rst_n, cs, we, addr, wdata, output rdata, ack);
endinterface

// Basit Bellek Tasarımı (DUT)
module memory_dut(mem_if.dut mif);
  logic [31:0] mem [256];

  always @(posedge mif.clk or negedge mif.rst_n) begin
    if (!mif.rst_n) begin
      mif.rdata <= 0;
      mif.ack   <= 0;
    end else if (mif.cs) begin
      if (mif.we) begin
        mem[mif.addr] <= mif.wdata;
      end else begin
        mif.rdata <= mem[mif.addr];
      end
      mif.ack <= 1;
    end else begin
      mif.ack <= 0;
    end
  end
endmodule

// İşlem (Transaction) sınıfı
class MemTransaction;
  rand bit         we;
  rand bit [7:0]   addr;
  rand logic [31:0] wdata;
  logic [31:0]    rdata;
  int              id;
  static int       count = 0;

  constraint c_addr {
    addr inside {[0:63]};
  }

  //
  constraint c_data {
    solve we before wdata; // ÖNEMLİ: Önce we sinyalini %50 olasılıkla hesaplasın diye koyduk
    we -> wdata != 0;
    !we -> wdata == 0;
  }

  function new();
    id = count++;
  endfunction

  function void display(string prefix = "");
    $display("  %sTXN#%0d | %s | Addr=0x%02h | WData=0x%08h | RData=0x%08h",
             prefix, id, we ? "WR" : "RD", addr, wdata, rdata);
  endfunction
endclass

// Driver sınıfı (sanal arayüz - virtual interface kullanarak)
class MemDriver;
  virtual mem_if.tb vif;
  mailbox #(MemTransaction) mbx;
  int driven_count = 0;

  function new(virtual mem_if.tb vif, mailbox #(MemTransaction) mbx);
    this.vif = vif;
    this.mbx = mbx;
  endfunction

  task run();
    MemTransaction txn;
    forever begin
      mbx.get(txn);
      @(vif.cb);
      vif.cb.cs    <= 1;
      vif.cb.we    <= txn.we;
      vif.cb.addr  <= txn.addr;
      vif.cb.wdata <= txn.wdata;
      
      // Onay (ack) sinyalini senkronize şekilde bekle (simülatörün asılı kalmasını önler)
      do begin
        @(vif.cb);
      end while (vif.cb.ack == 0);

      if (!txn.we)
        txn.rdata = vif.cb.rdata;
        
      vif.cb.cs <= 0;
      
      // Tasarımın (DUT) CS=0 sinyalini görmesi ve ACK'yı düşürmesi için boş (idle) döngü
      @(vif.cb);
      
      driven_count++;
      txn.display("[Driver] ");
    end
  endtask

  task reset();
    vif.rst_n = 0;
    vif.cb.cs    <= 0;
    vif.cb.we    <= 0;
    vif.cb.addr  <= 0;
    vif.cb.wdata <= 0;
    repeat(5) @(vif.cb);
    vif.rst_n = 1;
    @(vif.cb);
    $display("  [Driver] Reset tamamlandı");
  endtask
endclass

// Kapsam (Coverage) sınıfı
class MemCoverage;
  covergroup mem_cg with function sample(bit we, bit [7:0] addr, logic [31:0] data);
    cp_we: coverpoint we {
      bins read  = {0};
      bins write = {1};
    }

    cp_addr: coverpoint addr {
      bins low   = {[0:15]};
      bins mid   = {[16:31]};
      bins high  = {[32:47]};
      bins upper = {[48:63]};
    }

    cp_data: coverpoint data {
      bins zero    = {0};
      bins nonzero = default; // Hazırlık (elaboration) sırasında aracın asılı kalmasını önler
    }

    cx_we_addr: cross cp_we, cp_addr;
  endgroup

  // Covergroup, içine gömüldüğünde kendi örnek tipi (instance type) gibi davranır
  function new();
    mem_cg = new();
  endfunction

  function void sample(MemTransaction txn);
    mem_cg.sample(txn.we, txn.addr, txn.wdata);
  endfunction

  function void report();
    $display("\n  --- Kapsam Raporu (Coverage Report) ---");
    $display("  Toplam      : %.1f%%", mem_cg.get_coverage());
    $display("  cp_we       : %.1f%%", mem_cg.cp_we.get_coverage());
    $display("  cp_addr     : %.1f%%", mem_cg.cp_addr.get_coverage());
    $display("  cp_data     : %.1f%%", mem_cg.cp_data.get_coverage());
    $display("  cx_we_addr : %.1f%%", mem_cg.cx_we_addr.get_coverage());
  endfunction
endclass

// Üst seviye test ortamı (Testbench)
module lab5_interface_coverage;
  logic clk = 0;
  always #5 clk = ~clk;

  mem_if mif(clk);
  memory_dut dut(mif);

  mailbox #(MemTransaction) mbx;
  MemDriver    drv;
  MemCoverage cov;

  initial begin
    // Örtük statik değişken hatalarını önlemek için döngü dışında tanımlanmıştır
    MemTransaction txn;

    $display("============================================================");
    $display("  LAB 5: Interface + Driver + Coverage");
    $display("============================================================\n");

    mbx = new();
    drv = new(mif, mbx);
    cov = new();

    // Reset dizisi
    drv.reset();

    // Driver'ı arka planda çalıştır
    fork
      drv.run();
    join_none

    // İşlemleri (transaction) oluştur ve kuyruğa (mailbox) koy
    repeat (50) begin
      txn = new(); // Nesneyi burada başlat
      assert(txn.randomize()) else $fatal(1, "Randomize başarısız!");
      cov.sample(txn);
      mbx.put(txn);
    end

    // Tüm işlemlerin tamamen sürülmesini bekle
    wait(drv.driven_count == 50);
    #100;

    // Raporlama
    $display("\n  --- İstatistikler ---");
    $display("  Toplam sürülen: %0d", drv.driven_count);
    cov.report();

    $display("\n============================================================");
    $display("  LAB 5 TAMAMLANDI");
    $display("============================================================");
    $finish;
  end
endmodule

Kodun Açıklaması

  • mem_if arayüzü: Bellek sinyallerini (rst_n, cs, we, addr, wdata, rdata, ack) gruplar. clocking cb bloğu default input #1step output #0 ile zamanlamayı yönetir. tb modport'u clocking block'u ve rst_n çıkışını içerir; dut modport'u sentezlenebilir tarafı tanımlar.
  • memory_dut modülü: posedge mif.clk or negedge mif.rst_n ile çalışır. Reset'te çıkışları sıfırlar; mif.cs aktifken mif.we ise mem[mif.addr]'a yazar, değilse mif.rdata'ya okur ve ack'i 1 yapar. cs düştüğünde ack'i geri 0'a çeker.
  • MemTransaction sınıfı: we, addr, wdata rastgele üretilir. c_addr adresi 0-63'e sınırlar. c_data kısıtında solve we before wdata ile önce we çözülür (yazma/okuma ~%50 dengesi için); we ise wdata != 0, değilse wdata == 0 zorlanır. static int count ile her nesneye benzersiz id atanır.
  • MemDriver sınıfı: virtual mem_if.tb vif ve mailbox #(MemTransaction) mbx tutar. run task'i forever döngüsünde mbx.get(txn) ile işlem alır, vif.cb üzerinden DUT'u sürer, do ... while (vif.cb.ack == 0) ile senkron olarak ack'i bekler, okuma ise txn.rdata'yı örnekler. cs'yi düşürüp bir idle çevrim bekler ve driven_count'u artırır. reset task'i rst_n'yi indirip 5 çevrim sonra geri kaldırır.
  • MemCoverage sınıfı: mem_cg covergroup'u cp_we (read/write), cp_addr (dört adres bölgesi) ve cp_data (zero/nonzero) coverpoint'lerini ve cx_we_addr çaprazını içerir. sample(MemTransaction txn) sarmalayıcı fonksiyonu transaction alanlarını covergroup'a aktarır. report kapsam yüzdelerini basar.
  • lab5_interface_coverage (top): mem_if, DUT, mbx, drv ve cov oluşturulur. drv.reset() sonrası fork ... join_none ile driver arka planda başlatılır. repeat(50) döngüsünde her işlem randomize edilip cov.sample(txn) ile örneklenir ve mbx.put(txn) ile kuyruğa konur. wait(drv.driven_count == 50) tüm işlemlerin sürülmesini bekler, sonra rapor basılır.

Önemli Noktalar

  • solve ... before sıralamayı yönetir: solve we before wdata, çözücünün önce we'yi belirlemesini sağlayarak yazma/okuma dağılımını dengeler; c_data kısıtının yön bağımlılığı bu sayede tutarlı çalışır.
  • ack'i wait yerine senkron bekleyin: do begin @(vif.cb); end while (vif.cb.ack == 0) kullanımı, simülatörün asılı kalmasını ve yarış koşullarını engeller.
  • default bin asılı kalmayı önler: cp_data içindeki nonzero = default yorumu, elaboration sırasında 32-bit veri için devasa bin üretiminin yarattığı takılmayı engellemek içindir.
  • Mailbox ile bileşenleri ayırın: Stimulus üretimi (top) ile sinyal sürüşü (driver) mailbox üzerinden ayrıştırılmıştır; bu, ölçeklenebilir ve yeniden kullanılabilir bir yapıdır.
  • Kapsamı sürüşten önce örnekleyin: Burada cov.sample(txn) işlem mailbox'a konmadan önce çağrılır; kapsam, gönderilen stimulus'a göre ölçülür. Gerçek doğrulamada genellikle monitör gözlemini örneklemek tercih edilir.
  • join_none ile arka plan süreci: Driver fork ... join_none ile başlatıldığından üst akış engellenmez; senkronizasyon driven_count üzerinden wait ile sağlanır.