EDA Playground'da Dene

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 Transaction nesneleri ü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 event tetikler; 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

  1. Transaction: rand alanlar (addr, data, rw), static count ile artan id, display() ve copy() metotları.
  2. Generator: Kurucuda mailbox ve adet alsın; run() içinde her turda yeni nesne yaratıp randomize() + put() yapsın; bitince -> done.
  3. Driver: İki mailbox (giriş/çıkış). forever döngüsünde get → drive() (zaman tüketir) → put.
  4. Monitor: Tek mailbox; forever get ve sayaç.
  5. 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 Uretilen ve Izlenen sayı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ı

  • Transaction sınıfı: rand nitelikli addr, data ve rw alanları içerir; c_valid kısıtı (constraint) adresi 8'h00–8'hFF aralığında tutar. static int count ve id = 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.
  • Generator sınıfı: Yapıcısında bir gen2drv mailbox'ı ve üretilecek işlem sayısı (num_txn) alır. run() görevi her transaction'ı randomize() ile rastgeleleştirir (başarısızsa $fatal ile durur), gen2drv.put(txn) ile gönderir ve aralarda #5 bekler. Tüm üretim bitince -> done; ile bitiş olayını tetikler.
  • Driver sınıfı: İki mailbox tutar: girişi gen2drv, çıkışı drv2mon. run() içindeki forever döngüsü gen2drv.get(txn) ile bir transaction alır (yoksa bloklanır), drive() ile #10 boyunca sürer ve drv2mon.put(txn) ile Monitor'e iletir.
  • Monitor sınıfı: drv2mon mailbox'ından forever döngüsüyle transaction alır, monitored_count'u artırır ve gözlemlediğini yazdırır.
  • lab4_ipc_veri_akisi modülü: İki mailbox (gen2drv_mbx, drv2mon_mbx) oluşturulur ve aynı mailbox referansları ilgili bileşenlere geçirilerek zincir kurulur. fork ... join_any ile üç bileşen paralel başlatılır. Driver ve Monitor forever döngüsünde olduğundan asla kendiliğinden bitmez; bu yüzden join_any, Generator bittiğinde ana süreci serbest bırakır. Ardından wait(gen.done.triggered) Generator'ın bitişini kesin olarak garantiler ve #100 ile 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.
  • Driver ve Monitor'ın forever döngüleri hiç bitmediği için join yerine join_any kullanı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; .triggered sayesinde olay zaten tetiklenmiş olsa bile güvenle algılanır.
  • join_any sonrası #100 gecikmesi 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ı assert içinde kontrol edilir; başarısızlık durumunda $fatal ile 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çindeki Transaction t = new(); satırı constructor'ı çalıştırır ve count'u bir artırır; ardından t.id = this.id ile kimlik eski değere döndürülür. Sonuç: her kopya alındığında count bir sonraki yeni transaction için bir atlar (0, 1, 2, 4...). Bunu önlemek için ya new()'e "kimlik üretme" bayrağı eklenir ya da sayaç yalnızca generator'da artırılır. Ek görevlerde düzelteceksiniz.
  • c_valid kı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ğin addr[1:0] == 0 ile kelime hizalı adres) yazmalısınız.
  • #100 boşaltma süresi "sihirli sayı"dır: 8 transaction × #10 sü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)

  1. Kolay: #100 yerine wait(mon.monitored_count == gen.num_txn); kullanın ve gen = new(gen2drv_mbx, 50) ile 50 transaction'da da sayıların tuttuğunu gösterin.
  2. Orta: gen2drv_mbx'i sınırlı yapın (new(2)) ve Generator'ın put'ta beklediği anları zaman damgalarıyla gösterin. Driver'ın #10'unu #2 yapınca ne değişiyor?
  3. Orta: copy()'nin count'u atlatma hatasını düzeltin: new()'e bit assign_id = 1 argümanı ekleyip copy() içinde new(0) çağırın.
  4. Zor: Basit bir Scoreboard ekleyin: Driver sürdüğü transaction'ın kopyasını drv2scb mailbox'ına, Monitor gözlediğini mon2scb'ye koysun; Scoreboard ikisini get edip addr/data/rw alanları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.