EDA Playground'da Dene

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)

Kodun Açıklaması

Bu blok, uvm_config_db'nin en temel set ve get kullanımlarını üç küçük örnekle gösterir:

  • 1. Örnek (uvm_config_db#(int)::set): Tip parametresi olarak int verilir ve veri tabanına "max_loops" etiketiyle 100 değeri yazılır. context olarak this, inst_name olarak ise "*" (joker karakter) kullanılmıştır. "*" ifadesi "bu bağlamın altındaki tüm bileşenler bu değere erişebilsin" anlamına gelir; yani değerin görünürlüğünü olabildiğince geniş tutar.
  • 2. Örnek (uvm_config_db#(string)::set): Bu kez tip string'tir ve context olarak null verilmiştir. null bağlam, atamayı global (en tepeden) yapar; böylece "greeting" etiketli "Hello UVM!" değeri testbench'in herhangi bir noktasından okunabilir.
  • 3. Örnek (uvm_config_db#(string)::get): Önce değeri tutacak bir my_str değişkeni tanımlanır. get metodu okuma başarılı olursa 1, başarısız olursa 0 döndürdüğü için çağrı bir if koşulu içine alınmıştır. inst_name olarak boş string "" kullanılması "tam olarak bu bileşene gönderilen değeri ara" demektir. Değer bulunursa uvm_info makrosu ile $sformatf kullanılarak okunan metin ekrana basılır.

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

Kodun Açıklaması

Bu blok, konfigürasyon değerini gönderen (set eden) tarafı tanımlar:

  • uvm_component_utils(base_test): Sınıfı UVM fabrikasına (factory) kaydeder; böylece base_test ismiyle dinamik olarak yaratılabilir ve type_id::create ile örneklenebilir.
  • function new(...): Bileşenin kurucusudur (constructor). super.new(name, parent) çağrısıyla üst sınıfın kurucusunu çalıştırarak bileşeni UVM hiyerarşisine parent altına bağlar.
  • build_phase: Bileşenlerin yukarıdan aşağıya (top-down) yaratıldığı fazdır. İlk olarak super.build_phase(phase) çağrılır.
  • my_env::type_id::create("env_inst", this): env nesnesini fabrika üzerinden yaratır. İkinci argüman this ile yaratılan env_inst'in base_test'in çocuğu olduğu belirtilir.
  • uvm_config_db#(int)::set(this, "env_inst.agent_inst.drv", "driver_delay", 35): Asıl konfigürasyon atamasıdır. context olarak this (yani base_test), inst_name olarak hedefe giden hiyerarşik yol "env_inst.agent_inst.drv", field_name olarak "driver_delay" etiketi ve value olarak 35 verilir. Bu satır, "ileride bu yolda yaratılacak olan driver, driver_delay etiketiyle 35 değerini bulsun" anlamına gelir.
  • uvm_info("TEST_CFG", ...): Atamanın yapıldığını UVM_LOW ayrıntı seviyesinde loglar.

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

Kodun Açıklaması

Bu blok, konfigürasyon değerini alan (get eden) ve onu kullanan tarafı tanımlar:

  • int delay_time = 10;: Okunacak değeri tutacak değişkendir. 10 varsayılan değeri, config_db'den okuma başarısız olursa devreye girer; böylece simülasyon yine de güvenli bir değerle devam eder.
  • build_phase içindeki uvm_config_db#(int)::get(this, "", "driver_delay", delay_time): set ile gönderilen değeri okur. context olarak this (driver'ın kendisi), inst_name olarak boş string "" ("tam olarak bana gönderileni ara") ve field_name olarak "driver_delay" kullanılır. Okunan değer doğrudan delay_time değişkenine yazılır.
  • if (...) ... else ...: get başarılı olursa 1 döndürür ve uvm_info ile okunan değer loglanır; başarısız olursa uvm_warning ile uyarı basılır ve varsayılan değerle devam edilir. Bu savunmacı yaklaşım, eksik konfigürasyonun sessizce gözden kaçmasını engeller.
  • run_phase içindeki seq_item_port.get_next_item(req): Sequencer'dan sıradaki transaction'ı (req) bloklayarak çeker; transaction gelene kadar bekler.
  • #(delay_time): Sürme işlemi sırasında config_db'den dinamik olarak okunan gecikmeyi uygular. Sabit bir değer yerine konfigüre edilebilir delay_time kullanılması, aynı driver'ın farklı testlerde farklı zamanlamalarla çalışmasını sağlar.
  • seq_item_port.item_done(): Driver'ın işlemi tamamladığını sequencer'a (ve dolayısıyla finish_item'da bekleyen sequence'a) bildirir; handshake'i kapatır.

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:

  1. 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).
  2. 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.
  3. 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

Kodun Açıklaması

Bu blok, iki temel senaryoyu (write_sequence ve read_sequence) tanımlar. İkisi de uvm_sequence #(my_transaction) sınıfından türer:

  • uvm_object_utils(...): Sequence'lar bileşen değil nesne (object) olduğu için uvm_component_utils yerine uvm_object_utils ile fabrikaya kaydedilir.
  • function new(string name = ...): Sequence kurucusu yalnızca bir name argümanı alır; çünkü nesnelerin hiyerarşik bir parent'ı yoktur (bileşenlerin aksine).
  • virtual task body();: Sequence'ın asıl davranışını içeren görevdir. Sequence başlatıldığında otomatik olarak body çalışır.
  • req = my_transaction::type_id::create("req"): Gönderilecek transaction nesnesi fabrika üzerinden yaratılır.
  • start_item(req): Handshake'i başlatır; driver get_next_item çağırıp müsait olana kadar bu satır akışı bloklar.
  • req.randomize() with { write_en == 1'b1; }: Geç rastgeleleştirme (late randomization) adımıdır ve start_item'dan sonra yapılır. with bloğundaki satır içi kısıt (inline constraint), write_sequence'te write_en'i 1 (yazma), read_sequence'te ise 0 (okuma) olmaya zorlar. randomize() başarısız olursa 0 döndürür; bu durumda uvm_error ile hata basılır.
  • finish_item(req): Doldurulmuş transaction'ı driver'a teslim eder ve driver item_done çağırana kadar bekleyerek handshake'i tamamlar.

İki sequence arasındaki tek anlamlı fark, randomize() with içindeki kısıttır (write_en == 1'b1 yazma için, write_en == 1'b0 okuma için).

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

Kodun Açıklaması

Bu blok, alt sequence'ları çağıran hiyerarşik (ana) sequence'ı tanımlar:

  • write_sequence wr_seq; ve read_sequence rd_seq;: Alt senaryolara ait tutamaçlar (handle) sınıf içinde tanımlanır.
  • body() içindeki repeat(2) begin ... end: Aynı işlemin iki kez tekrarlanmasını sağlar; önce iki yazma, ardından iki okuma üretilir.
  • wr_seq = write_sequence::type_id::create("wr_seq"): Her tekrarda alt sequence fabrika üzerinden yeniden yaratılır. Döngü içinde yeniden yaratmak, her transaction'ın taze bir nesne olmasını sağlar.
  • wr_seq.start(m_sequencer, this): Alt sequence'ı çalıştıran kritik satırdır. start metodu alt sequence'ın body'sini tetikler. Birinci argüman m_sequencer, ana sequence'ın hâlihazırda üzerinde koştuğu sequencer'dır; bu sayede alt sequence'lar ayrıca bir sequencer'a atanmadan aynı sequencer üzerinde çalışır. İkinci argüman this, alt sequence'ın "parent sequence"ını belirtir ve hiyerarşik ilişkiyi (parent/child) kurarak ilerleme/öncelik yönetimini düzgün tutar.

m_sequencer, sequence başlatıldığında UVM tarafından otomatik olarak ayarlanan, sequence'ın bağlı olduğu sequencer'a işaret eden yerleşik bir değişkendir; bu yüzden alt sequence'ları aynı sequencer'a yönlendirmek için onu doğrudan kullanabiliriz.

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

Kodun Açıklaması

Bu blok, hem konfigürasyon atamasını (build_phase) hem de senaryonun tetiklenmesini (run_phase) bir arada gösterir:

  • build_phase: Önceki gibi env_inst'i type_id::create ile yaratır ve uvm_config_db#(int)::set(...) ile driver'a driver_delay = 35 değerini gönderir.
  • run_phase içindeki main_sequence m_seq;: Çalıştırılacak ana sequence için bir tutamaç tanımlanır.
  • phase.raise_objection(this): Simülasyon zamanının erken bitmesini engeller. Objection kaldırıldığı sürece UVM, bu fazın hâlâ işi olduğunu bilir ve simülasyonu sonlandırmaz.
  • m_seq = main_sequence::type_id::create("m_seq"): Ana sequence nesnesi fabrika üzerinden yaratılır.
  • m_seq.start(env_inst.agent_inst.sqr): Sequence'ı, agent'ın içindeki sequencer (sqr) üzerinde başlatır. Burada start'a sequencer'ın tam hiyerarşik referansı verilir; bu, sequence'ı doğru sequencer'a bağlar ve body'sinin çalışmasını başlatır. Sequence tamamen bitene kadar bu satır bekler.
  • #50;: Son transaction sürüldükten sonra sinyallerin yerleşmesi için simülasyona biraz ek süre tanır.
  • phase.drop_objection(this): Objection'ı düşürür. Bekleyen başka objection kalmadığında UVM run_phase'i sonlandırır ve simülasyon temiz bir şekilde biter.

Önemli Noktalar

Bu dersin tamamından çıkarılması gereken en kritik püf noktaları ve en iyi pratikler şunlardır:

  • String tabanlı adresleme: uvm_config_db ile set yaparken hedefi ("env_inst.agent_inst.drv" gibi) string olarak yazın; çünkü build_phase top-down çalışır ve değer gönderilirken hedef bileşen henüz yaratılmamış olabilir. Geniş erişim için "*" jokerini kullanmaktan çekinmeyin.
  • get dönüş değerini her zaman kontrol edin: get çağrısını bir if içine alıp başarısız durumu (uvm_warning + varsayılan değer) ele alın. Bu, eksik veya hatalı yazılmış bir field_name'in sessizce gözden kaçmasını engeller.
  • Geç rastgeleleştirme (late randomization): randomize()'ı her zaman start_item'dan sonra, finish_item'dan önce yapın. Böylece veri, driver'ın gerçekten hazır olduğu en güncel simülasyon durumuna göre üretilir.
  • Doğru fabrika makrosunu seçin: Bileşenler (uvm_component) için uvm_component_utils, sequence'lar ve transaction'lar (uvm_object) için uvm_object_utils kullanın. Tüm nesneleri new yerine type_id::create ile yaratarak fabrika override'larından faydalanın.
  • m_sequencer ile yeniden kullanım: Hiyerarşik sequence'larda alt sequence'ları start(m_sequencer, this) ile başlatın; bu, alt sequence'ları ana sequence ile aynı sequencer'a bağlar ve parent/child ilişkisini kurar.
  • Objection'ı yalnızca testte yönetin: Simülasyonun başlangıç/bitiş kontrolünü (raise_objection / drop_objection) her zaman testin en üst run_phase'inde yapın; performans nedeniyle bunu tek tek sequence'lar içinde kullanmaktan kaçının.

Özet ve Beklenen Sonuç

Simülasyon çalıştırıldığında testbench aşağıdaki sırayla çalışacaktır:

  1. build_phase sırasında test, config_db'ye 35 değerini yazacak.
  2. Driver, config_db'den 35 değerini başarıyla okuduğunu loglayacak (DRV_CFG).
  3. run_phase'de test main_sequence'i başlatacak.
  4. main_sequence sırasıyla iki adet YAZMA (write_en=1), ardından iki adet OKUMA (write_en=0) işlemi üretecek.
  5. Driver bu işlemleri alıp ekrana yazdıracak (Driving Transaction...) ve her işlemde 35 zaman birimi bekleyecek.
  6. İş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.

Soru 5: Transaction'ı neden start_item çağrısından ÖNCE değil de SONRA randomize ediyoruz? Önce randomize edip sonra göndersek ne kaybederiz?

Cevap: Teknik olarak start_item'dan önce randomize etmek de derlenir ve çalışır, ancak "geç rastgeleleştirme" (late randomization) endüstride bilinçli olarak tercih edilen bir tekniktir. start_item, driver get_next_item çağırana kadar akışı bloklar; yani driver'ın gerçekten o transaction'ı isteyeceği ana kadar bekler. Veriyi tam o anda üretirsek, simülasyonun en güncel durumundan (örneğin scoreboard'dan gelen geri besleme, register değerleri veya bir önceki işlemin sonucu) faydalanabiliriz. Eğer veriyi çok erken üretirsek, transaction sıraya girene kadar geçen sürede bu bilgiler bayatlayabilir ve kısıtlarımız (randomize() with) eski duruma göre çözülmüş olur. Kısacası geç randomize, en akıllı ve en güncel stimulus'u üretmenin yoludur.

Soru 6: Hiyerarşik sequence'te alt sequence'ı wr_seq.start(m_sequencer, this) ile başlatırken kullandığımız m_sequencer tam olarak nedir ve nereden gelir?

Cevap: m_sequencer, her sequence'ın içinde otomatik olarak bulunan, o sequence'ın hangi sequencer üzerinde koştuğunu gösteren yerleşik (built-in) bir tutamaçtır. Biz ana sequence'ı testte m_seq.start(env_inst.agent_inst.sqr) ile başlattığımızda, UVM m_seq'in m_sequencer alanını otomatik olarak o sequencer'a (sqr) ayarlar. Dolayısıyla ana sequence'ın body'si içindeyken m_sequencer zaten doğru sequencer'a işaret eder. Alt sequence'ları başlatırken bu değeri tekrar kullanmak, onları el yordamıyla bir sequencer referansı bulmaya zorlamadan aynı sequencer'a bağlamamızı sağlar. İkinci argüman olarak verdiğimiz this ise alt sequence'ın "parent"ını ana sequence yapar; bu parent/child ilişkisi öncelik (priority) ve ilerleme yönetiminin sağlıklı çalışması için önemlidir.