EDA Playground'da Dene

Monitor Modülü

Gün 7: Bitirme Projesi - Bölüm 2 | DUT çıkışlarını izleyen ve paketleyen modül

Monitor (izleyici), DUT'un arayüzündeki sinyal hareketlerini gözleyip tekrar transaction nesnelerine paketleyen pasif bileşendir. ALU mimarimizde driver'ın tersi yönde çalışır: pin seviyesinden transaction seviyesine geçiş yapar ve sonucu scoreboard'a iletir.

Monitor ve Mimarideki Yeri

Monitor doğrulamanın "gözüdür". Görevi yalnızca izlemektir; DUT'a hiçbir sinyal sürmez. Bu yüzden virtual alu_if.monitor modport'unu kullanır ki bu modport'ta tüm sinyaller input'tur.

İletişim akışı şöyledir:

  • Monitor, vif.mon_cb clocking block'unu her saat kenarında örnekler.
  • out_valid yüksek olduğunda DUT geçerli bir sonuç üretmiş demektir.
  • Bu anda gözlenen giriş ve çıkış değerleri yeni bir ALU_Transaction'a paketlenir ve mon2scb mailbox'ı ile scoreboard'a gönderilir.

Neden Ayrı Bir Monitor?

Driver "ne uyguladığımızı" bilirken, monitor "DUT'un gerçekte ne yaptığını" bağımsızca gözler. Scoreboard bu iki bağımsız kaynağı (driver kopyası vs. monitor gözlemi) karşılaştırarak DUT'un doğruluğunu test eder. İzlemenin sürüşten ayrılması, doğrulamanın güvenilirliğini artırır.

Kaynak Kod

// =============================================================================
// GUN 7 - Konu 1: Monitor - Arayuzdeki Hareketleri Izleyen Modul
// =============================================================================

class ALU_Monitor;
  virtual alu_if.monitor vif;
  mailbox #(ALU_Transaction) mon2scb;
  int monitored_count = 0;

  function new(virtual alu_if.monitor vif, mailbox #(ALU_Transaction) mbx);
    this.vif     = vif;
    this.mon2scb = mbx;
  endfunction

  task run();
    ALU_Transaction txn;
    $display("[Monitor] Baslatildi - DUT cikislari izleniyor");

    forever begin
      @(vif.mon_cb);
      if (vif.mon_cb.out_valid) begin
        txn = new();
        txn.operand_a = vif.mon_cb.operand_a;
        txn.operand_b = vif.mon_cb.operand_b;
        txn.opcode    = ALU_Transaction::opcode_e'(vif.mon_cb.opcode);
        txn.result    = vif.mon_cb.result;
        txn.flags     = vif.mon_cb.flags;
        txn.out_valid = 1;
        
        mon2scb.put(txn);
        monitored_count++;
      end
    end
  endtask
endclass

Kodun Açıklaması

  • Üyeler: virtual alu_if.monitor vif salt-okuma erişim sağlar; mon2scb gözlenen transaction'ları scoreboard'a taşır; monitored_count izlenen işlem sayısını tutar.
  • new(...): Virtual interface ve mailbox'ı dışarıdan alır.
  • run(): Sonsuz döngüde her @(vif.mon_cb) saat kenarında örnek alır.
  • if (vif.mon_cb.out_valid): Yalnızca DUT geçerli bir çıkış işaretlediğinde paketleme yapılır; geçersiz çevrimler atlanır.
  • Transaction paketleme: Gözlenen operand_a, operand_b, opcode, result, flags değerleri yeni bir txn'e kopyalanır. opcode, ALU_Transaction::opcode_e'(...) ile enum tipine dönüştürülür. out_valid alanı 1 olarak işaretlenir.
  • mon2scb.put(txn) ve monitored_count++: Paketlenen transaction scoreboard'a gönderilir ve sayaç artırılır.

Önemli Noktalar

  • Monitor tamamen pasiftir; mon_cb modport'u sayesinde hiçbir sinyale yazamaz, bu da yanlışlıkla DUT'u uyarmayı imkânsız kılar.
  • out_valid filtresi kritiktir: Yalnızca geçerli çevrimleri toplamak, scoreboard'da driver ile monitor akışlarının hizalı kalmasını sağlar.
  • opcode enum dönüşümü (opcode_e'(...)) yapılmadan ham logic değer scoreboard'da name() gibi enum metotlarıyla doğru yorumlanamaz.
  • DUT'un bir çevrimlik pipeline gecikmesi monitorün doğru zamanda örnekleme yapmasını gerektirir; clocking block bu hizalamayı otomatik sağlar.
  • run() sonsuz döngüdür; durması environment'ın yaşam döngüsü yönetimine bırakılmıştır.
  • Monitor operandları da out_valid anında okur. Driver in_valid'i düşürdükten sonra operandları değiştirmediği için (sonraki transaction bir çevrim sonra gelir), out_valid=1 olan kenarda operand_a/b ve opcode hâlâ o işleme aittir. Bu "şans" değil, driver ile monitor arasındaki zamanlama sözleşmesidir; driver back-to-back sürerse bu sözleşme bozulur ve monitor operandları bir sonraki işlemden okur. Ek görevlerde bunu kıracaksınız.
  • Monitor neden copy() göndermez? Çünkü her out_valid'de new() ile yeni bir nesne yaratır; kimseyle paylaşmaz. Driver'daki copy() ihtiyacı, nesnenin generator'dan gelmesi ve başka yerlerde de tutulmasıydı.
  • monitored_count ile driven_count eşit olmalıdır. Fark varsa ya monitor bir out_valid kaçırmıştır (skew/zamanlama) ya da DUT beklenmedik ekstra out_valid üretmiştir; bu eşitlik environment raporundaki ilk sağlık kontrolüdür.

Driver ve Monitor: Simetrik İkili

Driver Monitor
Yön Transaction → pinler Pinler → transaction
Modport alu_if.driver (sürer) alu_if.monitor (yalnızca okur)
Clocking block drv_cb (output + input) mon_cb (yalnızca input)
Zaman kaynağı Mailbox'tan gelen transaction Her saat kenarı (@(vif.mon_cb))
Tetikleyici gen2drv.get() out_valid == 1
Çıkış drv2scb.put(copy) (beklenen) mon2scb.put(new) (gözlenen)
Bilmesi gereken Giriş protokolü (in_valid el sıkışması) Çıkış protokolü (out_valid anlamı)
Bilmemesi gereken Doğru sonucun ne olduğu Driver'ın ne gönderdiği

Son iki satır kritiktir: monitor driver'dan bağımsız olmalıdır. Monitor "driver şunu gönderdi, demek ki bu çıkış ona ait" diye düşünmez; yalnızca pinlerde gördüğünü raporlar. Bağımsızlık, aynı hatanın iki yerde birden yapılıp gizlenmesini önler.

Benzetme: Driver bir restoranda garsondur: siparişi mutfağa (DUT) iletir. Monitor ise mutfaktan çıkan tabakları fotoğraflayan bağımsız bir denetçidir; garsonun ne söylediğiyle ilgilenmez, yalnızca tabakta ne olduğunu kaydeder. Scoreboard (hakem) sipariş fişi ile fotoğrafı karşılaştırır.

Sık Yapılan Hatalar

  • out_valid filtresini kaldırmak: Monitor her çevrim transaction üretir; scoreboard'daki mon2scb.get() driver'dan gelen beklenenle hizasını kaybeder ve her şey "uyuşmazlık" olur.
  • if (vif.mon_cb.out_valid == 1'b1) yerine === kullanmamak: Reset öncesi out_valid X olabilir; == ile X karşılaştırması X üretir ve if bunu yanlış sayar — burada zararsız, ama != 0 yazsaydınız X yanlış pozitif verirdi. Gün 5'teki alışkanlığı sürdürün: === 1'b1.
  • Ham sinyali okumak (vif.out_valid): Clocking block dışından okunan değer kenar anındaki NBA güncellemelerinden etkilenir; mon_cb.out_valid ise Preponed bölgede örneklenmiş, kararlı değerdir.
  • Enum dönüşümünü unutmak: txn.opcode = vif.mon_cb.opcode; bazı araçlarda uyarı, bazılarında hatadır; opcode_e'(...) cast'i hem taşınabilirliği hem de name()'in doğru çalışmasını sağlar.
  • Monitor'ü join_none ile başlatıp disable fork ile öldürmek: Gün 4'teki kapsam tuzağı; environment'ta timeout kalıbı kullanıyorsanız izole fork...join sarmalı gerekir.

Kendinizi Deneyin

  1. if (vif.mon_cb.out_valid) satırını if (1) yapın ve Gün 7'de final testbench'i çalıştırın: scoreboard kaç hata verdi, neden?
  2. Monitor'e int idle_cycles = 0; sayacı ekleyin; out_valid=0 olan her çevrimi sayın ve raporda basın. Driver'ın 2 çevrimlik ritminde idle_cycles ≈ monitored_count çıkması gerekir; açıklayın.
  3. Driver'ı back-to-back sürecek şekilde değiştirin (Ders 39, ek görev 1). Monitor'ün operandları hangi işlemden okuduğunu scoreboard hatalarından çıkarın. Sonra monitor'ü, operandları in_valid=1 olan kenarda yakalayıp bir çevrim sonra result ile birleştirecek şekilde (küçük bir pipeline) düzeltin.
  4. Monitor'e virtual alu_if.driver vif vermeyi deneyin. Hangi satır derlenmiyor? Bu hata neden "iyi bir hata"dır?
Hızlı Kontrol: Monitor, operandları neden out_valid anında okuyabiliyor; bu her DUT için geçerli midir?

Bu projede driver operandları in_valid düştükten sonra da bir çevrim daha pinlerde tuttuğu için out_valid kenarında hâlâ o işleme ait değerler okunur. Bu, driver ile monitor arasındaki zamanlama sözleşmesidir, genel bir kural değildir. Pipeline'ı daha derin bir DUT'ta ya da back-to-back sürüşte monitor, girişleri in_valid anında yakalayıp çıkışla eşleştirmek (kendi içinde kuyruklamak) zorundadır.