EDA Playground'da Dene

Interface ve Modport

Gün 5: Arayüzler, Assertion ve Coverage | Sinyal gruplama, erişim yönü tanımlama, master/slave/monitor

Bu derste SystemVerilog interface yapısını ve modport mekanizmasını öğreneceğiz. Bir veri yolu (bus) protokolünü tek bir arayüz içinde toplayıp, bu arayüze bağlanan modüllerin (master, slave, monitor) sinyallere hangi yönde eriştiğini nasıl tanımlayacağımızı göreceğiz.

Interface Neden Var?

Klasik Verilog'da bir modülün portları tek tek listelenir; aynı sinyal grubu birçok modülde tekrar tekrar yazılır. Tasarıma yeni bir sinyal eklendiğinde, bu sinyali taşıyan her modül başlığını ve her bağlantı satırını elle güncellemek gerekir. Bu hem yorucu hem de hataya açıktır.

interface, birbirine ait sinyalleri tek bir mantıksal birim altında gruplar:

  • Bakımı kolaylaştırır: Protokole yeni bir sinyal eklemek için sadece arayüzü güncellersiniz; bağlantılar olduğu gibi kalır.
  • Bağlantıyı sadeleştirir: Modülleri tek bir arayüz örneği (bif gibi) ile bağlarsınız, onlarca sinyali tek tek bağlamazsınız.
  • Yeniden kullanılabilir: Aynı arayüz tasarımda (RTL) ve doğrulama ortamında (testbench) ortak dil olur.

Modport: Erişim Yönünü Tanımlama

Bir arayüze birden fazla bileşen bağlanır ve her biri aynı sinyali farklı yönde kullanır. Örneğin valid sinyali master için bir çıkış, slave için bir giriştir. İşte modport (module port) tam olarak bu "bakış açısını" tanımlar:

  • master işlemi başlatır: valid, rw, addr, wdata üretir; ready, rdata, resp okur.
  • slave işleme yanıt verir: master'ın çıkışlarını dinler, ready/rdata/resp üretir.
  • monitor hiçbir şey sürmez; tüm sinyalleri yalnızca dinler (hepsi input). Bu, gözlemcinin tasarımı yanlışlıkla etkilemesini önler.

Modport sayesinde derleyici, bir modülün yanlış yönde sinyal sürmesini yakalayabilir ve kod okunabilirliği artar.

Kaynak Kod

// =============================================================================
// GUN 5 - Konu 1: Interface Kavrami ve Modports
// =============================================================================

// Interface tanimi: Sinyalleri gruplar
interface bus_if(input logic clk);
  logic        rst_n;
  logic        valid;
  logic        ready;
  logic        rw;        // 0=read, 1=write
  logic [7:0]  addr;
  logic [31:0] wdata;
  logic [31:0] rdata;
  logic        resp;      // 0=OK, 1=ERROR

  // Modport: Her bilesenin sinyallere erisim yonunu tanimlar
  modport master (
    input  clk, rst_n, ready, rdata, resp,
    output valid, rw, addr, wdata
  );

  modport slave (
    input  clk, rst_n, valid, rw, addr, wdata,
    output ready, rdata, resp
  );

  modport monitor (
    input clk, rst_n, valid, ready, rw, addr, wdata, rdata, resp
  );
endinterface

// Master module
module bus_master(bus_if.master bus);
  int txn_count = 0;

  task write(input logic [7:0] address, input logic [31:0] data);
    @(posedge bus.clk);
    bus.valid <= 1;
    bus.rw    <= 1;
    bus.addr  <= address;
    bus.wdata <= data;

    // Ready sinyalini clock ile senkron bir şekilde bekliyoruz
	// wait ile beklersek ne olurdu??
    do begin
      @(posedge bus.clk);
    end while (!bus.ready);

    bus.valid <= 0;
    txn_count++;
    $display("  [%0t][Master] WRITE: Addr=0x%02h, Data=0x%08h", $time, address, data);
  endtask

  task read(input logic [7:0] address, output logic [31:0] data);
    @(posedge bus.clk);
    bus.valid <= 1;
    bus.rw    <= 0;
    bus.addr  <= address;

    // Ready sinyalini clock ile senkron bir şekilde bekliyoruz
	// wait ile beklersek ne olurdu??
    do begin
      @(posedge bus.clk);
    end while (!bus.ready);

    data = bus.rdata;
    bus.valid <= 0;
    txn_count++;
    $display("  [%0t][Master] READ:  Addr=0x%02h, Data=0x%08h", $time, address, data);
  endtask
endmodule

// Slave module
module bus_slave(bus_if.slave bus);
  logic [31:0] memory [256];

  always @(posedge bus.clk or negedge bus.rst_n) begin
    if (!bus.rst_n) begin
      bus.ready <= 0;
      bus.rdata <= 0;
      bus.resp  <= 0;
    end else begin
      // Default state for ready
      bus.ready <= 0;

      // Process transaction only if valid is high and we are not already ready
      if (bus.valid && !bus.ready) begin
        if (bus.rw) begin  // Write operation
          memory[bus.addr] <= bus.wdata;
          bus.resp <= 0;
        end else begin     // Read operation
          bus.rdata <= memory[bus.addr];
          bus.resp  <= 0;
        end
        
        // Assert ready for one clock cycle to complete the handshake
        bus.ready <= 1;
      end
    end
  end
endmodule

// Monitor modulu
module bus_monitor(bus_if.monitor bus);
  int total_reads = 0, total_writes = 0;

  always @(posedge bus.clk) begin
    if (bus.valid && bus.ready) begin
      if (bus.rw) begin
        total_writes++;
        $display("  [%0t][Monitor] WRITE gozlemlendi: Addr=0x%02h", $time, bus.addr);
      end else begin
        total_reads++;
        $display("  [%0t][Monitor] READ gozlemlendi:  Addr=0x%02h", $time, bus.addr);
      end
    end
  end
endmodule

// Top-level
module interface_modport;
  logic clk = 0;
  always #5 clk = ~clk;

  // Interface ornekleme
  bus_if bif(clk);

  // Modul baglantilari
  bus_master  mst(bif);
  bus_slave   slv(bif);
  bus_monitor mon(bif);

  logic [31:0] read_data;

  initial begin
    $dumpfile("dump.vcd"); $dumpvars;
    $display("=== Interface ve Modport ===\n");

    // Reset
    bif.rst_n = 0;
    #20;
    bif.rst_n = 1;
    #10;

    // Yazma islemleri
    mst.write(8'h10, 32'hDEAD_BEEF);
    mst.write(8'h20, 32'hCAFE_BABE);
    mst.write(8'h30, 32'h1234_5678);

    #20;

    // Okuma islemleri
    mst.read(8'h10, read_data);
    mst.read(8'h20, read_data);

    #50;
    $display("\n  --- Istatistikler ---");
    $display("  Master islem sayisi: %0d", mst.txn_count);
    $display("  Monitor - Read: %0d, Write: %0d", mon.total_reads, mon.total_writes);

    $display("\n=== Interface ve Modport Sonu ===");
    
    $finish;
  end
endmodule

Kodun Açıklaması

  • interface bus_if(input logic clk): Tüm veri yolu sinyallerini (rst_n, valid, ready, rw, addr, wdata, rdata, resp) tek çatı altında toplar. Saat (clk) arayüze port olarak verilir, böylece tüm bağlı modüller aynı saati paylaşır.
  • modport master, modport slave, modport monitor: Aynı sinyal kümesini üç farklı bakış açısıyla sunar. Dikkat edin: valid master'da output, slave'de input olarak görünür; monitor ise her şeyi input alır.
  • bus_master modülü: Portunda bus_if.master bus yazarak yalnızca master görünümünü alır. write ve read task'leri el sıkışmayı yönetir: bus.valid kaldırılır, ardından do ... while (!bus.ready) döngüsüyle slave'in ready sinyali saat kenarına senkron biçimde beklenir. txn_count ile yapılan işlem sayısı tutulur.
  • bus_slave modülü: posedge bus.clk or negedge bus.rst_n ile çalışır. Reset sırasında çıkışları sıfırlar; aksi halde her saatte ready'yi varsayılan olarak 0 yapar, bus.valid && !bus.ready koşulunda işlemi gerçekleştirir. bus.rw 1 ise memory[bus.addr]'a yazar, 0 ise oradan bus.rdata'ya okur ve el sıkışmayı tamamlamak için ready'yi bir saat süresince 1 yapar.
  • bus_monitor modülü: Her saat kenarında bus.valid && bus.ready (başarılı el sıkışma anı) gerçekleştiğinde işlemi total_reads/total_writes sayaçlarıyla kayıt altına alır; hiçbir sinyali sürmez.
  • interface_modport (top): clk üretir, bus_if bif(clk) ile arayüzü örnekler ve mst, slv, mon modüllerini aynı bif örneğiyle bağlar. Reset uygulanır, üç yazma ve iki okuma işlemi yürütülür, sonunda istatistikler basılır.

Önemli Noktalar

  • Modport yönü doğrulukları: Bir sinyali yanlış modportta output ilan etmek derleme hatası ya da sürüş çakışması yaratır. Master/slave'in karşıt yönleri tutarlı olmalıdır.
  • El sıkışmayı wait yerine saat kenarıyla bekleyin: Kodda do begin @(posedge bus.clk); end while (!bus.ready) kullanılması bilinçli bir tercihtir. wait(bus.ready) ile beklemek, ready aynı delta zaman diliminde dalgalanırsa yarış (race) ve yanlış senkronizasyona yol açabilir.
  • Monitor pasif kalmalı: Gözlemci modport'unun tüm sinyalleri input olmalı; aksi halde monitör DUT davranışını istemeden değiştirebilir.
  • Tek arayüz, çok bağlantı: Tasarıma sinyal eklemek istediğinizde yalnızca bus_if'i güncellemeniz yeterlidir; modül bağlantılarını değiştirmeniz gerekmez. İşin gücü buradadır.
  • Non-blocking atama kullanımı: Sıralı mantıkta (always @(posedge clk)) <= kullanılması, simülasyon yarışlarını engeller ve gerçek donanım davranışını yansıtır.