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_sequencerve`uvm_declare_p_sequencer,fork/joinile 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...joinvejoin_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_envdeğ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ızcauvm_config_dbyolları ve portlar üzerinden tanımasıyla mümkündür; env'in içindetb_top.aifgibi bir hiyerarşik referans olsaydı iki kez kullanılamazdı (Gün 6, FAQ #1).uvm_sequencerparametresiz: Virtual sequencer hiçbir transaction tipi taşımaz;uvm_sequencer'ın varsayılan#(uvm_sequence_item)parametresi yeterlidir. İçineuvm_sequencertürevleri yerineuvm_sequence_itemkoyarsanı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_phasesırasındaenv_a.agent.sqrhenü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çinealu_vsequencer p_sequencer;üyesini vem_set_p_sequencer()metodunu ekler. Sequencestart(vsqr)ile başlatıldığında UVM,m_sequencer'ı (tipiuvm_sequencer_base)alu_vsequencer'a$castedipp_sequencer'a atar; cast başarısız olursa (yanlış sequencer'da başlatıldıysa)`uvm_fatalbasar. Böylecep_sequencer.sqr_ayazabilirsiniz;m_sequencer.sqr_ayazamazsınız, çünkü taban tiptesqr_adiye bir alan yoktur. extends uvm_sequenceparametresiz: Virtual sequence'inreq/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'tekiwr_seq.start(m_sequencer, this)kalıbı; farkı, hedef sequencer'ınm_sequencer(virtual sequencer) değil onun tuttuğu gerçek sequencer olması. İkinci argümanthis, alt sequence'in parent'ını virtual sequence yapar; öncelik vekill()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 vefork/joinile aynı anda başlatılır.joinolmadan (join_none) ikinci adım birinciyi beklemez ve ALU'lar birbirinden kayar;join_anyile yalnızca biri sürülmüş olabilir. Hangijoin'in seçildiği senaryonun anlamıdır (SystemVerilog Ders 24). uvm_sequence_basedönüş tipi: Virtual sequence'ler parametresiz olduğu için kancauvm_sequence_basedö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; heralu_envkendi 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ızcaWEIGHTED,STRICT_*modlarında anlamlıdır; varsayılanFIFOmodunda 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'ınitem_done'ını bekler; o sırada diğer sequencestart_itemile 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_arbitrationsequencer ü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_sequencersequencer tipine bağlar:`uvm_declare_p_sequencerile gelir; yanlış sequencer'da başlatmak$casthatasıyla anında görünür. Aynı sequencer'da kalıyorsanızm_sequenceryeterlidir.fork/joinseçimi senaryonun anlamıdır:join= ikisi de bitsin,join_any= biri bitince devam,join_none= beklemeden ilerle. Lockstep içinjoin.- 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ılanFIFO'dastart(..., 500)hiçbir şeyi değiştirmez. lockkibardır,grabkabadır: İkisinde de bırakmayı unutmak ortamı kilitler.- Objection hâlâ testtedir: Virtual sequence ne kadar karmaşık olursa olsun,
raise/droptestinrun_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_sequencercast'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_aatamasınıbuild_phase'de yapmak:env_a.agent.sqrhenüznullolabilir; atamaconnect_phase'de yapılır.- Virtual sequencer'a driver bağlamaya çalışmak:
seq_item_export'u var ama kimseget_next_itemçağırmaz; virtual sequence yanlışlıklastart_itemyaparsa sonsuza dek bekler. p_sequencerkullanıp`uvm_declare_p_sequenceryazmamak: "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 (özellikleuvm_errorsonrasıreturnile çıkışta): Diğer sequence'ler sonsuza dek bekler, test timeout'a düşer. Çıkış yollarının hepsindeunlock()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ıthisverin.
Kendinizi Deneyin
dual_lockstep_vseq'tekijoin'ijoin_noneyapın.env_aveenv_b'nin scoreboard'ları hâlâ geçiyor mu? Neden geçiyor olmaları "lockstep çalışıyor" anlamına gelmez?dual_independent_vseq'ienv_a.agent.sqrüzerinde başlatın (vseq.start(denv.env_a.agent.sqr)). Hangi mesajı aldınız? Mesaj hangi makrodan geliyor?interleave_test'te arbitrasyonu sırasıylaFIFO,WEIGHTED,STRICT_FIFOyapın ve driver loglarından ilk 20 işlemin hangi sequence'ten geldiğini sayın. Öncelikler 100/500 ikenWEIGHTEDmodda oran kaça yaklaşıyor?- Lockstep checker: Gün 3 ek bölümündeki
`uvm_analysis_imp_decl(_a)/_btekniğiyledual_alu_env'e birlockstep_checkerekleyin:env_a.agent.apveenv_b.agent.ap'den gelen işlemleri sırayla eşleştirsin;result/flagsfarklıysauvm_errorbassın.dut_b'ninOP_XORsatı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?) alu_atomic_seq'iinterleave_test'e üçüncü birforkdalı olarak ekleyin; driver loglarında üçOP_ADD'ın arada başka işlem olmadan art arda geldiğini doğrulayın. Sonralock()yerinegrab()kullanın: atomik dizi ne zaman başladı?n_items'ı virtual sequence'euvm_config_db#(int)::get(m_sequencer, "", "n_items", n_items)ile taşıyın (Gün 4 ek bölümü). Bağlamm_sequencerikengethangi yola bakıyor? Testtenset(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.