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.

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'h008'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.