Lab 4: IPC Veri Akışı
Gün 4: Süreçler Arası İletişim (IPC) | Generator-Driver-Monitor arası Mailbox ile veri akışı
Bu lab, Gün 4 boyunca öğrendiğiniz tüm IPC kavramlarını (mailbox, event, fork...join) bir araya getirir. Gerçek bir doğrulama ortamının çekirdeğini oluşturan Generator → Driver → Monitor zincirini, bileşenler arasında mailbox kullanarak baştan kuracaksınız.
Lab'in Amacı ve Mimarisi
Modern testbench'ler katmanlı (layered) bir mimari kullanır. Bu lab, o mimarinin en temel veri akışını modeller:
- Generator (Üretici): Rastgele
Transactionnesneleri üretir ve bir mailbox üzerinden Driver'a gönderir. - Driver (Sürücü): Transaction'ları alır, "sürer" (DUT'a uygulamayı simüle eder) ve ikinci bir mailbox ile Monitor'e iletir.
- Monitor (İzleyici): Sürülen transaction'ları gözlemler ve sayar.
Bu üç bileşen fork ile paralel çalışır ve aralarındaki bağlantı iki mailbox ile kurulur. Bu kalıbın önemli yanları:
- Gevşek bağlama (loose coupling): Bileşenler birbirini doğrudan çağırmaz; yalnızca mailbox üzerinden haberleşir. Bu, her bileşenin bağımsız geliştirilip test edilmesini sağlar.
- Otomatik senkronizasyon: Mailbox'ın bloklayıcı
get'i sayesinde Driver, veri gelene kadar bekler; süreçleri elle senkronize etmeye gerek kalmaz. - Bitiş kontrolü: Generator işini bitirince bir
eventtetikler; ana süreç bu olayıwait(...triggered)ile bekleyerek pipeline'ın güvenle boşalmasını sağlar.
Bu yapı, ileride öğreneceğiniz UVM'deki uvm_sequencer, uvm_driver ve uvm_monitor mimarisinin sadeleştirilmiş bir önizlemesidir.
Veri Akışı Şeması
┌───────────┐ gen2drv ┌──────────┐ drv2mon ┌───────────┐
│ Generator │ ─mailbox─▶ │ Driver │ ─mailbox─▶ │ Monitor │
│ randomize │ │ #10 sür │ │ say & bas │
└───────────┘ └──────────┘ └───────────┘
│ -> done (event)
▼
wait(gen.done.triggered) → #100 → istatistik → $finish
Her ok tek yönlü bir mailbox'tır; her kutu fork içinde paralel koşan bir run() task'ıdır. Generator işini bitirdiğinde done olayını tetikler; Driver ve Monitor ise forever döngüsünde yaşar.
Adım Adım Görev Listesi
- Transaction:
randalanlar (addr,data,rw),static countile artanid,display()vecopy()metotları. - Generator: Kurucuda mailbox ve adet alsın;
run()içinde her turda yeni nesne yaratıprandomize()+put()yapsın; bitince-> done. - Driver: İki mailbox (giriş/çıkış).
foreverdöngüsündeget→drive()(zaman tüketir) →put. - Monitor: Tek mailbox;
forever getve sayaç. - Top: Mailbox'ları bir kez yaratıp aynı handle'ları bileşenlere verin;
fork...join_any,wait(gen.done.triggered), boşaltma süresi, istatistik.
Kontrol noktası: Çıktıdaki
UretilenveIzlenensayıları eşit olmalı. Eşit değilse ya boşaltma süresi (#100) kısa kalmıştır ya da bir mailbox handle'ı yanlış bağlanmıştır.
Kaynak Kod
// =============================================================================
// GUN 4 - Lab 4: Generator-Driver-Monitor Veri Akisi (Mailbox ile)
// =============================================================================
class Transaction;
rand logic [7:0] addr;
rand logic [31:0] data;
rand bit rw; // 0=read, 1=write
int id;
static int count = 0;
constraint c_valid {
addr inside {[8'h00:8'hFF]};
}
function new();
id = count++;
endfunction
function void display(string prefix = "");
$display(" %sTXN#%0d | %s | Addr=0x%02h | Data=0x%08h",
prefix, id, rw ? "WR" : "RD", addr, data);
endfunction
function Transaction copy();
Transaction t = new();
t.addr = this.addr;
t.data = this.data;
t.rw = this.rw;
t.id = this.id;
return t;
endfunction
endclass
class Generator;
mailbox #(Transaction) gen2drv;
int num_txn;
event done;
function new(mailbox #(Transaction) mbx, int num = 10);
this.gen2drv = mbx;
this.num_txn = num;
endfunction
task run();
Transaction txn;
$display("\n [Generator] Baslatildi (%0d islem uretilecek)", num_txn);
for (int i = 0; i < num_txn; i++) begin
txn = new();
assert(txn.randomize()) else $fatal(1, "Randomize hata!");
$display(" [%0t][Generator] Uretildi:", $time);
txn.display(" ");
gen2drv.put(txn);
#5;
end
$display(" [%0t][Generator] Tum islemler uretildi", $time);
-> done;
endtask
endclass
class Driver;
mailbox #(Transaction) gen2drv;
mailbox #(Transaction) drv2mon;
function new(mailbox #(Transaction) in_mbx, mailbox #(Transaction) out_mbx);
this.gen2drv = in_mbx;
this.drv2mon = out_mbx;
endfunction
task run();
Transaction txn;
$display(" [Driver] Baslatildi");
forever begin
gen2drv.get(txn);
drive(txn);
drv2mon.put(txn); // Monitor'e gonder
end
endtask
task drive(Transaction txn);
$display(" [%0t][Driver] Suruluyor:", $time);
txn.display(" ");
#10; // Surme suresi
$display(" [%0t][Driver] Tamamlandi: TXN#%0d", $time, txn.id);
endtask
endclass
class Monitor;
mailbox #(Transaction) drv2mon;
int monitored_count = 0;
function new(mailbox #(Transaction) mbx);
this.drv2mon = mbx;
endfunction
task run();
Transaction txn;
$display(" [Monitor] Baslatildi");
forever begin
drv2mon.get(txn);
monitored_count++;
$display(" [%0t][Monitor] Izlendi (#%0d):", $time, monitored_count);
txn.display(" ");
end
endtask
endclass
module lab4_ipc_veri_akisi;
initial begin
mailbox #(Transaction) gen2drv_mbx = new();
mailbox #(Transaction) drv2mon_mbx = new();
Generator gen = new(gen2drv_mbx, 8);
Driver drv = new(gen2drv_mbx, drv2mon_mbx);
Monitor mon = new(drv2mon_mbx);
$display("============================================================");
$display(" LAB 4: Generator -> Driver -> Monitor Veri Akisi");
$display("============================================================");
fork
gen.run();
drv.run();
mon.run();
join_any
// Generator bitene kadar bekle
wait(gen.done.triggered);
#100; // Pipeline'in bosalmasini bekle
$display("\n --- Istatistikler ---");
$display(" Uretilen : %0d", gen.num_txn);
$display(" Izlenen : %0d", mon.monitored_count);
$display("\n============================================================");
$display(" LAB 4 TAMAMLANDI");
$display("============================================================");
$finish;
end
endmodule
Kodun Açıklaması
Transactionsınıfı:randnitelikliaddr,dataverwalanları içerir;c_validkısıtı (constraint) adresi8'h00–8'hFFaralığında tutar.static int countveid = count++ile her transaction'a benzersiz, artan bir kimlik verilir.copy()metodu transaction'ın derin (alanların elle kopyalandığı) bir kopyasını döndürür.Generatorsınıfı: Yapıcısında birgen2drvmailbox'ı ve üretilecek işlem sayısı (num_txn) alır.run()görevi her transaction'ırandomize()ile rastgeleleştirir (başarısızsa$fatalile durur),gen2drv.put(txn)ile gönderir ve aralarda#5bekler. Tüm üretim bitince-> done;ile bitiş olayını tetikler.Driversınıfı: İki mailbox tutar: girişigen2drv, çıkışıdrv2mon.run()içindekiforeverdöngüsügen2drv.get(txn)ile bir transaction alır (yoksa bloklanır),drive()ile#10boyunca sürer vedrv2mon.put(txn)ile Monitor'e iletir.Monitorsınıfı:drv2monmailbox'ındanforeverdöngüsüyle transaction alır,monitored_count'u artırır ve gözlemlediğini yazdırır.lab4_ipc_veri_akisimodülü: İki mailbox (gen2drv_mbx,drv2mon_mbx) oluşturulur ve aynı mailbox referansları ilgili bileşenlere geçirilerek zincir kurulur.fork ... join_anyile üç bileşen paralel başlatılır. Driver ve Monitorforeverdöngüsünde olduğundan asla kendiliğinden bitmez; bu yüzdenjoin_any, Generator bittiğinde ana süreci serbest bırakır. Ardındanwait(gen.done.triggered)Generator'ın bitişini kesin olarak garantiler ve#100ile pipeline'daki kalan transaction'ların işlenmesi beklenir. Son olarak üretilen ve izlenen sayılar karşılaştırmalı istatistik olarak yazdırılır.
Önemli Noktalar
- Bileşenler aynı mailbox referansını paylaşmalıdır; bu yüzden mailbox'lar modülde bir kez oluşturulup yapıcılara (
new) parametre olarak geçirilir. Mailbox'lar nesne (handle) olduğundan kopyalanmaz, referansla paylaşılır. DriverveMonitor'ınforeverdöngüleri hiç bitmediği içinjoinyerinejoin_anykullanılır; aksi halde simülasyon sonsuza kadar beklerdi.- Generator'ın bitişini yakalamak için
event done+wait(gen.done.triggered)kullanılır;.triggeredsayesinde olay zaten tetiklenmiş olsa bile güvenle algılanır. join_anysonrası#100gecikmesi kritiktir: pipeline'da hâlâ işlenen transaction'ların Monitor'e ulaşması için zaman tanır. Bu olmadan üretilen/izlenen sayıları tutmayabilir.randomize()çağrısıassertiçinde kontrol edilir; başarısızlık durumunda$fatalile erken ve net biçimde durulur. Rastgeleleştirmenin sessizce başarısız olmasına asla izin vermeyin.- Bu Generator-Driver-Monitor zinciri, gevşek bağlamalı ve yeniden kullanılabilir doğrulama bileşenleri yazmanın temel kalıbıdır; UVM'e geçişte bu yapı doğrudan karşılığını bulur.
- İnce bir nokta —
copy()ve statik sayaç:copy()içindekiTransaction t = new();satırı constructor'ı çalıştırır vecount'u bir artırır; ardındant.id = this.idile kimlik eski değere döndürülür. Sonuç: her kopya alındığındacountbir sonraki yeni transaction için bir atlar (0, 1, 2, 4...). Bunu önlemek için yanew()'e "kimlik üretme" bayrağı eklenir ya da sayaç yalnızca generator'da artırılır. Ek görevlerde düzelteceksiniz. c_validkısıtı (addr inside {[8'h00:8'hFF]}) 8-bit bir alan için her değeri kapsar, yani etkisizdir. Burada yalnızca söz dizimini hatırlatmak için bırakılmıştır; gerçek bir projede anlamlı bir aralık (örneğinaddr[1:0] == 0ile kelime hizalı adres) yazmalısınız.#100boşaltma süresi "sihirli sayı"dır: 8 transaction ×#10sürme süresi için yeter ama adet 50 olursa yetmez. Daha sağlam yol, Monitor'ün sayacını beklemektir:wait(mon.monitored_count == gen.num_txn);. Bu, Lab 5'te kullanılan yaklaşımdır.
UVM'e Köprü
| Bu lab'daki yapı | UVM karşılığı |
|---|---|
Transaction sınıfı |
uvm_sequence_item |
Generator.run() |
uvm_sequence::body() |
gen2drv mailbox |
uvm_sequencer + seq_item_port (get_next_item/item_done) |
Driver.run() |
uvm_driver::run_phase() |
drv2mon mailbox |
Gerçek projede yok: monitor DUT pinlerini izler, driver'dan veri almaz |
Monitor.run() |
uvm_monitor::run_phase() + uvm_analysis_port |
fork gen/drv/mon join_any |
UVM fazlama (run_phase tüm bileşenlerde paralel) |
-> done + wait |
raise_objection / drop_objection |
Tablodaki en önemli satır drv2mon'dur: bu lab'da monitor driver'dan beslenir çünkü henüz bir DUT ve interface yoktur. Gün 5'ten itibaren monitor gerçek sinyalleri gözleyecek; driver ile monitor arasındaki tek bağ DUT olacaktır.
Ek Görevler (Zorluk Artan)
- Kolay:
#100yerinewait(mon.monitored_count == gen.num_txn);kullanın vegen = new(gen2drv_mbx, 50)ile 50 transaction'da da sayıların tuttuğunu gösterin. - Orta:
gen2drv_mbx'i sınırlı yapın (new(2)) ve Generator'ınput'ta beklediği anları zaman damgalarıyla gösterin. Driver'ın#10'unu#2yapınca ne değişiyor? - Orta:
copy()'nincount'u atlatma hatasını düzeltin:new()'ebit assign_id = 1argümanı ekleyipcopy()içindenew(0)çağırın. - Zor: Basit bir Scoreboard ekleyin: Driver sürdüğü transaction'ın kopyasını
drv2scbmailbox'ına, Monitor gözlediğinimon2scb'ye koysun; Scoreboard ikisinigetedipaddr/data/rwalanlarını karşılaştırsın ve PASS/FAIL saysın. (Bu, Gün 7'de yapacağınız işin ön provasıdır.)
Hızlı Kontrol: Neden fork ... join yerine join_any kullanıldı?
Driver ve Monitor forever döngüsünde olduğu için asla bitmez; join tüm süreçlerin bitmesini beklediğinden simülasyon sonsuza kadar sürerdi. join_any, Generator bittiği anda ana süreci serbest bırakır; ardından boşaltma süresi ve istatistik gelir.