EDA Playground'da Dene

UVM Eğitimi 3. Gün Laboratuvar Kılavuzu: TLM Haberleşmesi, Scoreboard ve Çevre (Environment) Kurulumu

Amaç

Bu laboratuvarın amacı, UVM bileşenleri arasında veri aktarımı sağlayan TLM (Transaction Level Modeling) portlarını kullanmak, verileri doğrulamak için bir Scoreboard tasarlamak ve fonksiyonel kapsama (coverage) verilerini toplamak için iki farklı yaklaşımı (Geleneksel TLM Tabanlı ve Modern Handle Tabanlı) karşılaştırmalı olarak uygulamaktır. Ayrıca simülasyon sonunda kapsama raporlarının nasıl alınacağını göreceğiz.

Ön Koşullar

  • 1 ve 2. gün konuları (Agent hiyerarşisi, Sequence Item oluşturma).
  • Nesne yönelimli programlamada (OOP) referanslar (handle) ve write gibi fonksiyonların işleyişi.

Lab 3.1: Monitor'e Analysis Port ve Coverage Handle Eklenmesi

Monitor, arayüzden okuduğu veriyi diğer bileşenlere iletmekle görevlidir. Bu veriyi standart TLM yapısıyla (Scoreboard ve TLM-Coverage için) "yayınlamak" (broadcast) için bir uvm_analysis_port kullanacağız. Ayrıca modern yaklaşımı göstermek için Handle-Based Coverage Collector sınıfımızın referansını da Monitor içine tanımlayacağız.

Görev: 2. Gün yazdığımız my_monitor sınıfını aşağıdaki gibi güncelleyin.

class my_monitor extends uvm_monitor;  
  `uvm_component_utils(my_monitor)  
    
  // 1. Declare the analysis port (Used for Scoreboard and TLM Coverage)  
  uvm_analysis_port #(my_transaction) ap;  
    
  // 2. Declare a handle for direct coverage sampling (Modern approach)  
  my_handle_coverage cov_handle;  
    
  // Constructor  
  function new(string name = "my_monitor", uvm_component parent = null);  
    super.new(name, parent);  
  endfunction  
    
  // Build phase: Instantiate the analysis port  
  virtual function void build_phase(uvm_phase phase);  
    super.build_phase(phase);  
    ap = new("ap", this);  
  endfunction  
    
  // Run phase: Monitor signals and broadcast/sample the transaction  
  virtual task run_phase(uvm_phase phase);  
    my_transaction tr;  
    super.run_phase(phase);  
      
    forever begin  
      #20; // Simulated sampling delay  
        
      // Create a dummy transaction to represent sampled data  
      tr = my_transaction::type_id::create("tr");  
      tr.addr     = 8'hAA;   
      tr.data     = 32'h12345678;  
      tr.write_en = 1'b1;  
        
      `uvm_info("MONITOR", "Sampled data from virtual interface...", UVM_LOW)  
        
      // 1. Broadcast via TLM (Will be received by Scoreboard and TLM Coverage)  
      ap.write(tr);  
        
      // 2. Send to modern coverage collector via direct function call   
      if (cov_handle != null) begin  
        cov_handle.sample_cov(tr.write_en);  
      end  
    end  
  endtask  
endclass

Kodun Açıklaması

Bu blokta my_monitor sınıfı iki farklı haberleşme yöntemini aynı anda barındıracak şekilde tasarlanmıştır:

  • Bildirimler: uvm_analysis_port #(my_transaction) ap ifadesi, my_transaction tipinde verileri "yayınlamak" (broadcast) için kullanılan TLM analiz portunu tanımlar. Yanında tanımlanan my_handle_coverage cov_handle ise modern (handle tabanlı) yöntem için bir referanstır; henüz bir nesneye işaret etmez, yalnızca bir el (handle) tutar.
  • build_phase: Burada ap = new("ap", this) ile analiz portu örneklendirilir. UVM'de portlar da bileşen hiyerarşisine bağlı olduğu için this parametresi ile sahibine (Monitor'e) bağlanır.
  • run_phase: forever döngüsü içinde #20 gecikmeyle örnekleme simüle edilir. Her turda my_transaction::type_id::create("tr") ile factory üzerinden bir transaction üretilir ve addr, data, write_en alanları doldurulur.
  • İki kanaldan dağıtım: ap.write(tr) çağrısı, porta bağlı TÜM dinleyicilere (Scoreboard ve TLM tabanlı Coverage) aynı transaction'ı iletir; bu broadcast davranışıdır. Hemen ardından if (cov_handle != null) kontrolü yapılarak, eğer handle bir nesneye bağlanmışsa cov_handle.sample_cov(tr.write_en) ile doğrudan fonksiyon çağrısı yapılır. null kontrolü, handle bağlanmadan çalıştırılırsa simülasyonun çökmesini engeller.

Lab 3.2: UVM Scoreboard Tasarımı

Scoreboard, Monitor'den gelen işlemleri dinler (Analysis Implementation aracılığıyla) ve doğruluğunu kontrol eder.

Görev: my_scoreboard adında bir sınıf oluşturun. Bir uvm_analysis_imp portu ekleyin ve mecburi olan write() fonksiyonunu tanımlayın.

class my_scoreboard extends uvm_scoreboard;  
  `uvm_component_utils(my_scoreboard)  
    
  // Declare the analysis implementation port  
  uvm_analysis_imp #(my_transaction, my_scoreboard) ap_imp;  
    
  // Constructor  
  function new(string name = "my_scoreboard", uvm_component parent = null);  
    super.new(name, parent);  
  endfunction  
    
  // Build phase  
  virtual function void build_phase(uvm_phase phase);  
    super.build_phase(phase);  
    ap_imp = new("ap_imp", this);  
  endfunction  
    
  // The mandatory write() method triggered by ap.write()  
  virtual function void write(my_transaction tr);  
    if(tr.addr == 8'hAA && tr.data == 32'h12345678) begin  
      `uvm_info("SCOREBOARD", "Data matches expected values! [PASS]", UVM_LOW)  
    end else begin  
      `uvm_error("SCOREBOARD", "Data mismatch detected! [FAIL]")  
    end  
  endfunction  
endclass

Kodun Açıklaması

my_scoreboard sınıfı, Monitor'den gelen veriyi alıp doğrulayan pasif bir bileşendir:

  • uvm_analysis_imp #(my_transaction, my_scoreboard) ap_imp: Bu satır bir analysis "implementation" portu tanımlar. İki tip parametresine dikkat edin: birincisi taşınacak veri tipi (my_transaction), ikincisi ise write() metodunu barındıran sınıfın kendisidir (my_scoreboard). Bu ikinci parametre, gelen veride hangi sınıfın write() metodunun çağrılacağını belirtir.
  • build_phase: ap_imp = new("ap_imp", this) ile implementation portu örneklendirilir.
  • Zorunlu write() metodu: uvm_analysis_imp kullanan her sınıf, virtual function void write(my_transaction tr) metodunu mutlaka tanımlamak zorundadır. Monitor tarafında ap.write(tr) çağrıldığında, TLM mekanizması otomatik olarak buradaki write() fonksiyonunu tetikler. İçeride tr.addr ve tr.data beklenen değerlerle karşılaştırılır; eşleşirse uvm_info ile [PASS], eşleşmezse uvm_error ile [FAIL] mesajı basılır.

Lab 3.3: Kapsama (Coverage) Toplayıcıları ve Raporlama

Bu bölümde iki farklı Coverage Collector oluşturacağız ve her ikisine de simülasyon biterken nihai sonuçları ekrana yazdırması için report_phase metodunu ekleyeceğiz.

Yöntem 1: Geleneksel TLM Tabanlı (uvm_subscriber)

uvm_subscriber tek bir porta sahip olduğu için büyük projelerde yetersiz kalabilir.

// APPROACH 1: TLM-based using uvm_subscriber  
class my_tlm_coverage extends uvm_subscriber #(my_transaction);  
  `uvm_component_utils(my_tlm_coverage)  
    
  my_transaction cov_tr;  
    
  covergroup cg;  
    option.per_instance = 1;  
    cp_write_en: coverpoint cov_tr.write_en {  
      bins read_op  = {0};  
      bins write_op = {1};  
    }  
  endgroup  
    
  function new(string name = "my_tlm_coverage", uvm_component parent = null);  
    super.new(name, parent);  
    cg = new();  
  endfunction  
    
  // Mandatory write method for TLM  
  virtual function void write(my_transaction t);  
    cov_tr = t;  
    cg.sample();  
    `uvm_info("TLM_COV", $sformatf("Sampled via TLM. write_en=%0b", cov_tr.write_en), UVM_LOW)  
  endfunction  
    
  // Report phase to print the final coverage at the end of the simulation  
  virtual function void report_phase(uvm_phase phase);  
    super.report_phase(phase);  
    `uvm_info("TLM_COV_REPORT", $sformatf("Final Coverage (TLM-Based): %0.2f%%", cg.get_inst_coverage()), UVM_NONE)  
  endfunction  
endclass

Kodun Açıklaması

my_tlm_coverage sınıfı, kapsama verisini TLM portu üzerinden toplayan geleneksel yöntemi gösterir:

  • extends uvm_subscriber #(my_transaction): uvm_subscriber, içinde hazır bir analysis_export barındıran özel bir bileşendir. Bu sayede ayrıca bir port tanımlamaya gerek kalmaz; ancak yalnızca TEK bir giriş portu olduğu için büyük projelerde sınırlayıcıdır.
  • covergroup cg ve coverpoint: cp_write_en adlı coverpoint, cov_tr.write_en sinyalini izler. İçindeki bins read_op = {0} ve bins write_op = {1} tanımları, sinyalin 0 ve 1 değerlerini ayrı kovalarda (bin) sayar. option.per_instance = 1 ise her örnek (instance) için ayrı kapsama hesaplanmasını sağlar.
  • new() constructor: cg = new() ile covergroup örneklendirilir. Covergroup'lar otomatik oluşturulmaz, açıkça new ile başlatılmalıdır.
  • Zorunlu write() metodu: uvm_subscriber'dan miras alındığı için write() metodu mutlaka tanımlanır. Gelen t transaction'ı cov_tr'ye atanır ve cg.sample() ile o anki değer örneklenir.
  • report_phase: Simülasyon sonunda cg.get_inst_coverage() ile o örneğe ait yüzdesel kapsama değeri alınır ve UVM_NONE seviyesiyle (her zaman görünür) ekrana yazdırılır.

Yöntem 2: Modern Handle Tabanlı (Doğrudan Fonksiyon Çağrısı)

Sektörde tavsiye edilen, port kısıtlaması olmayan yöntem. Covergroup argümanları doğrudan fonksiyonla toplanır.

// APPROACH 2: Handle-based using standard uvm_component  
class my_handle_coverage extends uvm_component;  
  `uvm_component_utils(my_handle_coverage)  
    
  // Covergroup with arguments passed to its sample function  
  covergroup cg with function sample(bit w_en);  
    option.per_instance = 1;  
    cp_write_en: coverpoint w_en {  
      bins read_op  = {0};  
      bins write_op = {1};  
    }  
  endgroup  
    
  function new(string name = "my_handle_coverage", uvm_component parent = null);  
    super.new(name, parent);  
    cg = new();  
  endfunction  
    
  // Custom function to be called directly from anywhere  
  function void sample_cov(bit w_en);  
    cg.sample(w_en);  
    `uvm_info("HANDLE_COV", $sformatf("Sampled via Handle. write_en=%0b", w_en), UVM_LOW)  
  endfunction  
    
  // Report phase to print the final coverage at the end of the simulation  
  virtual function void report_phase(uvm_phase phase);  
    super.report_phase(phase);  
    `uvm_info("HANDLE_COV_REPORT", $sformatf("Final Coverage (Handle-Based): %0.2f%%", cg.get_inst_coverage()), UVM_NONE)  
  endfunction  
endclass

Kodun Açıklaması

my_handle_coverage sınıfı, sektörde tavsiye edilen handle tabanlı yöntemi gösterir ve sıradan bir uvm_component'ten türer (TLM portuna gerek yoktur):

  • covergroup cg with function sample(bit w_en): Bu, kapsama mantığının kalbidir. Geleneksel yöntemden farklı olarak covergroup, parametre alabilen özel bir sample(bit w_en) fonksiyonuyla tanımlanır. Böylece bir sınıf üyesi değişkene (cov_tr gibi) bağlı kalmadan, dışarıdan gelen değer doğrudan örneklenebilir.
  • coverpoint w_en: cp_write_en coverpoint'i, sınıf değişkeni yerine doğrudan fonksiyon argümanı olan w_en'i izler; bins read_op = {0} ve bins write_op = {1} ile değerleri ayrı kovalarda sayar.
  • sample_cov(bit w_en): Bu, herhangi bir yerden (örneğin Monitor'den) doğrudan çağrılabilen özel bir fonksiyondur. İçeride cg.sample(w_en) ile gelen değeri covergroup'a aktarır. Port/bağlantı gerektirmediği için tek bir kaynaktan birden çok yere kolayca dağıtım yapılabilir.
  • report_phase: Geleneksel yöntemde olduğu gibi cg.get_inst_coverage() ile nihai kapsama yüzdesi alınıp ekrana yazdırılır.

Lab 3.4: Çevre (Environment) İçinde Bağlantıların Yapılması

Scoreboard ve TLM tabanlı Coverage için geleneksel port bağlantılarını (connect) kullanacağız. Handle tabanlı modern Coverage sınıfı için ise referans ataması (=) yapacağız.

Görev: my_env sınıfını güncelleyerek build_phase ve connect_phase mekanizmalarını kurun.

class my_env extends uvm_env;  
  `uvm_component_utils(my_env)  
    
  // Component handles  
  my_agent           agent_inst;  
  my_scoreboard      sb_inst;  
  my_tlm_coverage    tlm_cov_inst;  
  my_handle_coverage handle_cov_inst;  
    
  // Constructor  
  function new(string name = "my_env", uvm_component parent = null);  
    super.new(name, parent);  
  endfunction  
    
  // Build phase  
  virtual function void build_phase(uvm_phase phase);  
    super.build_phase(phase);  
    agent_inst      = my_agent::type_id::create("agent_inst", this);  
    sb_inst         = my_scoreboard::type_id::create("sb_inst", this);  
    tlm_cov_inst    = my_tlm_coverage::type_id::create("tlm_cov_inst", this);  
    handle_cov_inst = my_handle_coverage::type_id::create("handle_cov_inst", this);  
  endfunction  
    
  // Connect phase  
  virtual function void connect_phase(uvm_phase phase);  
    super.connect_phase(phase);  
      
    // 1. TLM Connections: ap is a broadcast port, it can connect to multiple destinations  
    agent_inst.mon.ap.connect(sb_inst.ap_imp);  
    agent_inst.mon.ap.connect(tlm_cov_inst.analysis_export);  
      
    // 2. Handle Passing: Pass the coverage collector handle directly to the monitor  
    agent_inst.mon.cov_handle = this.handle_cov_inst;  
      
    `uvm_info("ENV_CONN", "TLM connections and handle passing completed successfully.", UVM_LOW)  
  endfunction  
endclass

Kodun Açıklaması

my_env sınıfı, tüm bileşenleri bir araya getiren ve aralarındaki bağlantıları kuran çevredir:

  • Bileşen referansları: Sınıf başında my_agent, my_scoreboard, my_tlm_coverage ve my_handle_coverage tiplerinde handle'lar tanımlanır.
  • build_phase: Her bileşen type_id::create(...) ile factory üzerinden örneklendirilir. this parametresi, oluşturulan bileşenin Environment'a child olarak bağlanmasını sağlar.
  • connect_phase – TLM bağlantıları: agent_inst.mon.ap.connect(sb_inst.ap_imp) ile Monitor'ün analiz portu Scoreboard'un implementation portuna bağlanır. Hemen ardından aynı ap portu tlm_cov_inst.analysis_export'a da bağlanır. Bu, broadcast (yayın) portunun TEK kaynaktan ÇOK hedefe bağlanabildiğini gösterir; ap.write() çağrıldığında her iki bağlantı da tetiklenir.
  • connect_phase – Handle aktarımı: agent_inst.mon.cov_handle = this.handle_cov_inst satırı, herhangi bir port kullanmadan, modern coverage toplayıcının referansını doğrudan Monitor'e atar (= ile). Artık Monitor içindeki cov_handle boş değildir ve sample_cov çağrıları geçerli nesneye ulaşır.

Önemli Noktalar

  • TLM port vs Handle yaklaşımı: TLM (analysis_port/analysis_imp) yaklaşımı standart, çözük (decoupled) ve test edilebilir bir bağlantı sunar; alıcı ile gönderici birbirini doğrudan tanımaz. Handle tabanlı yaklaşım ise daha doğrudan ve hızlıdır, port/bağlantı maliyeti olmadan basit veri aktarımı için idealdir. Genel kural: bileşenler arası standart haberleşmede TLM, hafif/lokal kapsama örneklemede handle tercih edin.
  • Broadcast portun gücü: Tek bir uvm_analysis_port, connect çağrısı tekrarlanarak birden fazla hedefe (Scoreboard, Coverage, Logger vb.) bağlanabilir. ap.write(tr) tek çağrıyla tüm dinleyicilere aynı veriyi iletir; bu, yeni dinleyici eklemeyi zahmetsiz kılar.
  • write() metodu zorunludur: uvm_analysis_imp veya uvm_subscriber kullanan her sınıf, write() fonksiyonunu tanımlamak zorundadır; aksi halde derleme hatası alınır. Bu metot, gelen transaction geldiğinde TLM tarafından otomatik tetiklenir.
  • uvm_subscriber sınırı: uvm_subscriber yalnızca tek bir analysis_export barındırır. Birden fazla farklı kaynaktan veri toplamak gerektiğinde yetersiz kalır; bu durumda ayrı uvm_analysis_imp portları veya handle tabanlı yaklaşım gerekir.
  • Covergroup mutlaka new ile başlatılır: Covergroup'lar otomatik oluşturulmaz; constructor içinde cg = new() çağrısı unutulursa örnekleme çalışmaz ve kapsama sıfır kalır. option.per_instance = 1 ile her örnek için ayrı kapsama izlenir.
  • null kontrolü güvenliği: Handle tabanlı çağrılarda (cov_handle.sample_cov(...)) öncesinde if (cov_handle != null) kontrolü yapmak, handle henüz atanmadan çalıştırılan senaryolarda simülasyonun çökmesini engeller.

Özet ve Beklenen Sonuç

base_test içerisinde #100 zaman birimi boyunca beklenildiğinde, Monitor (her 20 birimde bir çalıştığı için) 5 adet işlem (transaction) üretecektir.

Simülasyon çalıştırıldığında testin run_phase'i süresince ekranda Scoreboard eşleşmeleri [PASS] ve her iki Coverage örneklendirme [TLM_COV], [HANDLE_COV] logları art arda düşecektir.

Simülasyon kapatılırken (UVM report_phase'e geçtiğinde), terminalde aşağıdaki nihai rapor çıktılarını görmelisiniz:

UVM_INFO @ 100: uvm_test_top.env_inst.tlm_cov_inst [TLM_COV_REPORT] Final Coverage (TLM-Based): 50.00%
UVM_INFO @ 100: uvm_test_top.env_inst.handle_cov_inst [HANDLE_COV_REPORT] Final Coverage (Handle-Based): 50.00%

(Not: Dummy verimiz sürekli write_en = 1 ürettiği için coverpoint içindeki "write" durumu %100, "read" durumu %0 kapsanmıştır. İkisinin ortalaması %50'dir.)

Sıkça Sorulan Sorular (FAQ)

Soru 1: analysis_port ile analysis_imp arasındaki fark nedir?

Cevap: uvm_analysis_port veriyi GÖNDEREN (üreten) taraftadır; Monitor gibi bileşenlerde ap.write(tr) ile veri yayınlamak için kullanılır. uvm_analysis_imp ise veriyi ALAN (tüketen) taraftadır ve içinde write() metodunun gerçek uygulamasını (implementation) barındırır. Kısaca port "çağıran", imp ise "uygulayan" taraftır. Bağlantı her zaman porttan imp'e doğru connect ile yapılır.

Soru 2: write() metodunu neden mutlaka tanımlamak zorundayım?

Cevap: TLM analysis mekanizması, gönderici tarafta ap.write(tr) çağrıldığında, bağlı tüm alıcıların write() metodunu otomatik olarak tetikler. Bu metot, uvm_analysis_imp veya uvm_subscriber arayüzünün bir sözleşmesidir (interface contract). Eğer tanımlanmazsa sınıf soyut kalır ve derleme hatası verir; çünkü TLM altyapısının çağıracağı somut bir fonksiyon bulamaz.

Soru 3: uvm_subscriber her zaman yeterli değil mi, neden başka yöntemlere ihtiyaç var?

Cevap: uvm_subscriber içinde hazır TEK bir analysis_export barındırır, bu da onu basit kapsama toplama için pratik kılar. Ancak bir bileşenin birden fazla farklı kaynaktan (örneğin hem input hem output Monitor'den) veri toplaması gerektiğinde tek port yetersiz kalır. Bu gibi durumlarda ya birden çok uvm_analysis_imp (makrolarla isimlendirilmiş) tanımlanır ya da handle tabanlı yaklaşıma geçilir.

Soru 4: Handle tabanlı kapsama toplamanın avantajı nedir?

Cevap: Handle tabanlı yöntem port, export ve connect zincirine ihtiyaç duymaz; sadece bir referans ataması (cov_handle = handle_cov_inst) ve doğrudan fonksiyon çağrısı (sample_cov(...)) yeterlidir. Bu, daha az kod ve daha hızlı çağrı anlamına gelir. Ayrıca covergroup ... with function sample(...) kullanıldığı için, birden fazla parametre temiz biçimde tek çağrıda örneklenebilir. Hafif ve lokal örneklemelerde ideal bir tekniktir.

Soru 5: Bir analysis_port neden birden fazla yere bağlanabiliyor?

Cevap: uvm_analysis_port tasarım gereği bir "broadcast" (yayın) portudur; tıpkı bir radyo vericisi gibi tek bir veriyi dinleyen herkese aynı anda iletir. Bu yüzden aynı ap portu üzerinde connect'i tekrar tekrar çağırarak Scoreboard, Coverage ve istediğiniz kadar başka dinleyiciyi bağlayabilirsiniz. ap.write(tr) çağrıldığında UVM, bağlı tüm imp/export'ların write() metodunu sırayla tetikler; bu sayede yeni bir dinleyici eklemek mevcut kodu değiştirmeden mümkün olur.