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)

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:

  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

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:

  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.