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.
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 = default; // Hazırlık (elaboration) sırasında aracın asılı kalmasını önler
}
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.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.defaultbin asılı kalmayı önler:cp_dataiçindekinonzero = defaultyorumu, elaboration sırasında 32-bit veri için devasa bin üretiminin yarattığı takılmayı engellemek içindir.- 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.