EDA Playground'da Dene

UVM Transaction Modeli ve Agent Bileşenleri

UVM Gün 2: Veri Modeli ve Agent Bileşenleri | uvm_sequence_item, field makroları, sequencer–driver el sıkışması, pasif monitor, ACTIVE/PASSIVE agent

Amaç

Bu laboratuvarın amacı, UVM testbench'inde uyarıcıları (stimulus) modellemek için uvm_sequence_item oluşturmak ve bu verileri işleyecek olan temel Agent bileşenlerini (uvm_sequencer, uvm_driver, uvm_monitor) tasarlayıp birbirine bağlamaktır.

Ön Koşullar

  • 1.Gün laboratuvarında oluşturulan temel hiyerarşi kavramlarına (build_phase, connect_phase) hakimiyet.
  • SystemVerilog rand ve OOP sınıfları hakkında temel bilgi.

Lab 2.1: Transaction (Veri Modeli) Oluşturma

UVM'de DUT'a (Design Under Test) gönderilecek veya DUT'tan okunacak her veri paketi uvm_sequence_item sınıfından türetilir.

Görev: my_transaction adında bir sınıf oluşturun. İçine rastgele (random) üretilebilecek adres, veri ve yazma/okuma kontrol sinyallerini ekleyin. Ayrıca UVM alan (field) makrolarını kullanarak bu değişkenleri UVM sistemine tanıtın.

class my_transaction extends uvm_sequence_item;  
    
  // Random payload variables  
  rand bit [31:0] data;  
  rand bit [7:0]  addr;  
  rand bit        write_en; // 1: Write, 0: Read  
    
  // Register fields to UVM factory and enable generic functions (print, copy, compare)  
  `uvm_object_utils_begin(my_transaction)  
    `uvm_field_int(data,     UVM_ALL_ON)  
    `uvm_field_int(addr,     UVM_ALL_ON)  
    `uvm_field_int(write_en, UVM_ALL_ON)  
  `uvm_object_utils_end  
    
  // Constructor  
  function new(string name = "my_transaction");  
    super.new(name);  
  endfunction  
    
endclass

Kodun Açıklaması

Bu kod bloğu, testbench'in taşıdığı veri paketini (transaction) tanımlar:

  • my_transaction extends uvm_sequence_item: Sınıfımızı doğrudan uvm_object yerine uvm_sequence_item'dan türetiyoruz. Bu sayede paket, ileride bir Sequence tarafından üretilip Sequencer üzerinden Driver'a akabilecek "taşınabilir veri" niteliği kazanır.
  • rand bit [31:0] data, rand bit [7:0] addr, rand bit write_en: Paketin asıl yükünü (payload) oluşturan alanlardır. Başlarındaki rand anahtar kelimesi, randomize() çağrıldığında bu değerlerin SystemVerilog tarafından rastgele üretilebileceğini belirtir; böylece farklı senaryolar otomatik olarak denenebilir.
  • uvm_object_utils_begin(my_transaction) ... uvm_object_utils_end: Sınıfı UVM factory'ye kaydeden ve alan (field) makrolarını sarmalayan blok çiftidir. Sadece tek bir uvm_object_utils yerine bu blok kullanıldığında içine alan makroları eklenebilir.
  • uvm_field_int(data, UVM_ALL_ON) (ve addr, write_en için aynısı): Her bir değişkeni UVM'in otomasyon altyapısına tanıtır. UVM_ALL_ON bayrağı sayesinde bu alanlar için print(), copy(), compare(), pack() gibi genel metotlar UVM tarafından otomatik üretilir; bu fonksiyonları elle yazmaya gerek kalmaz.
  • function new(string name = "my_transaction"): Yapıcı (constructor) metottur. super.new(name) çağrısıyla üst sınıf olan uvm_sequence_item'ın kurulumunu tamamlar. Bir uvm_object türevi olduğu için yapıcı sadece name parametresi alır (component'lerden farklı olarak parent almaz).

uvm_object ve uvm_component: UVM'in İki Soy Ağacı

UVM'deki her sınıf bu iki kökten birine bağlanır ve hangisini seçtiğiniz her şeyi belirler:

uvm_object soyu uvm_component soyu
Örnekler uvm_sequence_item, uvm_sequence, konfigürasyon nesneleri uvm_driver, uvm_monitor, uvm_sequencer, uvm_agent, uvm_env, uvm_test
Ömür Dinamik: yaratılır, akar, çöp toplanır Statik: build_phase'de yaratılır, simülasyon sonuna kadar yaşar
Hiyerarşi Yok (parent almaz) Var (parent alır, get_full_name() yolu vardır)
Fazlar Yok build_phase, run_phase...
Factory makrosu `uvm_object_utils `uvm_component_utils
create çağrısı type_id::create("ad") type_id::create("ad", this)
SystemVerilog projesindeki karşılığı ALU_Transaction ALU_Driver, ALU_Monitor, ALU_Environment...

Kural basittir: akan şey (veri, senaryo) uvm_object; duran şey (altyapı) uvm_component.

Field Makroları: Konfor mu, Maliyet mi?

`uvm_field_int gibi makrolar print, copy, compare, pack metodlarını otomatik üretir; ama bunu çalışma zamanında alan listesi üzerinde döngü kurarak yapar ve büyük regresyonlarda ölçülebilir performans maliyeti vardır. Endüstride iki yaklaşım yaygındır:

Yaklaşım Kod Artısı Eksisi
Field makroları (bu ders) `uvm_field_int(data, UVM_ALL_ON) Hızlı yazım, az kod Yavaş çalışma, hata mesajları makro içinden gelir
do_* metotlarını elle yazmak do_copy, do_compare, do_print, convert2string override Hızlı çalışma, tam kontrol Her alan için elle kod

Öğrenirken makrolar idealdir; büyük projelerde ekip standardı genellikle do_* metotlarıdır. SystemVerilog projesindeki copy() ve display() metotlarınız tam olarak do_copy() ve convert2string()'in elle yazılmış hâliydi.

do_* Metotları ile Elle Yazılmış Transaction

Aynı my_transaction, field makroları olmadan şöyle yazılır. copy(), compare(), print() gibi genel metotlar uvm_object'te zaten vardır; her biri sizin yazdığınız bir do_* kancasını çağırır:

class my_transaction extends uvm_sequence_item;
  rand bit [31:0] data;
  rand bit [7:0]  addr;
  rand bit        write_en;

  `uvm_object_utils(my_transaction)     // field makrosu yok: yalnizca factory kaydi

  function new(string name = "my_transaction");
    super.new(name);
  endfunction

  // copy(rhs) ve clone() bunu cagirir
  virtual function void do_copy(uvm_object rhs);
    my_transaction t;
    if (!$cast(t, rhs)) `uvm_fatal("DO_COPY", "tip uyusmuyor")
    super.do_copy(rhs);
    data = t.data;  addr = t.addr;  write_en = t.write_en;
  endfunction

  // compare(rhs) bunu cagirir; comparer ilk farki raporlar
  virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    my_transaction t;
    if (!$cast(t, rhs)) return 0;
    return super.do_compare(rhs, comparer)
        && comparer.compare_field_int("data",     data,     t.data,     32)
        && comparer.compare_field_int("addr",     addr,     t.addr,      8)
        && comparer.compare_field_int("write_en", write_en, t.write_en,  1);
  endfunction

  // print() / sprint() bunu cagirir (UVM 1.1d'de print_field_int yerine print_int)
  virtual function void do_print(uvm_printer printer);
    super.do_print(printer);
    printer.print_field_int("data",     data,     32, UVM_HEX);
    printer.print_field_int("addr",     addr,      8, UVM_HEX);
    printer.print_field_int("write_en", write_en,  1, UVM_BIN);
  endfunction

  // `uvm_info icin tek satir; print()'in tablosundan cok daha okunakli
  virtual function string convert2string();
    return $sformatf("addr=0x%0h data=0x%0h we=%0b", addr, data, write_en);
  endfunction
endclass
Çağırdığınız metot Arkada çalışan kanca Ne zaman gerekir?
copy(rhs) do_copy Scoreboard'a kopya vermek, clone()
clone() create (factory) + do_copy Yeni nesne + kopya tek adımda (Gün 7)
compare(rhs) do_compare Scoreboard eşitlik testi
print() / sprint() do_print Hata ayıklama tablosu
pack / unpack do_pack / do_unpack Bit akışına çevirme (seri protokoller)
record() do_record Dalga formuna transaction kaydı

Hangisini seçmeli? Küçük transaction, hızlı prototip → makrolar. Büyük regresyon, yüzlerce alan, ölçülebilir performans → do_*. Karışık kullanmayın: makrolar bir alanı, do_* başka bir alanı kopyalıyorsa hata ayıklamak çok zordur. convert2string() her iki yaklaşımda da elle yazılır ve her transaction'da bulunmalıdır.

Lab 2.2: Sequencer ve Driver Tasarımı

Sequencer, üretilen transaction'ları sıraya koyan bir hakemdir. Driver ise bu transaction'ları Sequencer'dan çekip donanıma (fiziksel pinlere) süren işçidir.

Görev: my_sequencer ve my_driver sınıflarını my_transaction tipinde çalışacak şekilde oluşturun. Driver'ın run_phase'i içerisinde Sequencer'dan veri çekme (get_next_item) ve işi bitirme (item_done) adımlarını kodlayın.

// ----------------------------------------------------------------------------  
// SEQUENCER  
// ----------------------------------------------------------------------------  
// Typedef is often enough for a simple sequencer, but we define a class for clarity  
class my_sequencer extends uvm_sequencer #(my_transaction);  
  `uvm_component_utils(my_sequencer)  
    
  // Constructor  
  function new(string name = "my_sequencer", uvm_component parent = null);  
    super.new(name, parent);  
  endfunction  
endclass

// ----------------------------------------------------------------------------  
// DRIVER  
// ----------------------------------------------------------------------------  
class my_driver extends uvm_driver #(my_transaction);  
  `uvm_component_utils(my_driver)  
    
  // Constructor  
  function new(string name = "my_driver", uvm_component parent = null);  
    super.new(name, parent);  
  endfunction  
    
  // Run phase: Continuously fetch items from sequencer and "drive" them  
  virtual task run_phase(uvm_phase phase);  
    super.run_phase(phase);  
      
    forever begin  
      // 1. Ask sequencer for the next item  
      seq_item_port.get_next_item(req);  
        
      `uvm_info("DRIVER", $sformatf("Driving Transaction: Addr=0x%0h, Data=0x%0h, WE=%0b",   
                                     req.addr, req.data, req.write_en), UVM_LOW)  
        
      // Simulate hardware driving delay (e.g., waiting for clock edges)  
      #20;   
        
      // 2. Tell sequencer we are done with this item  
      seq_item_port.item_done();  
    end  
  endtask  
endclass

Kodun Açıklaması

Bu blok, Agent'ın aktif (active) tarafını oluşturan iki bileşeni tanımlar:

  • my_sequencer extends uvm_sequencer #(my_transaction): Sequencer'ı my_transaction tipi üzerine parametrelendiririz. Bu sayede Sequencer'ın yöneteceği veri paketinin tipi netleşir. Basit durumlarda bir typedef yeterli olsa da, okunabilirlik için ayrı bir sınıf tanımlanmıştır.
  • uvm_component_utils(my_sequencer) ve uvm_component_utils(my_driver): Bu sınıfları UVM factory'ye birer component olarak kaydeder. Transaction'da kullanılan uvm_object_utils'ten farklıdır; çünkü Sequencer ve Driver kalıcı hiyerarşi elemanlarıdır (component), tek seferlik veri nesneleri değildir.
  • Component yapıcılarındaki function new(string name, uvm_component parent = null): Component'ler bir hiyerarşiye ait olduğundan, uvm_object'ten farklı olarak parent parametresi de alır ve super.new(name, parent) ile üst sınıfa iletilir.
  • my_driver extends uvm_driver #(my_transaction): Driver da my_transaction tipi ile parametrelendirilir. uvm_driver'dan türetilince hazır olarak seq_item_port ve req gibi üyeler miras alınır.
  • virtual task run_phase(uvm_phase phase): Driver'ın asıl iş yaptığı zamana yayılı (time-consuming) fazdır. super.run_phase(phase) ile üst sınıfın davranışı korunur.
  • seq_item_port.get_next_item(req): Driver'ın Sequencer'dan bir sonraki paketi istediği çağrıdır. Hazır bir paket yoksa bu çağrı bloklar (bekler); paket geldiğinde miras alınan req değişkenine yazılır.
  • uvm_info("DRIVER", $sformatf(...), UVM_LOW): Çekilen paketin req.addr, req.data ve req.write_en alanlarını biçimli bir mesaj olarak loglar. UVM_LOW verbosity seviyesi, mesajın varsayılan ayarlarda bile görüneceği anlamına gelir.
  • #20: Donanımda paketi sürerken geçen süreyi (örneğin saat kenarlarını beklemeyi) simüle eden basit bir gecikmedir.
  • seq_item_port.item_done(): Driver'ın paketle işinin bittiğini Sequencer'a bildirdiği çağrıdır. Bu çağrı yapılmadan get_next_item yeni bir paket vermez; forever döngüsü bu get/done çiftini sürekli tekrarlar.

Mailbox'tan seq_item_port'a

SystemVerilog projesinde driver gen2drv.get(txn) ile mailbox'tan okuyordu. UVM'de bu kanal sequencer ve seq_item_port ile değiştirilir; fark yalnızca isim değildir:

mailbox (SV projesi) seq_item_port + sequencer (UVM)
Veri alma get(txn) — bloklar get_next_item(req) — bloklar
Bitiş bildirimi Yok item_done() — zorunlu; üretici (sequence) bu ana kadar bekler
Akış kontrolü Mailbox kapasitesi Driver "bitti" demeden yeni paket gelmez (tam el sıkışma)
Çoklu üretici Ayrı mailbox'lar Tek sequencer, birden çok sequence; arbitrasyon yerleşik
Yanıt dönüşü Ayrı mailbox gerekir rsp ile put_response() / item_done(rsp) yerleşik

item_done() çağrısı, SystemVerilog projesinde hiç olmayan bir şeyi getirir: üretici (sequence), paketinin gerçekten sürüldüğünü bilir ve bir sonrakini ona göre üretebilir. Bu, Gün 4'teki "geç rastgeleleştirme" tekniğinin temelidir.

Yanıt Yolu: rsp, item_done(rsp) ve get_response()

Tablodaki son satır, bu derste kullanmadığımız bir yeteneği gösteriyor: driver, sürdüğü paketin cevabını (okuma verisi, hata kodu, gecikme) sequence'e geri gönderebilir. İki yol vardır:

// YOL 1 (en yaygin): ayni nesneyi doldur. req bir handle'dir; sequence ve driver
// ayni nesneyi gorur, finish_item dondugunde alanlar dolmustur.
virtual task run_phase(uvm_phase phase);
  forever begin
    seq_item_port.get_next_item(req);
    // ... pinleri sur, okuma ise sonucu bekle ...
    req.data = read_data_from_dut;      // yanit, istek nesnesine yazilir
    seq_item_port.item_done();
  end
endtask
// sequence: finish_item(req); `uvm_info("SEQ", $sformatf("okunan=0x%0h", req.data), UVM_LOW)

// YOL 2 (acik yanit nesnesi): ayri rsp uret, kimligini req'ten kopyala
virtual task run_phase(uvm_phase phase);
  forever begin
    seq_item_port.get_next_item(req);
    rsp = my_transaction::type_id::create("rsp");
    rsp.set_id_info(req);               // ZORUNLU: yanit hangi istege ait?
    rsp.data = read_data_from_dut;
    seq_item_port.item_done(rsp);       // veya: seq_item_port.put_response(rsp)
  end
endtask
// sequence: finish_item(req); get_response(rsp);   // rsp gelene kadar bekler
  • Yol 1 çoğu protokol için yeterlidir ve ek nesne yaratmaz. Tek koşul, sequence'in finish_item'dan sonra req'i okumasıdır.
  • Yol 2 pipelined protokollerde (istek ile yanıt arasında başka istekler varken) gerekir. set_id_info(req) olmadan sequencer yanıtı doğru sequence'e yönlendiremez. Sequence get_response() çağırmazsa yanıtlar sequencer'ın yanıt kuyruğunda birikir; varsayılan 8 öğeden sonra UVM_ERROR ... Response queue overflow alırsınız. Kural: item_done(rsp) yazan driver'ın karşısında get_response() yazan sequence olmalıdır.

Lab 2.3: Monitor Tasarımı

Monitor'ün görevi veri sürmek değil, fiziksel arayüzdeki (interface) sinyalleri izleyip, bunlardan tekrar bir Transaction (veri paketi) oluşturmaktır (Bu laboratuvarda arayüz olmadığı için sanal bir izleme simüle edeceğiz). Monitor asla sinyalleri değiştirmez, sadece okur (Passive).

Görev: my_monitor sınıfını oluşturun.

class my_monitor extends uvm_monitor;  
  `uvm_component_utils(my_monitor)  
    
  // Constructor  
  function new(string name = "my_monitor", uvm_component parent = null);  
    super.new(name, parent);  
  endfunction  
    
  // Run phase: Continuously monitor interface signals  
  virtual task run_phase(uvm_phase phase);  
    super.run_phase(phase);  
      
    // In a real testbench, this would wait for clock edges and sample virtual interface  
    forever begin  
      #20; // Simulated sampling delay  
      `uvm_info("MONITOR", "Sampled data from virtual interface...", UVM_HIGH)  
    end  
  endtask  
endclass

Kodun Açıklaması

Bu blok, arayüzü dinleyip gözlemleyen pasif bileşeni tanımlar:

  • my_monitor extends uvm_monitor: Monitor'ü UVM'in uvm_monitor temel sınıfından türetiriz. Driver'dan farklı olarak Monitor sinyal sürmez, yalnızca okur; bu yüzden bir Sequencer'a veya seq_item_port'a ihtiyaç duymaz.
  • uvm_component_utils(my_monitor): Monitor'ü factory'ye bir component olarak kaydeder.
  • function new(string name, uvm_component parent = null): Diğer component'lerde olduğu gibi name ve parent alır, super.new(name, parent) ile üst sınıfı kurar.
  • virtual task run_phase(uvm_phase phase): Monitor'ün izleme yaptığı zamana yayılı fazdır. Gerçek bir testbench'te burada saat kenarları beklenir ve sanal arayüz (virtual interface) örneklenir.
  • forever begin ... #20 ... end: Sürekli çalışan örnekleme döngüsüdür; #20 gecikmesi gerçek bir örnekleme periyodunu temsil eder.
  • uvm_info("MONITOR", "...", UVM_HIGH): Örnekleme yapıldığını loglar. Burada UVM_HIGH verbosity seviyesi kullanıldığı için bu mesaj yalnızca log seviyesi yükseltildiğinde görünür; bu da Monitor loglarının normalde ekranı kalabalıklaştırmasını engeller.

Lab 2.4: Agent'ı Güncelleme (Bağlantıların Yapılması)

Şimdi 1. gün yazdığımız içi boş Agent'ı, yeni oluşturduğumuz bu 3 bileşenle (Sequencer, Driver, Monitor) dolduracağız. Ayrıca TLM (Transaction Level Modeling) portlarını connect_phase içinde bağlayacağız.

Görev: 1. Gün'den kalan my_agent sınıfınızı aşağıdaki gibi güncelleyerek build_phase ve connect_phase mekanizmalarını ekleyin.

class my_agent extends uvm_agent;  
  `uvm_component_utils(my_agent)  
    
  // Declare handles for agent components  
  my_sequencer sqr;  
  my_driver    drv;  
  my_monitor   mon;  
    
  // Constructor  
  function new(string name = "my_agent", uvm_component parent = null);  
    super.new(name, parent);  
  endfunction  
    
  // Build phase: Create components based on is_active status  
  virtual function void build_phase(uvm_phase phase);  
    super.build_phase(phase);  
      
    // Monitor is ALWAYS built  
    mon = my_monitor::type_id::create("mon", this);  
      
    // Sequencer and Driver are only built if the agent is ACTIVE  
    if(get_is_active() == UVM_ACTIVE) begin  
      sqr = my_sequencer::type_id::create("sqr", this);  
      drv = my_driver::type_id::create("drv", this);  
    end  
  endfunction  
    
  // Connect phase: Connect Driver and Sequencer TLM ports  
  virtual function void connect_phase(uvm_phase phase);  
    super.connect_phase(phase);  
      
    // Check if components exist before connecting  
    if(get_is_active() == UVM_ACTIVE) begin  
      // Connect driver's sequence item port to sequencer's sequence item export  
      drv.seq_item_port.connect(sqr.seq_item_export);  
      `uvm_info("AGENT_CONN", "Connected Driver and Sequencer.", UVM_LOW)  
    end  
  endfunction  
endclass

Kodun Açıklaması

Bu blok, daha önce yazdığımız üç bileşeni bir araya getiren Agent'ı kurar ve TLM bağlantılarını yapar:

  • my_agent extends uvm_agent: Agent'ı UVM'in uvm_agent temel sınıfından türetiriz. uvm_agent, içinde otomatik olarak bir aktiflik durumu (is_active) taşır.
  • my_sequencer sqr; my_driver drv; my_monitor mon;: Alt bileşenlere erişmek için kullanılacak el (handle) tanımlarıdır. Bunlar henüz nesne değildir; nesneler build_phase'de yaratılır.
  • virtual function void build_phase(uvm_phase phase): Bileşenlerin yaratıldığı inşa fazıdır. super.build_phase(phase) ile üst sınıfın kurulumu çalıştırılır.
  • mon = my_monitor::type_id::create("mon", this): Monitor'ü factory üzerinden yaratır. Monitor her zaman (hem ACTIVE hem PASSIVE modda) yaratılır; çünkü gözlem her durumda gereklidir. type_id::create kullanımı, factory override mekanizmasının çalışabilmesi için doğrudan new çağırmaya tercih edilir.
  • if(get_is_active() == UVM_ACTIVE): Agent'ın aktiflik durumunu sorgular. get_is_active() Agent'ın güncel modunu döndürür; UVM_ACTIVE ise Agent'ın uyarıcı üretmesi beklenir.
  • Bu koşul içinde sqr ve drv yaratılır: Sequencer ve Driver yalnızca UVM_ACTIVE modda oluşturulur. Pasif (UVM_PASSIVE) bir Agent yalnızca dinlediği için bu iki bileşene ihtiyaç duymaz.
  • virtual function void connect_phase(uvm_phase phase): Bileşenler arası TLM port bağlantılarının yapıldığı fazdır.
  • drv.seq_item_port.connect(sqr.seq_item_export): Driver'ın seq_item_port'unu Sequencer'ın seq_item_export'una bağlar. Bu bağlantı sayesinde Driver'daki get_next_item/item_done çağrıları Sequencer'a ulaşır. Bağlantı yalnızca bileşenlerin var olduğu (ACTIVE) durumda yapıldığı için yine get_is_active() koşulu altındadır.
  • uvm_info("AGENT_CONN", "...", UVM_LOW): Bağlantının başarıyla kurulduğunu loglayan bilgilendirme mesajıdır.

Önemli Noktalar

  • Doğru temel sınıfı seçin: Veri paketleri uvm_sequence_item'dan, kalıcı yapı taşları (Sequencer, Driver, Monitor, Agent) ise ilgili uvm_component türevlerinden türetilmelidir. Bu ayrım, uvm_object_utils ile uvm_component_utils makro seçimini de belirler.
  • Field makrolarını kullanın: uvm_field_int gibi alan makroları UVM_ALL_ON ile birlikte print, copy, compare gibi metotları otomatik üretir; bunları elle yazmak hem zaman alır hem de hata kaynağıdır.
  • Monitor daima pasiftir ve her zaman yaratılır: Monitor sinyalleri yalnızca okur, asla sürmez. Bu yüzden Agent ister ACTIVE ister PASSIVE olsun Monitor build_phase'de koşulsuz oluşturulur.
  • ACTIVE/PASSIVE ayrımına uyun: Sequencer ve Driver yalnızca get_is_active() == UVM_ACTIVE olduğunda yaratılmalı ve bağlanmalıdır; aksi halde pasif Agent gereksiz uyarıcı üretmeye çalışır.
  • type_id::create tercih edin: Bileşenleri ve nesneleri doğrudan new ile değil, factory üzerinden type_id::create ile oluşturun; bu, testlerde tip override yapabilmenin önkoşuludur.
  • TLM bağlantısını connect_phase'de yapın: seq_item_port.connect(seq_item_export) bağlantısı her zaman connect_phase içinde ve bileşenlerin yaratıldığından emin olduktan sonra kurulmalıdır.

Çalıştırma Öncesi Not

Bu laboratuvar, sadece Agent'ın iç mimarisini kurar. Driver'ın içindeki get_next_item komutu, Sequencer'a bir "Sequence" (Senaryo) gönderilmediği sürece sonsuza kadar bekler (Block olur). Simülasyonu çalıştırdığınızda herhangi bir transaction ekrana basılmayacaktır. Veri üretimini tetikleyecek senaryoların (Sequences) yazılması 4. Gün'ün konusudur.

Yine de çalıştırmaya değer: print_topology() çıktısında agent'ın altında sqr, drv, mon bileşenlerini görmelisiniz. Agent'ı UVM_PASSIVE yaparsanız (uvm_config_db#(uvm_active_passive_enum)::set(this, "env_inst.agent_inst", "is_active", UVM_PASSIVE); satırını testin build_phase'ine ekleyin) yalnızca mon kalır.

Agent Mimarisi

                         my_agent
          ┌──────────────────────────────────────┐
          │  my_sequencer  ──seq_item_export──┐   │
          │       ▲                           │   │
          │       │ (Gun 4: sequence.start)   ▼   │
          │                            seq_item_port
          │                               my_driver ──▶ DUT pinleri (Gun 3+: vif)
          │                                             │
          │  my_monitor ◀────────────────── DUT pinleri ┘
          │     └── ap (Gun 3: analysis_port) ──▶ scoreboard, coverage
          └──────────────────────────────────────┘
             is_active: UVM_ACTIVE → sqr + drv + mon
                        UVM_PASSIVE → yalnizca mon

Agent, "bir arayüzü süren ve izleyen her şey" için standart paketleme birimidir. Bir SoC'de her protokol (AXI, APB, UART...) için bir agent bulunur; aynı agent sınıfı bir ortamda ACTIVE (sürer), başka bir ortamda PASSIVE (yalnızca izler) olarak yeniden kullanılır.

Sık Yapılan Hatalar

  • **Transaction'ı `uvm_component_utils ile kaydetmek:** Derleme hatası; transaction bir uvm_object'tir.
  • item_done() çağırmamak: Sequencer ilk paketi teslim eder, ikincisini asla vermez; driver get_next_item'da sonsuza kadar bekler ve simülasyon "takılmış" görünür.
  • get_next_item ile try_next_item'ı karıştırmak: İlki bloklar, ikincisi paket yoksa null döndürür. try_next_item kullanıyorsanız null kontrolü şarttır ve yine item_done() gerekir.
  • Monitor'e seq_item_port eklemek: Monitor sequencer'dan beslenmez; pasif kalmalı ve yalnızca pinleri (vif) okumalıdır.
  • drv.seq_item_port.connect(...)'i build_phase'de yapmak: Bağlantılar connect_phase'de yapılır; build sırasında karşı taraf henüz yaratılmamış olabilir.
  • get_is_active() kontrolünü unutmak: PASSIVE agent'ta drv null'dır; connect_phase'de koşulsuz drv.seq_item_port.connect(...) çağrısı null-handle hatası verir.

Kendinizi Deneyin

  1. my_transaction'a rand bit [3:0] burst_len; alanı ekleyin ve `uvm_field_int ile kaydedin. Gün 4'te driver'ın req.print() çağrısıyla yeni alanın otomatik yazdırıldığını göreceksiniz; şimdilik derlemenin geçtiğini doğrulayın.
  2. Field makrolarını kaldırıp virtual function string convert2string(); yazın: $sformatf("addr=0x%0h data=0x%0h we=%0b", addr, data, write_en) döndürsün. Driver'daki `uvm_info mesajını req.convert2string() ile sadeleştirin.
  3. Test'in build_phase'ine uvm_config_db#(uvm_active_passive_enum)::set(this, "env_inst.agent_inst", "is_active", UVM_PASSIVE); ekleyin. print_topology() çıktısı nasıl değişti? AGENT_CONN mesajı basıldı mı?
  4. Driver'daki get_next_item yerine try_next_item kullanın; null dönerse #10 bekleyip tekrar denesin. Bu değişiklik driver'ın "boşta" davranışını nasıl etkiler?
Hızlı Kontrol: Bir agent UVM_PASSIVE yapılandırıldığında hangi bileşenleri yaratır ve neden?

Yalnızca monitor. Sequencer ve driver uyarıcı üretip sürmek içindir; pasif bir agent'ın görevi yalnızca gözlemektir (örneğin başka bir blok tarafından sürülen bir arayüzü izlemek veya sistem seviyesinde kapsam toplamak). Monitor her iki modda da gerektiği için koşulsuz yaratılır.

Sıkça Sorulan Sorular (FAQ)

Soru 1: Neden Monitor pasiftir ve neden Agent her durumda Monitor'ü yaratır?

Cevap: Monitor'ün görevi fiziksel arayüzdeki sinyalleri yalnızca gözlemlemektir; hiçbir zaman sinyal sürmez veya değiştirmez. Bu yüzden "pasif" (passive) olarak adlandırılır. Verifikasyonda, Agent uyarıcı üretsin (ACTIVE) ya da yalnızca dinlesin (PASSIVE), DUT'tan gelen veriyi yakalayıp Scoreboard/Coverage gibi bileşenlere iletmek her durumda gereklidir. Bu nedenle build_phase içinde Monitor, get_is_active() koşuluna bakılmaksızın koşulsuz olarak oluşturulur.

Soru 2: Sequencer ve Driver neden sadece UVM_ACTIVE modda yaratılıyor?

Cevap: Sequencer ve Driver'ın tek amacı DUT'a uyarıcı (stimulus) üretip sürmektir. Bir Agent UVM_PASSIVE modda yapılandırıldığında, görevi yalnızca dinlemek/gözlemlemektir ve veri sürmemesi gerekir. if(get_is_active() == UVM_ACTIVE) koşulu sayesinde bu iki bileşen sadece gerektiğinde yaratılır; bu hem kaynak israfını önler hem de aynı verifikasyon ortamının (environment) hem aktif hem pasif senaryolarda yeniden kullanılabilmesini sağlar.

Soru 3: get_next_item neden bu laboratuvarda hiç transaction basmadan bekliyor?

Cevap: seq_item_port.get_next_item(req) çağrısı bloklayan (blocking) bir çağrıdır: Sequencer'ın elinde sürülecek bir paket olmadığı sürece geri dönmez. Bu laboratuvarda Sequencer'a herhangi bir Sequence (senaryo) gönderilmediği için kuyrukta hiç paket oluşmaz ve Driver sonsuza kadar bu satırda bekler. Paket üretimini tetikleyecek Sequence'ler yazıldığında (4. Gün) Driver paket çekmeye ve loglamaya başlayacaktır.

Soru 4: uvm_sequence_item ile uvm_object arasındaki fark nedir; transaction'ı neden uvm_object'ten türetmiyoruz?

Cevap: uvm_sequence_item, zaten uvm_object'ten türemiş özel bir sınıftır ve Sequence–Sequencer–Driver mekanizmasında akabilmek için gereken ek altyapıyı (örneğin sıralama/akış desteği ve kimlik bilgileri) içinde taşır. Düz bir uvm_object bu akışa giremez. Bu yüzden Sequencer üzerinden Driver'a gönderilecek veri paketleri her zaman uvm_sequence_item'dan türetilir; uvm_object ise daha çok konfigürasyon nesneleri gibi akmayan yapılar için uygundur.

Soru 5: uvm_field_int gibi alan (field) makrolarını kullanmanın faydası nedir?

Cevap: Alanlarınızı uvm_object_utils_begin/_end bloğu içinde uvm_field_int(...) ile tanıttığınızda, UVM bu alanlar için print(), copy(), compare(), pack()/unpack() gibi sık ihtiyaç duyulan metotları otomatik olarak üretir. Böylece her transaction için bu fonksiyonları elle yazmaktan kurtulursunuz; bu hem geliştirme süresini kısaltır hem de elle yazımdan kaynaklanabilecek hataları önler. UVM_ALL_ON bayrağı, bu otomasyonların tümünü ilgili alan için etkinleştirir.