Lab 5: Interface + Coverage
Gün 5: Arayüzler, Assertion ve Coverage | Interface, Virtual Interface, Driver ve Coverage entegrasyonu
Bu lab, Gün 5 boyunca öğrendiğimiz tüm parçaları (interface, clocking block, virtual interface, transaction sınıfı, driver ve functional coverage) tek bir mini doğrulama ortamında birleştiriyor. Hedef, basit bir bellek DUT'unu mailbox tabanlı bir driver ile sürmek ve sürülen işlemlerin kapsamını ölçmek.
Lab'ın Mimarisi
Bu test ortamı, gerçek bir UVM ortamının sadeleştirilmiş bir taslağıdır. Akış şöyle ilerler:
- Interface + clocking block (
mem_if): DUT ile testbench arasındaki sinyalleri gruplar;cbclocking block'u senkronizasyonu sağlar.tbvedutmodport'ları iki tarafın bakış açısını ayırır. - Transaction (
MemTransaction): Tek bir bellek işlemini (yazma/okuma, adres, veri) temsil eden, rastgele üretilebilen bir veri nesnesidir. - Driver (
MemDriver): Bir mailbox üzerinden gelen transaction'ları alır ve virtual interface aracılığıyla DUT'a sürer. - Coverage (
MemCoverage): Üretilen işlemlerin kapsamınıcovergroupile ölçer. - Top (
lab5_interface_coverage): Tüm bileşenleri bağlar, driver'ı arka planda çalıştırır ve işlemleri üretip mailbox'a koyar.
Producer–Consumer ve Mailbox
Bu labın can damarı mailbox ile kurulan üretici–tüketici (producer–consumer) modelidir. Üst modül işlemleri üretip mbx.put(txn) ile kuyruğa koyar; driver ise mbx.get(txn) ile bunları sırayla alıp sürer. mailbox, iki süreç arasında güvenli ve senkron veri aktarımı sağlar; tüketici hazır olana dek üreticiyi doğal biçimde dengeler. Bu ayrıştırma, stimulus üretimini sinyal seviyesinden bağımsız kılar — UVM'deki sequencer/driver ayrımının temelidir.
Mimari Şema
lab5_interface_coverage (top)
├── clk ────────────────────────────────┐
├── mem_if mif(clk) ◀── virtual ───┐ │
│ ├─ clocking cb (tb tarafi) │ │
│ ├─ modport tb │ │
│ └─ modport dut ──▶ memory_dut (DUT)
├── mailbox mbx ──▶ MemDriver drv (vif = mif.tb) ── cb ──▶ DUT pinleri
├── MemCoverage cov ◀── cov.sample(txn) (stimulus uretilirken)
└── initial: reset → fork drv.run() → 50 × {randomize, sample, put} → wait → rapor
Adım Adım Görev Listesi
- Interface:
mem_ifiçinde sinyaller,cbclocking block'u (output cs, we, addr, wdata; input rdata, ack) vetb/dutmodport'ları. - DUT: Senkron,
rst_nile asenkron sıfırlanan 256 kelimelik bellek;csaktifken yaz/oku veacküret. - Transaction:
rand we, addr, wdata; adres 0–63;solve we before wdataile yön bağımlı veri kısıtı. - Driver:
virtual mem_if.tb vif+mailbox;run()içindeget→ sür →ackbekle →rdataoku →csdüşür → bir idle çevrim. - Coverage:
with function sample(...)covergroup'u,cp_we,cp_addr,cp_datavecx_we_addr. - Top: Reset,
join_noneile driver, 50 transaction,wait(drv.driven_count == 50), rapor.
Kontrol noktası:
cp_we%100,cp_addr%100 vecx_we_addr%100'e yakın çıkmalı (8 kombinasyon, 50 örnek).cp_data%100 olmalı: okuma işlemlerizero, yazmalarnonzerobin'ini doldurur.
Kaynak Kod
// =============================================================================
// GÜN 5 - Lab 5: Interface + Virtual Interface + Driver + Coverage (Kapsam)
// =============================================================================
// Arayüz (Interface) tanımı
interface mem_if(input logic clk);
logic rst_n;
logic cs; // çip seçme (chip select)
logic we; // yazma yetkisi (write enable)
logic [7:0] addr;
logic [31:0] wdata;
logic [31:0] rdata;
logic ack;
clocking cb @(posedge clk);
default input #1step output #0;
output cs, we, addr, wdata;
input rdata, ack;
endclocking
modport tb (clocking cb, output rst_n, input clk);
modport dut (input clk, rst_n, cs, we, addr, wdata, output rdata, ack);
endinterface
// Basit Bellek Tasarımı (DUT)
module memory_dut(mem_if.dut mif);
logic [31:0] mem [256];
always @(posedge mif.clk or negedge mif.rst_n) begin
if (!mif.rst_n) begin
mif.rdata <= 0;
mif.ack <= 0;
end else if (mif.cs) begin
if (mif.we) begin
mem[mif.addr] <= mif.wdata;
end else begin
mif.rdata <= mem[mif.addr];
end
mif.ack <= 1;
end else begin
mif.ack <= 0;
end
end
endmodule
// İşlem (Transaction) sınıfı
class MemTransaction;
rand bit we;
rand bit [7:0] addr;
rand logic [31:0] wdata;
logic [31:0] rdata;
int id;
static int count = 0;
constraint c_addr {
addr inside {[0:63]};
}
//
constraint c_data {
solve we before wdata; // ÖNEMLİ: Önce we sinyalini %50 olasılıkla hesaplasın diye koyduk
we -> wdata != 0;
!we -> wdata == 0;
}
function new();
id = count++;
endfunction
function void display(string prefix = "");
$display(" %sTXN#%0d | %s | Addr=0x%02h | WData=0x%08h | RData=0x%08h",
prefix, id, we ? "WR" : "RD", addr, wdata, rdata);
endfunction
endclass
// Driver sınıfı (sanal arayüz - virtual interface kullanarak)
class MemDriver;
virtual mem_if.tb vif;
mailbox #(MemTransaction) mbx;
int driven_count = 0;
function new(virtual mem_if.tb vif, mailbox #(MemTransaction) mbx);
this.vif = vif;
this.mbx = mbx;
endfunction
task run();
MemTransaction txn;
forever begin
mbx.get(txn);
@(vif.cb);
vif.cb.cs <= 1;
vif.cb.we <= txn.we;
vif.cb.addr <= txn.addr;
vif.cb.wdata <= txn.wdata;
// Onay (ack) sinyalini senkronize şekilde bekle (simülatörün asılı kalmasını önler)
do begin
@(vif.cb);
end while (vif.cb.ack == 0);
if (!txn.we)
txn.rdata = vif.cb.rdata;
vif.cb.cs <= 0;
// Tasarımın (DUT) CS=0 sinyalini görmesi ve ACK'yı düşürmesi için boş (idle) döngü
@(vif.cb);
driven_count++;
txn.display("[Driver] ");
end
endtask
task reset();
vif.rst_n = 0;
vif.cb.cs <= 0;
vif.cb.we <= 0;
vif.cb.addr <= 0;
vif.cb.wdata <= 0;
repeat(5) @(vif.cb);
vif.rst_n = 1;
@(vif.cb);
$display(" [Driver] Reset tamamlandı");
endtask
endclass
// Kapsam (Coverage) sınıfı
class MemCoverage;
covergroup mem_cg with function sample(bit we, bit [7:0] addr, logic [31:0] data);
cp_we: coverpoint we {
bins read = {0};
bins write = {1};
}
cp_addr: coverpoint addr {
bins low = {[0:15]};
bins mid = {[16:31]};
bins high = {[32:47]};
bins upper = {[48:63]};
}
cp_data: coverpoint data {
bins zero = {0};
bins nonzero = {[1:$]}; // 1'den en buyuk degere kadar TEK bin ($ = ust sinir)
// NOT: 32-bit veri icin bins yazmazsaniz arac otomatik 64 kova uretir;
// "nonzero = default" yazmak ise bu bin'i kapsam yuzdesine SAYMAZ.
}
cx_we_addr: cross cp_we, cp_addr;
endgroup
// Covergroup, içine gömüldüğünde kendi örnek tipi (instance type) gibi davranır
function new();
mem_cg = new();
endfunction
function void sample(MemTransaction txn);
mem_cg.sample(txn.we, txn.addr, txn.wdata);
endfunction
function void report();
$display("\n --- Kapsam Raporu (Coverage Report) ---");
$display(" Toplam : %.1f%%", mem_cg.get_coverage());
$display(" cp_we : %.1f%%", mem_cg.cp_we.get_coverage());
$display(" cp_addr : %.1f%%", mem_cg.cp_addr.get_coverage());
$display(" cp_data : %.1f%%", mem_cg.cp_data.get_coverage());
$display(" cx_we_addr : %.1f%%", mem_cg.cx_we_addr.get_coverage());
endfunction
endclass
// Üst seviye test ortamı (Testbench)
module lab5_interface_coverage;
logic clk = 0;
always #5 clk = ~clk;
mem_if mif(clk);
memory_dut dut(mif);
mailbox #(MemTransaction) mbx;
MemDriver drv;
MemCoverage cov;
initial begin
// Örtük statik değişken hatalarını önlemek için döngü dışında tanımlanmıştır
MemTransaction txn;
$display("============================================================");
$display(" LAB 5: Interface + Driver + Coverage");
$display("============================================================\n");
mbx = new();
drv = new(mif, mbx);
cov = new();
// Reset dizisi
drv.reset();
// Driver'ı arka planda çalıştır
fork
drv.run();
join_none
// İşlemleri (transaction) oluştur ve kuyruğa (mailbox) koy
repeat (50) begin
txn = new(); // Nesneyi burada başlat
assert(txn.randomize()) else $fatal(1, "Randomize başarısız!");
cov.sample(txn);
mbx.put(txn);
end
// Tüm işlemlerin tamamen sürülmesini bekle
wait(drv.driven_count == 50);
#100;
// Raporlama
$display("\n --- İstatistikler ---");
$display(" Toplam sürülen: %0d", drv.driven_count);
cov.report();
$display("\n============================================================");
$display(" LAB 5 TAMAMLANDI");
$display("============================================================");
$finish;
end
endmodule
Kodun Açıklaması
mem_ifarayüzü: Bellek sinyallerini (rst_n,cs,we,addr,wdata,rdata,ack) gruplar.clocking cbbloğudefault input #1step output #0ile zamanlamayı yönetir.tbmodport'u clocking block'u verst_nçıkışını içerir;dutmodport'u sentezlenebilir tarafı tanımlar.memory_dutmodülü:posedge mif.clk or negedge mif.rst_nile çalışır. Reset'te çıkışları sıfırlar;mif.csaktifkenmif.weisemem[mif.addr]'a yazar, değilsemif.rdata'ya okur veack'i 1 yapar.csdüştüğündeack'i geri 0'a çeker.MemTransactionsınıfı:we,addr,wdatarastgele üretilir.c_addradresi 0-63'e sınırlar.c_datakısıtındasolve we before wdataile önceweçözülür (yazma/okuma ~%50 dengesi için);weisewdata != 0, değilsewdata == 0zorlanır.static int countile her nesneye benzersizidatanır.MemDriversınıfı:virtual mem_if.tb vifvemailbox #(MemTransaction) mbxtutar.runtask'iforeverdöngüsündembx.get(txn)ile işlem alır,vif.cbüzerinden DUT'u sürer,do ... while (vif.cb.ack == 0)ile senkron olarakack'i bekler, okuma isetxn.rdata'yı örnekler.cs'yi düşürüp bir idle çevrim bekler vedriven_count'u artırır.resettask'irst_n'yi indirip 5 çevrim sonra geri kaldırır.MemCoveragesınıfı:mem_cgcovergroup'ucp_we(read/write),cp_addr(dört adres bölgesi) vecp_data(zero/nonzero) coverpoint'lerini vecx_we_addrçaprazını içerir.cp_dataiçindeki{[1:$]}aralığı "1'den tipin en büyük değerine kadar" demektir;$burada üst sınırı temsil eder ve 32-bit için32'hFFFF_FFFFyazmaktan kurtarır.sample(MemTransaction txn)sarmalayıcı fonksiyonu transaction alanlarını covergroup'a aktarır.reportkapsam yüzdelerini basar.lab5_interface_coverage(top):mem_if, DUT,mbx,drvvecovoluşturulur.drv.reset()sonrasıfork ... join_noneile driver arka planda başlatılır.repeat(50)döngüsünde her işlemrandomizeedilipcov.sample(txn)ile örneklenir vembx.put(txn)ile kuyruğa konur.wait(drv.driven_count == 50)tüm işlemlerin sürülmesini bekler, sonra rapor basılır.
Önemli Noktalar
solve ... beforesıralamayı yönetir:solve we before wdata, çözücünün öncewe'yi belirlemesini sağlayarak yazma/okuma dağılımını dengeler;c_datakısıtının yön bağımlılığı bu sayede tutarlı çalışır.ack'iwaityerine senkron bekleyin:do begin @(vif.cb); end while (vif.cb.ack == 0)kullanımı, simülatörün asılı kalmasını ve yarış koşullarını engeller.- Geniş coverpoint'lerde bin'leri siz tanımlayın: 32-bit
dataiçinbinsyazılmazsa araç otomatik 64 kova üretir ve anlamsız bir yüzde çıkar.nonzero = {[1:$]}tek ve anlamlı bir kova verir ve kapsam yüzdesine sayılır;nonzero = defaultyazımı ise çalışır amadefaultbin yüzdeye dahil edilmediği içincp_datayalnızcazeroüzerinden hesaplanırdı. - Mailbox ile bileşenleri ayırın: Stimulus üretimi (top) ile sinyal sürüşü (driver) mailbox üzerinden ayrıştırılmıştır; bu, ölçeklenebilir ve yeniden kullanılabilir bir yapıdır.
- Kapsamı sürüşten önce örnekleyin: Burada
cov.sample(txn)işlem mailbox'a konmadan önce çağrılır; kapsam, gönderilen stimulus'a göre ölçülür. Gerçek doğrulamada genellikle monitör gözlemini örneklemek tercih edilir. join_noneile arka plan süreci: Driverfork ... join_noneile başlatıldığından üst akış engellenmez; senkronizasyondriven_countüzerindenwaitile sağlanır. Bu, Lab 4'teki sabit#100beklemesinden daha sağlamdır: transaction sayısı değişse de kod değişmez.- Okuma işlemlerinde
rdatadriver tarafından doldurulur:txn.rdata = vif.cb.rdatasatırı, mailbox'tan gelen aynı nesneye yazar; handle paylaşımı sayesinde top'taki döngü bu nesneye hâlâ erişebilseydi okunan değeri görebilirdi. Gün 7'de bu bilgi scoreboard'a taşınacak. solve we before wdataolmadan ne olurdu? Kısıtwe -> wdata != 0; !we -> wdata == 0;çözücü için iki çözüm kümesi tanımlar:{we=1, wdata ∈ 1..2³²-1}(4 milyar çözüm) ve{we=0, wdata=0}(tek çözüm). Çözücü çözümleri eşit olasılıkla seçersewe=0neredeyse hiç çıkmaz.solve ... beforeöncewe'yi %50/%50 seçtirir, sonrawdata'yı ona göre çözer.
Ek Görevler (Zorluk Artan)
- Kolay:
solve we before wdata;satırını silip 50 transaction içinde kaç okuma (RD) çıktığını sayın. Beklentinizle karşılaştırın. - Orta:
MemCoverage'acp_rdataekleyin vesample()'ı driver sürdükten sonra çağırın (driver'a birMemCoverage covhandle'ı verin). Stimulus'tan toplanan kapsamla DUT'tan okunan kapsamı karşılaştırın. - Orta:
mem_ifiçine bir assertion ekleyin:csdüştükten bir çevrim sonraackda düşmeli ($fell(cs) |=> !ack). DUT'u bozup (örneğinack <= 1sabitleyip) assertion'ın yakaladığını gösterin. - Zor: Bir
MemMonitorsınıfı yazın (virtual mem_if.mon vif, tümü-input birmon_cbile — önceki dersteki kalıp) vecs && ackanlarındaMemTransactionüretip bir kuyruğa koysun. Simülasyon sonunda monitor kuyruğundaki işlem sayısınındriven_count'a eşit olduğunu doğrulayın; ardından yazılan her adrese yapılan okumanın yazılan değeri döndürdüğünü kontrol eden basit bir scoreboard ekleyin.
Hızlı Kontrol: solve we before wdata dağılımı neden değiştirir?
Çözücü varsayılan olarak tüm çözüm kombinasyonlarını eşit olasılıkla seçer. we=1 için milyarlarca wdata çözümü, we=0 için tek çözüm varsa we=0 neredeyse hiç seçilmez. solve we before wdata, önce we'yi kendi aralığında (0/1, %50 olasılıkla) seçtirip wdata'yı ardından çözer; böylece okuma/yazma dengesi korunur.