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 (
bifgibi) 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:
masterişlemi başlatır:valid,rw,addr,wdataüretir;ready,rdata,respokur.slaveişleme yanıt verir: master'ın çıkışlarını dinler,ready/rdata/respüretir.monitorhiçbir şey sürmez; tüm sinyalleri yalnızca dinler (hepsiinput). 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:validmaster'daoutput, slave'deinputolarak görünür;monitorise her şeyiinputalır.bus_mastermodülü: Portundabus_if.master busyazarak yalnızca master görünümünü alır.writevereadtask'leri el sıkışmayı yönetir:bus.validkaldırılır, ardındando ... while (!bus.ready)döngüsüyle slave'inreadysinyali saat kenarına senkron biçimde beklenir.txn_countile yapılan işlem sayısı tutulur.bus_slavemodülü:posedge bus.clk or negedge bus.rst_nile çalışır. Reset sırasında çıkışları sıfırlar; aksi halde her saatteready'yi varsayılan olarak0yapar,bus.valid && !bus.readykoşulunda işlemi gerçekleştirir.bus.rw1 isememory[bus.addr]'a yazar, 0 ise oradanbus.rdata'ya okur ve el sıkışmayı tamamlamak içinready'yi bir saat süresince 1 yapar.bus_monitormodülü: Her saat kenarındabus.valid && bus.ready(başarılı el sıkışma anı) gerçekleştiğinde işlemitotal_reads/total_writessayaç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 vemst,slv,monmodü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
outputilan 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ı
waityerine saat kenarıyla bekleyin: Koddado begin @(posedge bus.clk); end while (!bus.ready)kullanılması bilinçli bir tercihtir.wait(bus.ready)ile beklemek,readyaynı delta zaman diliminde dalgalanırsa yarış (race) ve yanlış senkronizasyona yol açabilir. - Monitor pasif kalmalı: Gözlemci modport'unun tüm sinyalleri
inputolmalı; 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.