UVM Eğitimi 4. Gün Laboratuvar Kılavuzu: Sequence'lar, Stimulus Yönetimi ve Konfigürasyon
Amaç
Bu laboratuvarın amacı, UVM testbench'ine dinamik senaryolar eklemek için Sequence sınıfları (uvm_sequence) oluşturmak, hiyerarşik (iç içe) senaryolar yazmak ve UVM Configuration Database (uvm_config_db) kullanarak testten en alt seviyedeki bileşenlere (örneğin Driver'a) parametre/konfigürasyon aktarımı yapmaktır.
Ön Koşullar
- 1., 2. ve 3. gün konuları (Agent hiyerarşisi, Sequence Item, TLM).
- SystemVerilog rand değişkenler ve with kısıtları (constraints) hakkında bilgi.
Lab 4.1: UVM Configuration DB Kullanımı
uvm_config_db, testbench'in herhangi bir yerinden bir kutuya bilgi koyup (set), başka bir yerinden bu kutudaki bilgiyi güvenle almamızı (get) sağlayan sihirli bir veri tabanıdır. Genellikle sanal arayüzleri (virtual interface) veya konfigürasyon nesnelerini taşımak için kullanılır.
uvm_config_db Fonksiyonları ve Parametreleri
Veri tabanına bir şey yazmak için set(), okumak için ise get() metodu kullanılır. Her iki metot da temel olarak 4 adet parametre alır:
Sözdizimi (Syntax):
uvm_config_db#(TYPE)::set(context, inst_name, field_name, value);
uvm_config_db#(TYPE)::get(context, inst_name, field_name, value);
- #(TYPE) : Taşınacak verinin tipidir. int, string, virtual interface veya konfigürasyon sınıfı (uvm_object) olabilir.
- context (Bağlam): İşlemin yapıldığı referans noktasıdır. Genellikle veriyi gönderen/alan sınıfın kendisi olan this anahtar kelimesi kullanılır. Global bir gönderim için uvm_root::get() veya null kullanılabilir.
- inst_name (Örnek Adı): Hedefin, context'e göre hiyerarşik yoludur.
- Gönderirken (Set): "env.agent.driver" gibi hedefin tam veya göreceli adresi yazılır.
- Alırken (Get): Sınıf zaten hedefin kendisiyse, "benim içimde ara" manasına gelen boş string "" kullanılır.
- field_name (Alan Adı): Bu bilgiye verdiğimiz özel etikettir (Örn: "timeout_degeri", "vif"). Okuyan taraf, tam olarak bu etiketi arayarak veriyi bulur.
- value (Değer): set işleminde kutuya konulan asıl veri, get işleminde ise kutudan çıkan verinin kaydedileceği değişkendir.
Basit Kullanım Örnekleri:
// 1. Passing a simple integer
uvm_config_db#(int)::set(this, "*", "max_loops", 100);
// 2. Passing a string globally
uvm_config_db#(string)::set(null, "*", "greeting", "Hello UVM!");
// 3. Receiving a string in any component
string my_str;
if (uvm_config_db#(string)::get(this, "", "greeting", my_str))
`uvm_info("CFG", $sformatf("Got string: %s", my_str), UVM_LOW)
Bu adımda, Test sınıfından Driver sınıfına bir "gecikme süresi" (delay_time) parametresi aktaracağız.
Görev 1: base_test sınıfının build_phase metodunda bir uvm_config_db ataması (set) yapın.
class base_test extends uvm_test;
`uvm_component_utils(base_test)
my_env env_inst;
// Constructor
function new(string name = "base_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env_inst = my_env::type_id::create("env_inst", this);
// Set a configuration integer for the driver's delay
// Context: this (base_test)
// Target Path: "env_inst.agent_inst.drv"
// Key/Field: "driver_delay"
// Value: 35
uvm_config_db#(int)::set(this, "env_inst.agent_inst.drv", "driver_delay", 35);
`uvm_info("TEST_CFG", "Set driver_delay to 35 via config_db.", UVM_LOW)
endfunction
// ... (Other phases will be added later)
endclass
Görev 2: 2. Gün yazdığımız my_driver sınıfının build_phase metodunu güncelleyerek bu değeri okuyun (get).
class my_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(my_driver)
// Variable to hold the delay time
int delay_time = 10; // Default value if not set by config_db
// Constructor
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Get the configuration integer
// Context: this (my_driver)
// Target Path: "" (Look exactly at this component)
// Key/Field: "driver_delay"
// Variable to store: delay_time
if(uvm_config_db#(int)::get(this, "", "driver_delay", delay_time)) begin
`uvm_info("DRV_CFG", $sformatf("Successfully got driver_delay: %0d", delay_time), UVM_LOW)
end else begin
`uvm_warning("DRV_CFG", "Could not get driver_delay! Using default.")
end
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
forever begin
seq_item_port.get_next_item(req);
`uvm_info("DRIVER", $sformatf("Driving Transaction: Addr=0x%0h, WE=%0b", req.addr, req.write_en), UVM_LOW)
// Use the dynamically configured delay time
#(delay_time);
seq_item_port.item_done();
end
endtask
endclass
Lab 4.2: Temel Sequence'ların Oluşturulması
Sequencer ve Driver artık komut bekliyor. Modern UVM'de senaryoları (sequence) yazarken doğrudan start_item() ve finish_item() API'leri kullanılır.
Önemli Kavram: start_item ve finish_item Nasıl Çalışır? (Handshake Mekanizması)
Bu iki metot, Sequence (Garson), Sequencer (Sipariş Sırası) ve Driver (Aşçı) arasındaki senkronizasyonu sağlar:
- start_item(req) (İzin İsteme): Sequence, "Elimde yeni bir işlem var, Driver müsait mi?" diye sorar. Driver get_next_item() komutunu çağırana kadar bu satır kodun akışını bloklar (bekletir).
- Late Randomization (Geç Rastgeleleştirme): Dikkat ederseniz koddaki req.randomize() işlemi start_item'dan sonra yapılmıştır. Bunun sebebi veriyi tam Driver'ın müsait olduğu (iznin çıktığı) milisaniyede üretmektir. Böylece veri, simülasyonun o anki en güncel durumuna göre şekillenir.
- finish_item(req) (Teslimat ve Onay Bekleme): İçi doldurulmuş paket Driver'a yollanır. Ancak Sequence işine devam etmez; Driver fiziksel sürme işlemini bitirip item_done() çağrısını yapana kadar bu satırda bekler.
Görev: Bu mekanizmayı kullanarak; biri sadece YAZMA işlemi üreten (write_sequence), diğeri sadece OKUMA işlemi üreten (read_sequence) iki temel senaryo yazın.
// ----------------------------------------------------------------------------
// SEQUENCE 1: Write Sequence
// ----------------------------------------------------------------------------
class write_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(write_sequence)
function new(string name = "write_sequence");
super.new(name);
endfunction
// The 'body' task contains the behavior of the sequence
virtual task body();
// Create the transaction object
req = my_transaction::type_id::create("req");
// Step 1: Wait for driver to be ready (Handshake begins)
start_item(req);
// Step 2: Late Randomization with inline constraints
if (!req.randomize() with { write_en == 1'b1; }) begin
`uvm_error("SEQ", "Randomization failed!")
end
// Step 3: Send to driver and wait for item_done (Handshake completes)
finish_item(req);
endtask
endclass
// ----------------------------------------------------------------------------
// SEQUENCE 2: Read Sequence
// ----------------------------------------------------------------------------
class read_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(read_sequence)
function new(string name = "read_sequence");
super.new(name);
endfunction
virtual task body();
req = my_transaction::type_id::create("req");
start_item(req);
if (!req.randomize() with { write_en == 1'b0; }) begin
`uvm_error("SEQ", "Randomization failed!")
end
finish_item(req);
endtask
endclass
Lab 4.3: Hiyerarşik (Master) Sequence Oluşturma
Birden fazla alt senaryoyu tek bir çatı altında toplayarak karmaşık senaryolar yaratabiliriz. Buna "Hierarchical Sequences" denir.
Görev: 2 kez yazma, ardından 2 kez okuma yapan bir ana sequence (main_sequence) oluşturun.
class main_sequence extends uvm_sequence #(my_transaction);
`uvm_object_utils(main_sequence)
// Handles for sub-sequences
write_sequence wr_seq;
read_sequence rd_seq;
function new(string name = "main_sequence");
super.new(name);
endfunction
virtual task body();
`uvm_info("MAIN_SEQ", "Starting main hierarchical sequence...", UVM_LOW)
// Execute 2 Write sequences
repeat(2) begin
wr_seq = write_sequence::type_id::create("wr_seq");
// To start a sub-sequence, pass the current sequencer (m_sequencer)
wr_seq.start(m_sequencer, this);
end
// Execute 2 Read sequences
repeat(2) begin
rd_seq = read_sequence::type_id::create("rd_seq");
rd_seq.start(m_sequencer, this);
end
`uvm_info("MAIN_SEQ", "Main sequence finished.", UVM_LOW)
endtask
endclass
Lab 4.4: Sequence'ın Test İçinden Tetiklenmesi
Artık hiyerarşik senaryomuzu test sınıfının (base_test) run_phase'i içerisinde tetikleyebiliriz. Simülasyon süresini kontrol etmek için objection (raise_objection / drop_objection) mekanizmasını kullanmayı unutmayacağız.
Görev: base_test sınıfını güncelleyerek main_sequence'i Agent'ın içindeki Sequencer üzerinde başlatın.
class base_test extends uvm_test;
`uvm_component_utils(base_test)
my_env env_inst;
function new(string name = "base_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env_inst = my_env::type_id::create("env_inst", this);
uvm_config_db#(int)::set(this, "env_inst.agent_inst.drv", "driver_delay", 35);
endfunction
virtual task run_phase(uvm_phase phase);
// Declare the sequence
main_sequence m_seq;
// 1. Raise objection to start simulation time
phase.raise_objection(this);
`uvm_info("TEST", "Starting main sequence...", UVM_LOW)
// 2. Create the sequence
m_seq = main_sequence::type_id::create("m_seq");
// 3. Start the sequence on the designated sequencer
m_seq.start(env_inst.agent_inst.sqr);
`uvm_info("TEST", "Main sequence completed.", UVM_LOW)
// Add a little extra time before shutting down
#50;
// 4. Drop objection to end simulation
phase.drop_objection(this);
endtask
endclass
Özet ve Beklenen Sonuç
Simülasyon çalıştırıldığında testbench aşağıdaki sırayla çalışacaktır:
- build_phase sırasında test, config_db'ye 35 değerini yazacak.
- Driver, config_db'den 35 değerini başarıyla okuduğunu loglayacak (DRV_CFG).
- run_phase'de test main_sequence'i başlatacak.
- main_sequence sırasıyla iki adet YAZMA (write_en=1), ardından iki adet OKUMA (write_en=0) işlemi üretecek.
- Driver bu işlemleri alıp ekrana yazdıracak (Driving Transaction...) ve her işlemde 35 zaman birimi bekleyecek.
- İşlemler bittikten 50 zaman birimi sonra objection düşülecek ve simülasyon başarıyla tamamlanacaktır.
4. Gün Sıkça Sorulan Sorular (FAQ)
Bu bölüm eğitim sırasında 4. Gün konuları ile ilgili olarak öğrencilerden gelebilecek soruları ve yanıtlarını içerir.
Soru 1: uvm_config_db kullanırken neden "set" işleminde hedef yolunu "env_inst.agent_inst.drv" şeklinde string (metin) olarak yazıyoruz? Sınıf referanslarını doğrudan kullansak olmaz mı?
Cevap: build_phase yukarıdan aşağıya (top-down) çalışır. Biz base_test'in build_phase'i içerisindeyken, agent_inst ve drv henüz yaratılmamış olabilir (bellekte yer kaplamazlar). Olmayan bir objenin referansını veremezsiniz. Bu yüzden UVM, "İleride bu yolda (path) yaratılacak olan bileşen bu değeri alsın" mantığıyla string tabanlı hiyerarşik bir adresleme sistemi kullanır.
Soru 2: Neden uvm_do gibi daha kısa makroları kullanmak yerine transaction yaratma, kısıtlama (randomize) ve gönderme işlemlerini uzun uzun elimizle yazıyoruz?
Cevap: Eski UVM kodlarında uvm_do makroları çok popülerdi. Ancak bu makrolar arka planda tam olarak ne olduğunu (ve hata çıktığında hangi satırda patladığını) gizlerler. Modern UVM standartlarında ve endüstrideki büyük projelerde, debug edilebilirliğinin (hata ayıklama) yüksek olması nedeniyle API tabanlı start_item / finish_item yapısı zorunlu tutulmaktadır.
Soru 3: Sequence'lar (uvm_sequence) neden uvm_component değil de uvm_object soyundan geliyor?
Cevap: uvm_component sınıfları (Env, Agent, Driver) donanım iskeleti gibidir; simülasyonun 0. anında yaratılırlar ve simülasyon bitene kadar asla yok edilmezler. Sequence'lar ise dinamiktir. Simülasyonun ortasında yaratılır, içindeki görevleri yapar ve işleri bitince bellekten silinirler. Bu kısa ömürlü ve "veri tabanlı" yapıları nedeniyle uvm_object (daha spesifik olarak uvm_sequence_item) soyundan gelirler. Component fazlarına (build_phase, connect_phase vb.) sahip değildirler.
Soru 4: Objection (raise_objection) kaldırma/indirme işlemini neden Sequence'ın kendi içinde (body taskı içinde) yapmadık da base_test içinde yaptık?
Cevap: Sequence içinde objection kullanmak mümkündür ancak kesinlikle tavsiye edilmez. Büyük bir projede binlerce transaction ve yüzlerce alt-sequence çalışabilir. Her biri için objection raise/drop yapmak performansı inanılmaz derecede düşürür. Endüstri standardı olarak simülasyonun ne zaman başlayıp ne zaman biteceğine her zaman testin (örneğin base_test) en üst run_phase'i karar vermelidir. Test objection'ı kaldırır, ana sequence'ı başlatır, sequence bitince objection'ı düşürür.