Virtual Sequence, Virtual Sequencer ve Çok Agent'lı Ortamlar

UVM Gün 8: Senaryo Koordinasyonu | Blok seviyesi env'in sistem seviyesinde yeniden kullanımı, virtual sequencer, p_sequencer ve `uvm_declare_p_sequencer, fork/join ile paralel alt sequence'ler, lockstep senaryosu, sequencer arbitrasyonu ve öncelik, lock() / grab(), sequence kütüphanesi

Amaç

Gün 7'ye kadar tek bir agent, tek bir sequencer vardı; test seq.start(env.agent.sqr) deyince her şey hallediliyordu. Gerçek bir SoC'de ise onlarca agent vardır ve senaryo "AXI'den veri gelirken UART'tan komut gönder, ortasında interrupt bekle" gibi birden fazla agent'ı koordine eden bir hikâyedir. Bu derste iki ALU çekirdeğinden oluşan küçük bir sistem kuracak, Gün 7'deki alu_env'i değiştirmeden iki kez kullanacak ve senaryoları bir virtual sequence ile yöneteceksiniz. Dersin ikinci yarısı, aynı sequencer'a birden çok sequence'in yarıştığı durumu ele alır: arbitrasyon, öncelik, lock/grab.

Ön Koşullar

  • UVM Gün 4 (sequence, m_sequencer, hiyerarşik sequence'ler), Gün 6–7 (alu_env, alu_agent, alu_sequencer, sequence'ler, test kancası).
  • SystemVerilog Gün 4: fork...join ve join_any (Dersler 24–25).

Problem: Senaryoyu Kim Koordine Eder?

İki agent'lı bir ortamda testin run_phase'ine şunu yazabilirsiniz:

fork
  seq_a.start(env.env_a.agent.sqr);
  seq_b.start(env.env_b.agent.sqr);
join

Çalışır; ama senaryo artık test sınıfının içine gömülüdür ve env'in iç yollarını bilir. Üç test aynı senaryoyu küçük farklarla koşacaksa üç kopya oluşur; senaryoyu başka bir testbench'te kullanmak istediğinizde test sınıfını taşımanız gerekir. UVM'in cevabı, senaryoyu da bir sequence yapmaktır:

Test'te fork Virtual sequence
Senaryo nerede yaşar? Test sınıfında Yeniden kullanılabilir bir uvm_sequence nesnesinde
Sequencer yollarını kim bilir? Test (env.env_a.agent.sqr) Virtual sequencer (tek yerde)
Senaryoları birleştirmek Kopyala-yapıştır Hiyerarşik: virtual sequence içinde başka virtual sequence'ler
Test ne yapar? Her şeyi Yalnızca hangi virtual sequence'in koşacağını seçer (Gün 7 kancası)

Virtual sequence, kendisi hiç start_item yapmayan, yalnızca başka sequence'leri doğru sequencer'larda başlatan bir sequence'tir. Üzerinde koştuğu virtual sequencer ise driver'ı olmayan, gerçek sequencer'lara handle tutan bir "yönlendirme tablosu"dur.

uvm_test_top : dual_lockstep_test
  └─ denv : dual_alu_env
       ├─ env_a : alu_env   (Gün 7, degismedi)  agent.sqr ◀─┐
       ├─ env_b : alu_env   (Gün 7, degismedi)  agent.sqr ◀─┼─ vsqr.sqr_a / vsqr.sqr_b
       └─ vsqr  : alu_vsequencer  (driver yok) ─────────────┘
                     ▲
            dual_lockstep_vseq.start(denv.vsqr)   ──▶  body(): fork  sa.start(p_sequencer.sqr_a)
                                                                    sb.start(p_sequencer.sqr_b)  join

Lab 8.1: Blok Seviyesinden Sistem Seviyesine — İki ALU, Bir Env

Görev: Gün 7'deki alu_env'i iki kez örnekleyen bir sistem env'i, bir virtual sequencer ve iki DUT'lu tb_top yazın.

// Virtual sequencer: driver'i yok; yalnizca gercek sequencer'lara handle tutar
class alu_vsequencer extends uvm_sequencer;           // #(uvm_sequence_item) varsayilan parametre
  `uvm_component_utils(alu_vsequencer)

  alu_sequencer sqr_a;       // env'in connect_phase'i doldurur
  alu_sequencer sqr_b;

  function new(string name = "alu_vsequencer", uvm_component parent = null);
    super.new(name, parent);
  endfunction
endclass

// Sistem seviyesi env: iki blok seviyesi env + virtual sequencer
class dual_alu_env extends uvm_env;
  `uvm_component_utils(dual_alu_env)

  alu_env        env_a;      // Gun 7: agent + predictor + scoreboard + coverage
  alu_env        env_b;
  alu_vsequencer vsqr;

  function new(string name = "dual_alu_env", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env_a = alu_env::type_id::create("env_a", this);
    env_b = alu_env::type_id::create("env_b", this);
    vsqr  = alu_vsequencer::type_id::create("vsqr", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    vsqr.sqr_a = env_a.agent.sqr;   // TLM baglantisi degil, duz handle atamasi
    vsqr.sqr_b = env_b.agent.sqr;
  endfunction
endclass
// tb_top.sv: iki interface, iki DUT, iki config_db::set
module tb_top;
  import uvm_pkg::*;
  import alu_pkg::*;

  logic clk = 0;
  always #5 clk = ~clk;

  alu_if aif_a(clk);
  alu_if aif_b(clk);

  alu dut_a (.clk(aif_a.clk), .rst_n(aif_a.rst_n), .in_valid(aif_a.in_valid),
             .operand_a(aif_a.operand_a), .operand_b(aif_a.operand_b), .opcode(aif_a.opcode),
             .out_valid(aif_a.out_valid), .result(aif_a.result), .flags(aif_a.flags));
  alu dut_b (.clk(aif_b.clk), .rst_n(aif_b.rst_n), .in_valid(aif_b.in_valid),
             .operand_a(aif_b.operand_a), .operand_b(aif_b.operand_b), .opcode(aif_b.opcode),
             .out_valid(aif_b.out_valid), .result(aif_b.result), .flags(aif_b.flags));

  initial begin
    // Ayni anahtar ("vif"), farkli yollar: her alu_env kendi interface'ini bulur (Gun 6, FAQ #5)
    uvm_config_db#(virtual alu_if)::set(null, "uvm_test_top.denv.env_a.*", "vif", aif_a);
    uvm_config_db#(virtual alu_if)::set(null, "uvm_test_top.denv.env_b.*", "vif", aif_b);
    run_test();
  end
endmodule

Kodun Açıklaması

  • alu_env değişmedi: Gün 7'de tek ALU için yazdığınız env, agent'ı, scoreboard'u ve coverage'ıyla birlikte iki kez örnekleniyor. Bu, UVM'in "blok seviyesi ortamı sistem seviyesinde yeniden kullan" vaadidir ve ancak env'in dış dünyayı yalnızca uvm_config_db yolları ve portlar üzerinden tanımasıyla mümkündür; env'in içinde tb_top.aif gibi bir hiyerarşik referans olsaydı iki kez kullanılamazdı (Gün 6, FAQ #1).
  • uvm_sequencer parametresiz: Virtual sequencer hiçbir transaction tipi taşımaz; uvm_sequencer'ın varsayılan #(uvm_sequence_item) parametresi yeterlidir. İçine uvm_sequencer türevleri yerine uvm_sequence_item koyarsanız driver bekler; virtual sequencer'a bağlı bir driver yoktur ve olmamalıdır.
  • vsqr.sqr_a = env_a.agent.sqr: Bu bir TLM bağlantısı değil, Gün 3'teki handle aktarımıyla aynı fikir: virtual sequencer, hedef sequencer'ların adresini tutar. connect_phase'de yapılır; çünkü build_phase sırasında env_a.agent.sqr henüz yaratılmamış olabilir (build top-down, alt bileşenler sonra).
  • İki set, aynı anahtar: Driver ve monitor hâlâ get(this, "", "vif", vif) der; yol deseni hangi env'in altında olduklarına göre doğru interface'i seçer. Gün 5'teki instance override mantığının config_db karşılığı.

Lab 8.2: Virtual Sequence — p_sequencer ile Doğru Sequencer'a Ulaşmak

Görev: İki senaryo yazın: (1) iki ALU'da bağımsız sequence'ler paralel, (2) lockstep: iki ALU'ya aynı işlem, aynı anda.

// Tek islem suren kucuk sequence: lockstep senaryosunun yapi tasi
class alu_single_seq extends uvm_sequence #(alu_transaction);
  `uvm_object_utils(alu_single_seq)
  opcode_e  op_v;            // item alanlariyla (opcode, a, b) karismasin diye _v soneki
  bit [7:0] a_v, b_v;

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

  virtual task body();
    req = alu_transaction::type_id::create("req");
    start_item(req);
    if (!req.randomize() with { opcode == op_v; a == a_v; b == b_v; })
      `uvm_error("SEQ", "randomize basarisiz")
    finish_item(req);
  endtask
endclass

// Virtual sequence 1: iki ALU'da bagimsiz senaryolar, paralel
class dual_independent_vseq extends uvm_sequence;      // item tipi yok: hic start_item yapmaz
  `uvm_object_utils(dual_independent_vseq)
  `uvm_declare_p_sequencer(alu_vsequencer)             // p_sequencer: alu_vsequencer tipinde handle

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

  virtual task body();
    alu_random_seq rnd = alu_random_seq::type_id::create("rnd");
    alu_corner_seq crn = alu_corner_seq::type_id::create("crn");
    rnd.n_items = 100;
    fork
      rnd.start(p_sequencer.sqr_a, this);   // ALU A: rastgele
      crn.start(p_sequencer.sqr_b, this);   // ALU B: kose taramasi
    join                                    // ikisi de bitmeden virtual sequence bitmez
  endtask
endclass

// Virtual sequence 2: lockstep — iki ALU'ya AYNI islem, AYNI anda
class dual_lockstep_vseq extends uvm_sequence;
  `uvm_object_utils(dual_lockstep_vseq)
  `uvm_declare_p_sequencer(alu_vsequencer)
  int n_items = 50;

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

  virtual task body();
    alu_transaction t = alu_transaction::type_id::create("t");   // yalnizca deger uretmek icin
    repeat (n_items) begin
      alu_single_seq sa, sb;
      if (!t.randomize())
        `uvm_error("VSEQ", "randomize basarisiz")
      sa = alu_single_seq::type_id::create("sa");
      sb = alu_single_seq::type_id::create("sb");
      sa.op_v = t.opcode;  sa.a_v = t.a;  sa.b_v = t.b;
      sb.op_v = t.opcode;  sb.a_v = t.a;  sb.b_v = t.b;
      fork
        sa.start(p_sequencer.sqr_a, this);
        sb.start(p_sequencer.sqr_b, this);
      join                                  // her adim: iki ALU da surulmeden ilerleme yok
    end
  endtask
endclass
class dual_base_test extends uvm_test;
  `uvm_component_utils(dual_base_test)
  dual_alu_env denv;

  function new(string name = "dual_base_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    denv = dual_alu_env::type_id::create("denv", this);
  endfunction

  // Kanca: turetilmis test yalnizca virtual sequence'i secer (Gun 7)
  virtual function uvm_sequence_base make_vseq();
    return dual_independent_vseq::type_id::create("vseq");
  endfunction

  virtual task run_phase(uvm_phase phase);
    uvm_sequence_base vseq = make_vseq();
    phase.raise_objection(this, get_type_name());
    phase.phase_done.set_drain_time(this, 50);
    vseq.start(denv.vsqr);                  // virtual sequencer uzerinde baslar
    phase.drop_objection(this, get_type_name());
  endtask

  virtual function void report_phase(uvm_phase phase);
    uvm_report_server svr = uvm_report_server::get_server();
    super.report_phase(phase);
    if (svr.get_severity_count(UVM_ERROR) == 0 && svr.get_severity_count(UVM_FATAL) == 0)
      `uvm_info("TEST", "*** TEST PASSED ***", UVM_NONE)
    else
      `uvm_info("TEST", "*** TEST FAILED ***", UVM_NONE)
  endfunction
endclass

class dual_lockstep_test extends dual_base_test;
  `uvm_component_utils(dual_lockstep_test)
  function new(string name = "dual_lockstep_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction
  virtual function uvm_sequence_base make_vseq();
    return dual_lockstep_vseq::type_id::create("vseq");
  endfunction
endclass

Kodun Açıklaması

  • **`uvm_declare_p_sequencer(alu_vsequencer):** Sequence'in içine alu_vsequencer p_sequencer; üyesini ve m_set_p_sequencer() metodunu ekler. Sequence start(vsqr) ile başlatıldığında UVM, m_sequencer'ı (tipi uvm_sequencer_base) alu_vsequencer'a $cast edip p_sequencer'a atar; cast başarısız olursa (yanlış sequencer'da başlatıldıysa) `uvm_fatal basar. Böylece p_sequencer.sqr_a yazabilirsiniz; m_sequencer.sqr_a yazamazsınız, çünkü taban tipte sqr_a diye bir alan yoktur.
  • extends uvm_sequence parametresiz: Virtual sequence'in req/rsp'si yoktur; start_item çağırmaz. İtem üretmeyi alt sequence'lere bırakır. Parametre vermek derleme hatası değildir ama "bu sequence'in kendi item'ı var" izlenimi yaratır; vermeyin.
  • rnd.start(p_sequencer.sqr_a, this): Gün 4'teki wr_seq.start(m_sequencer, this) kalıbı; farkı, hedef sequencer'ın m_sequencer (virtual sequencer) değil onun tuttuğu gerçek sequencer olması. İkinci argüman this, alt sequence'in parent'ını virtual sequence yapar; öncelik ve kill() davranışı bu zincirden miras alınır.
  • Lockstep'in iki katmanı: Dış döngüde değerler bir kez üretilir (t.randomize()), iki ayrı alu_single_seq'e kopyalanır ve fork/join ile aynı anda başlatılır. join olmadan (join_none) ikinci adım birinciyi beklemez ve ALU'lar birbirinden kayar; join_any ile yalnızca biri sürülmüş olabilir. Hangi join'in seçildiği senaryonun anlamıdır (SystemVerilog Ders 24).
  • uvm_sequence_base dönüş tipi: Virtual sequence'ler parametresiz olduğu için kanca uvm_sequence_base döner; start() bu tipte tanımlıdır. Test, hangi senaryonun koştuğunu bilmek zorunda değildir.
  • Bitiş: vseq.start(...) alt sequence'lerin hepsi bitene kadar döner; objection yalnızca testtedir (Gün 4 kuralı). Lockstep testinde 50 adım × 2 ALU = 100 işlem, bağımsız testte 100 + 240 işlem gözlenir; her alu_env kendi scoreboard'uyla kendi ALU'sunu denetler.

Lockstep neden önemli? Emniyet kritik sistemlerde (DO-254, ISO 26262) aynı hesabı iki çekirdek paralel yapar ve sonuçlar donanımda karşılaştırılır. Buradaki virtual sequence tam olarak o uyarıcıyı üretir; "iki çekirdek farklı sonuç verirse yakala" kontrolü ise Kendinizi Deneyin #4'te ekleyeceğiniz lockstep checker'ın işidir.

m_sequencer ve p_sequencer

m_sequencer p_sequencer
Kim tanımlar? uvm_sequence_base (her sequence'te var) `uvm_declare_p_sequencer(T) makrosu
Tipi uvm_sequencer_base Sizin verdiğiniz tip (alu_vsequencer)
Ne zaman dolar? start() çağrısında start() çağrısında, m_sequencer'dan $cast ile
Ne işe yarar? Alt sequence'i aynı sequencer'da başlatmak, uvm_config_db bağlamı, log yolu Sequencer'a özgü alanlara (sqr_a, sqr_b, konfigürasyon handle'ları) erişmek
Bedeli Yok Sequence belirli bir sequencer tipine bağlanır; başka sequencer'da başlatılamaz

Kural: alt sequence'leri aynı sequencer'da başlatacaksanız m_sequencer yeterlidir (Gün 4); başka sequencer'lara ulaşacaksanız p_sequencer gerekir (bu ders). Bazı ekipler p_sequencer bağımlılığından kaçınmak için hedef sequencer handle'larını virtual sequence'in alanlarına testten atar; iki yaklaşım da geçerlidir, önemli olan tek standartta kalmaktır.

Lab 8.3: Aynı Sequencer'da Birden Fazla Sequence — Arbitrasyon ve Öncelik

Virtual sequence farklı sequencer'ları koordine eder. Peki aynı sequencer'a iki sequence aynı anda paket göndermek isterse? Sequencer bir hakem (arbiter) gibi davranır: her get_next_item'da bekleyen sequence'lerden birini seçer.

Görev: Gün 7'nin tek ALU ortamında rastgele ve köşe sequence'lerini aynı sequencer'da yarıştırın.

class interleave_test extends alu_base_test;
  `uvm_component_utils(interleave_test)

  function new(string name = "interleave_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual task run_phase(uvm_phase phase);
    alu_random_seq r1 = alu_random_seq::type_id::create("r1");
    alu_corner_seq c1 = alu_corner_seq::type_id::create("c1");
    r1.n_items = 100;
    phase.raise_objection(this, get_type_name());
    phase.phase_done.set_drain_time(this, 50);

    // Varsayilan UVM_SEQ_ARB_FIFO: sira ile (r1, c1, r1, c1, ...) harmanlanir
    env.agent.sqr.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);   // oncelik: buyuk sayi once

    fork
      r1.start(env.agent.sqr, null, 100);   // dusuk oncelik
      c1.start(env.agent.sqr, null, 500);   // yuksek oncelik: kose taramasi bitene kadar r1 bekler
    join

    phase.drop_objection(this, get_type_name());
  endtask
endclass
Arbitrasyon modu Davranış
UVM_SEQ_ARB_FIFO (varsayılan) İstek sırasına göre; öncelik yok sayılır
UVM_SEQ_ARB_WEIGHTED Önceliklerle ağırlıklı rastgele seçim (500'lük sequence 100'lük olandan 5 kat sık)
UVM_SEQ_ARB_RANDOM Eşit olasılıkla rastgele
UVM_SEQ_ARB_STRICT_FIFO En yüksek öncelik her zaman önce; eşitlerde FIFO
UVM_SEQ_ARB_STRICT_RANDOM En yüksek öncelik önce; eşitlerde rastgele
UVM_SEQ_ARB_USER user_priority_arbitration()'ı ezerek kendi kuralınız

(UVM 1.1d'de aynı sabitler SEQ_ARB_FIFO, SEQ_ARB_STRICT_FIFO… adlarını taşır; UVM 1.2 ve 1800.2 UVM_ önekli adları kullanır.)

Kodun Açıklaması

  • start(sqr, parent, priority): Üçüncü argüman önceliktir (-1: parent'tan miras, kökte 100). Öncelik yalnızca WEIGHTED, STRICT_* modlarında anlamlıdır; varsayılan FIFO modunda hiç kullanılmaz; bu, "öncelik verdim ama bir şey değişmedi" şikâyetinin tek sebebidir.
  • Harmanlama neden olur? Her sequence finish_item'da driver'ın item_done'ını bekler; o sırada diğer sequence start_item ile sıraya girer. FIFO modunda sequencer sıradaki isteği verir: r1, c1, r1, c1... Scoreboard her iki akışı da doğru denetler, çünkü predictor gözlenen her işlemi bağımsız değerlendirir.
  • STRICT_FIFO + öncelik: c1'in her isteği r1'inkinden önce seçilir; r1 ancak c1 bittikten sonra ilerler. Harmanlamanın tamamen kaybolması bir hata değildir, istenen davranıştır: "önce köşeleri tara, sonra rastgele doldur".
  • Arbitrasyon sequencer'ın ayarıdır: set_arbitration sequencer üzerinde çağrılır ve o sequencer'a gelen tüm sequence'leri etkiler. Testin ortasında değiştirilebilir.

Lab 8.4: Atomik Diziler — lock() ve grab()

Bazı senaryolar bölünemez: "üç işlemi arka arkaya, arada başkası girmeden sür". FIFO arbitrasyonunda başka bir sequence araya girebilir. Çözüm, sequencer'ı geçici olarak kilitlemektir.

class alu_atomic_seq extends uvm_sequence #(alu_transaction);
  `uvm_object_utils(alu_atomic_seq)

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

  virtual task body();
    lock();                            // sira bize gelince sequencer'i kilitle (kuyruga saygi)
    // grab();                         // alternatif: kuyrugu atla, HEMEN kilitle (acil durum: reset)
    repeat (3) begin
      req = alu_transaction::type_id::create("req");
      start_item(req);
      if (!req.randomize() with { opcode == OP_ADD; })
        `uvm_error("SEQ", "randomize basarisiz")
      finish_item(req);
    end
    unlock();                          // (grab icin ungrab) — UNUTULURSA diger sequence'ler sonsuza dek bekler
  endtask
endclass
lock() / unlock() grab() / ungrab()
Ne zaman kilitlenir? Arbitrasyon sırası bu sequence'e geldiğinde Hemen, kuyruktaki herkesin önüne geçerek (yalnızca mevcut bir kilit/grab'ı bekler)
Tipik kullanım Atomik okuma-değiştir-yazma, burst Reset, hata enjeksiyonu, "şimdi" gereken müdahale
Çıkış unlock() ungrab()
Hiyerarşi Kilitli sequence'in alt sequence'leri de kilitten yararlanır Aynı

Kilit sequence bitince (body dönünce) UVM tarafından otomatik bırakılır; yine de unlock()'u açıkça yazmak hem niyeti belgeler hem de kilidi body'nin ortasında bırakmanıza izin verir. lock()'u bir fork...join_none içinde başlatıp sequence'i kill() ederseniz kilit kalır; atomik sequence'leri öldürmeyin, bitmesini bekleyin.

Sequence Kütüphanesi (İleri Okuma)

Bir sequencer'da "kayıtlı sequence'lerden rastgele N tane koş" demek için uvm_sequence_library vardır:

class alu_seq_lib extends uvm_sequence_library #(alu_transaction);
  `uvm_object_utils(alu_seq_lib)
  `uvm_sequence_library_utils(alu_seq_lib)
  function new(string name = "alu_seq_lib");
    super.new(name);
    init_sequence_library();
  endfunction
endclass

// Her sequence sinifinin icinde (uvm_object_utils'in hemen altina):
//   `uvm_add_to_seq_lib(alu_random_seq, alu_seq_lib)
//   `uvm_add_to_seq_lib(alu_corner_seq, alu_seq_lib)
// Test: lib = alu_seq_lib::type_id::create("lib"); lib.selection_mode = UVM_SEQ_LIB_RAND;
//       lib.min_random_count = 3; lib.max_random_count = 5; lib.start(env.agent.sqr);

Kütüphane, "hangi sequence'lerin hangi sırayla koşacağı" sorusunu da rastgeleleştirir; uzun regresyonlarda senaryo çeşitliliğini ucuza artırır. Küçük ortamlarda virtual sequence + kanca yeterlidir.

Önemli Noktalar

  • Virtual sequence = koordinatör, item üretmez: start_item'ı alt sequence'ler yapar; virtual sequence yalnızca kimin nerede, ne zaman koşacağını söyler.
  • Virtual sequencer = yönlendirme tablosu: Driver'ı yoktur; gerçek sequencer'lara handle tutar, connect_phase'de doldurulur.
  • p_sequencer sequencer tipine bağlar: `uvm_declare_p_sequencer ile gelir; yanlış sequencer'da başlatmak $cast hatasıyla anında görünür. Aynı sequencer'da kalıyorsanız m_sequencer yeterlidir.
  • fork/join seçimi senaryonun anlamıdır: join = ikisi de bitsin, join_any = biri bitince devam, join_none = beklemeden ilerle. Lockstep için join.
  • Blok env'i değişmeden sistemde kullanılır: Bunun bedeli disiplindir: env hiyerarşik yol bilmez, her şeyi config_db ve portlarla alır.
  • Öncelik yalnızca WEIGHTED/STRICT_* modlarında işler: Varsayılan FIFO'da start(..., 500) hiçbir şeyi değiştirmez.
  • lock kibardır, grab kabadır: İkisinde de bırakmayı unutmak ortamı kilitler.
  • Objection hâlâ testtedir: Virtual sequence ne kadar karmaşık olursa olsun, raise/drop testin run_phase'inde kalır (Gün 4).

Sık Yapılan Hatalar

  • Virtual sequence'i gerçek bir sequencer'da başlatmak: vseq.start(env_a.agent.sqr) → p_sequencer cast'i başarısız olur: UVM_FATAL [DCLPSQ] ... Error casting p_sequencer, please verify that this sequence/sequence item is intended to execute on this type of sequencer. Virtual sequence'ler virtual sequencer'da başlar.
  • vsqr.sqr_a atamasını build_phase'de yapmak: env_a.agent.sqr henüz null olabilir; atama connect_phase'de yapılır.
  • Virtual sequencer'a driver bağlamaya çalışmak: seq_item_export'u var ama kimse get_next_item çağırmaz; virtual sequence yanlışlıkla start_item yaparsa sonsuza dek bekler.
  • p_sequencer kullanıp `uvm_declare_p_sequencer yazmamak: "p_sequencer undeclared" derleme hatası.
  • Lockstep'te join_none: Dış döngü beklemeden ilerler, iki ALU'ya giden işlemler kayar; "lockstep" olmaz.
  • set_arbitration çağırmadan öncelik vermek: FIFO'da öncelik yok sayılır; harmanlama değişmez.
  • lock() sonrası unlock() unutmak (özellikle uvm_error sonrası return ile çıkışta): Diğer sequence'ler sonsuza dek bekler, test timeout'a düşer. Çıkış yollarının hepsinde unlock() olmalı.
  • Alt sequence'leri start(sqr) ile (parent'sız) başlatmak: Çalışır; ama öncelik mirası ve hiyerarşik raporlama kaybolur, kill() alt sequence'lere ulaşmaz. İkinci argümanı this verin.

Kendinizi Deneyin

  1. dual_lockstep_vseq'teki join'i join_none yapın. env_a ve env_b'nin scoreboard'ları hâlâ geçiyor mu? Neden geçiyor olmaları "lockstep çalışıyor" anlamına gelmez?
  2. dual_independent_vseq'i env_a.agent.sqr üzerinde başlatın (vseq.start(denv.env_a.agent.sqr)). Hangi mesajı aldınız? Mesaj hangi makrodan geliyor?
  3. interleave_test'te arbitrasyonu sırasıyla FIFO, WEIGHTED, STRICT_FIFO yapın ve driver loglarından ilk 20 işlemin hangi sequence'ten geldiğini sayın. Öncelikler 100/500 iken WEIGHTED modda oran kaça yaklaşıyor?
  4. Lockstep checker: Gün 3 ek bölümündeki `uvm_analysis_imp_decl(_a) / _b tekniğiyle dual_alu_env'e bir lockstep_checker ekleyin: env_a.agent.ap ve env_b.agent.ap'den gelen işlemleri sırayla eşleştirsin; result/flags farklıysa uvm_error bassın. dut_b'nin OP_XOR satırına hata enjekte edip checker'ın yakaladığını gösterin. (Her ALU'nun kendi scoreboard'u da yakalar; checker'ın ek değeri ne?)
  5. alu_atomic_seq'i interleave_test'e üçüncü bir fork dalı olarak ekleyin; driver loglarında üç OP_ADD'ın arada başka işlem olmadan art arda geldiğini doğrulayın. Sonra lock() yerine grab() kullanın: atomik dizi ne zaman başladı?
  6. n_items'ı virtual sequence'e uvm_config_db#(int)::get(m_sequencer, "", "n_items", n_items) ile taşıyın (Gün 4 ek bölümü). Bağlam m_sequencer iken get hangi yola bakıyor? Testten set(this, "denv.vsqr", "n_items", 20) ile ayarlayın.
Hızlı Kontrol: Virtual sequencer'ın driver'ı neden yoktur ve virtual sequence neden start_item çağırmaz?

Virtual sequencer bir koordinasyon noktasıdır: hangi gerçek sequencer'ların var olduğunu bilir, ama hiçbir arayüzü sürmez. Virtual sequence de buna uygun olarak item üretmez; alt sequence'leri p_sequencer.sqr_x üzerinde başlatır ve item'lar o sequencer'ların driver'larına gider. Virtual sequence start_item çağırsaydı, virtual sequencer'da get_next_item diyecek bir driver olmadığı için sonsuza kadar beklerdi.

UVM Gün 8: Sıkça Sorulan Sorular (FAQ)

Soru 1: Virtual sequencer yerine test'te fork ile iki start yapmak gerçekten o kadar kötü mü?

Cevap: Küçük bir ortamda değil. Sorun ölçekte başlar: beş agent, on senaryo, üç test varyantı dediğinizde senaryo mantığı testlere dağılır ve env'in iç yolları her teste sızar. Virtual sequence senaryoyu bir nesneye çevirir: test eder, birleştirir, kütüphaneye koyar, başka testbench'e taşırsınız. Ayrıca sequence'ler arası hiyerarşi (this parent'ı) öncelik ve kill davranışını tutarlı kılar; test'teki fork bunu vermez.

Soru 2: p_sequencer sequence'i sequencer tipine bağlıyor; bu yeniden kullanımı bozmaz mı?

Cevap: Bir miktar bozar; bu bilinçli bir takastır. Alternatif, virtual sequence'e hedef sequencer handle'larını alan olarak koyup testten atamaktır (vseq.sqr_a = denv.env_a.agent.sqr;). O zaman sequence tipten bağımsız kalır, ama her test bu atamaları tekrarlar. Ekipler genellikle "virtual sequence'ler p_sequencer kullanır, alt sequence'ler kullanmaz" kuralında uzlaşır: koordinatör katmanı sequencer'a bağlı olabilir, item üreten katman olmamalıdır.

Soru 3: Lockstep senaryosunda iki ALU gerçekten aynı saat kenarında mı sürülüyor?

Cevap: Aynı fork içinde başlatılan iki alu_single_seq, iki ayrı driver'da aynı anda drive_item'a girer; her driver @(vif.drv_cb) ile bir sonraki kenarı beklediği için iki ALU'ya aynı kenarda sürülür. Ancak bu garanti, iki driver'ın aynı reset'ten çıkmış ve aynı ritimde olmasına dayanır. Biri önceki senaryodan geç kalmışsa (örneğin arbitrasyonda bekliyorsa) kayma olur. Gerçek lockstep doğrulamasında bu yüzden monitor tarafında bir checker (Kendinizi Deneyin #4) zorunludur: "aynı kenarda gönderdim" varsayımı değil, "aynı kenarda gözledim" kanıtı.

Soru 4: Arbitrasyon ile lock() arasındaki fark nedir? İkisi de "kim önce" sorusunu çözmüyor mu?

Cevap: Arbitrasyon her item için ayrı karar verir: bu get_next_item'da hangi sequence'in paketi gidecek? lock() ise kararı bir süreliğine askıya alır: kilit açılana kadar yalnızca kilitleyen sequence (ve onun alt sequence'leri) paket verebilir. Arbitrasyon "adil paylaşım" aracıdır, lock/grab "bölünmezlik" aracı. Birini diğerinin yerine kullanmaya çalışmak (örneğin atomiklik için çok yüksek öncelik vermek) işe yaramaz: yüksek öncelikli sequence finish_item'da beklerken düşük öncelikli olan araya girebilir.

Soru 5: Virtual sequence objection kaldırmalı mı?

Cevap: Hayır; Gün 4'teki kural değişmez. Objection testin run_phase'inde kalır; vseq.start() tüm alt sequence'ler bitene kadar dönmediği için test, start'tan sonra drop_objection diyebilir. Sequence'lerin kendi objection'larını yönetmesi, iç içe yüzlerce sequence'te hem performansı düşürür hem de "kim düşürdü, neden bitti?" sorusunu cevaplanamaz hâle getirir.