UVM Eğitimi 2. Gün Laboratuvar Kılavuzu: Veri Modeli ve Agent Bileşenleri
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ğrudanuvm_objectyerineuvm_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ındakirandanahtar 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 biruvm_object_utilsyerine bu blok kullanıldığında içine alan makroları eklenebilir.uvm_field_int(data, UVM_ALL_ON)(veaddr,write_eniçin aynısı): Her bir değişkeni UVM'in otomasyon altyapısına tanıtır.UVM_ALL_ONbayrağı sayesinde bu alanlar içinprint(),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 olanuvm_sequence_item'ın kurulumunu tamamlar. Biruvm_objecttürevi olduğu için yapıcı sadecenameparametresi alır (component'lerden farklı olarakparentalmaz).
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_transactiontipi üzerine parametrelendiririz. Bu sayede Sequencer'ın yöneteceği veri paketinin tipi netleşir. Basit durumlarda birtypedefyeterli olsa da, okunabilirlik için ayrı bir sınıf tanımlanmıştır.uvm_component_utils(my_sequencer)veuvm_component_utils(my_driver): Bu sınıfları UVM factory'ye birer component olarak kaydeder. Transaction'da kullanılanuvm_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ı olarakparentparametresi de alır vesuper.new(name, parent)ile üst sınıfa iletilir. my_driver extends uvm_driver #(my_transaction): Driver damy_transactiontipi ile parametrelendirilir.uvm_driver'dan türetilince hazır olarakseq_item_portvereqgibi ü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ınanreqdeğişkenine yazılır.uvm_info("DRIVER", $sformatf(...), UVM_LOW): Çekilen paketinreq.addr,req.datavereq.write_enalanlarını biçimli bir mesaj olarak loglar.UVM_LOWverbosity 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ılmadanget_next_itemyeni bir paket vermez;foreverdöngüsü bu get/done çiftini sürekli tekrarlar.
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'inuvm_monitortemel sınıfından türetiriz. Driver'dan farklı olarak Monitor sinyal sürmez, yalnızca okur; bu yüzden bir Sequencer'a veyaseq_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 gibinameveparentalı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;#20gecikmesi gerçek bir örnekleme periyodunu temsil eder.uvm_info("MONITOR", "...", UVM_HIGH): Örnekleme yapıldığını loglar. BuradaUVM_HIGHverbosity 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'inuvm_agenttemel 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; nesnelerbuild_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::createkullanımı, factory override mekanizmasının çalışabilmesi için doğrudannewç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_ACTIVEise Agent'ın uyarıcı üretmesi beklenir.- Bu koşul içinde
sqrvedrvyaratılır: Sequencer ve Driver yalnızcaUVM_ACTIVEmodda 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'ınseq_item_port'unu Sequencer'ınseq_item_export'una bağlar. Bu bağlantı sayesinde Driver'dakiget_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 yineget_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 ilgiliuvm_componenttürevlerinden türetilmelidir. Bu ayrım,uvm_object_utilsileuvm_component_utilsmakro seçimini de belirler. - Field makrolarını kullanın:
uvm_field_intgibi alan makrolarıUVM_ALL_ONile birlikteprint,copy,comparegibi 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_ACTIVEolduğunda yaratılmalı ve bağlanmalıdır; aksi halde pasif Agent gereksiz uyarıcı üretmeye çalışır. type_id::createtercih edin: Bileşenleri ve nesneleri doğrudannewile değil, factory üzerindentype_id::createile 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 zamanconnect_phaseiç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.
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.