Clocking Block
Gün 5: Arayüzler, Assertion ve Coverage | Zamanlama blokları, setup/hold yönetimi, APB protokolü
🌟🌟Okuma Önerisi: Taming Testbench Timing
Bu derste clocking block (zamanlama bloğu) yapısını ve test ortamı ile DUT arasındaki yarış koşullarını (race condition) nasıl ortadan kaldırdığını öğreneceğiz. Örnek olarak basit bir APB protokolünü clocking block üzerinden süreceğiz.
Clocking Block Neden Gerekli?
Bir test ortamı (testbench), DUT ile aynı saat kenarında sinyal sürerse iki kritik problem ortaya çıkar:
- Yarış koşulu (race condition): TB sinyali tam saat kenarında değiştirir, DUT da aynı kenarda örnekler. Hangisinin önce çalışacağı belirsizdir; simülatöre göre sonuç değişebilir.
- Setup/Hold belirsizliği: Gerçek donanımda sinyaller saat kenarından belirli bir süre önce kararlı olmalı (setup) ve kenardan sonra bir süre korunmalıdır (hold). Bunu modellemeden yapılan testler gerçekçi değildir.
clocking block, bir saat olayına (@(posedge clk)) göre sinyallerin ne zaman örnekleneceğini (input) ve ne zaman sürüleceğini (output) merkezi olarak tanımlar. Böylece testbench, DUT'tan otomatik olarak güvenli bir zaman aralığında ayrılır.
Input/Output Skew ve 1step
Koddaki default input #1step output #1 satırı zamanlamanın kalbidir:
input #1step: Girişler, saat kenarından bir simülasyon adımı önce (yani Preponed bölgesinde) örneklenir. Bu, DUT henüz aynı kenarda değer değiştirmeden, kararlı eski değeri okumayı garanti eder; böylece yarış koşulu yok olur.output #1: Çıkışlar, saat kenarından 1 zaman birimi sonra sürülür. Bu, DUT'un girişleri kenarın hemen üzerinde değil, küçük bir gecikmeyle görmesini sağlar (hold benzeri davranış).
Test kodunda aif.cb.psel <= 1 gibi atamalar ve @(aif.cb) ile beklemeler, doğrudan ham sinyallere değil clocking block üzerinden yapıldığı için bu zamanlama kuralları otomatik uygulanır.
Kaynak Kod
// =============================================================================
// GUN 5 - Konu 2: Clocking Blocks (Zamanlama Bloklari)
// =============================================================================
interface apb_if(input logic clk);
logic psel;
logic penable;
logic pwrite;
logic [7:0] paddr;
logic [31:0] pwdata;
logic [31:0] prdata;
logic pready;
// Clocking Block: Testbench zamanlama yonetimi
// Sinyallerin ornekleme ve surme zamanlarini tanimlar
clocking cb @(posedge clk);
default input #1step output #1; // Input output skew
output psel;
output penable;
output pwrite;
output paddr;
output pwdata;
input prdata;
input pready;
endclocking
modport dut (
input clk, psel, penable, pwrite, paddr, pwdata,
output prdata, pready
);
endinterface
// Basit APB Slave (DUT rolunde)
module apb_slave(apb_if.dut apb);
logic [31:0] reg_file [256];
always @(posedge apb.clk) begin
apb.pready <= 0;
if (apb.psel && apb.penable) begin
if (apb.pwrite)
reg_file[apb.paddr] <= apb.pwdata;
else
apb.prdata <= reg_file[apb.paddr];
apb.pready <= 1;
end
end
endmodule
module clocking_block;
logic clk = 0;
always #5 clk = ~clk;
apb_if aif(clk);
apb_slave dut(aif);
// Clocking block ile surme
task apb_write(input logic [7:0] addr, input logic [31:0] data);
// Setup phase
aif.cb.psel <= 1;
aif.cb.pwrite <= 1;
aif.cb.paddr <= addr;
aif.cb.pwdata <= data;
aif.cb.penable <= 0;
@(aif.cb);
// Access phase
aif.cb.penable <= 1;
@(aif.cb);
wait(aif.cb.pready);
// Idle
aif.cb.psel <= 0;
aif.cb.penable <= 0;
$display(" [%0t] APB Write: Addr=0x%02h, Data=0x%08h", $time, addr, data);
endtask
task apb_read(input logic [7:0] addr, output logic [31:0] data);
aif.cb.psel <= 1;
aif.cb.pwrite <= 0;
aif.cb.paddr <= addr;
aif.cb.penable <= 0;
@(aif.cb);
aif.cb.penable <= 1;
@(aif.cb);
wait(aif.cb.pready);
data = aif.cb.prdata;
aif.cb.psel <= 0;
aif.cb.penable <= 0;
$display(" [%0t] APB Read: Addr=0x%02h, Data=0x%08h", $time, addr, data);
endtask
initial begin
$dumpfile("dump.vcd");
$dumpvars;
end
initial begin
logic [31:0] rdata;
$display("=== Clocking Block ===\n");
// Baslangic degerleri
aif.cb.psel <= 0;
aif.cb.penable <= 0;
#20;
// Yazma
apb_write(8'h00, 32'hAAAA_BBBB);
apb_write(8'h04, 32'hCCCC_DDDD);
// Okuma
apb_read(8'h00, rdata);
apb_read(8'h04, rdata);
#50;
$display("\n=== Clocking Block Sonu ===");
$finish;
end
endmodule
Kodun Açıklaması
interface apb_if(input logic clk): APB sinyallerini (psel,penable,pwrite,paddr,pwdata,prdata,pready) gruplar.clocking cb @(posedge clk): Tüm testbench zamanlamasını yöneten bloktur.default input #1step output #1ile varsayılan örnekleme/sürme gecikmeleri belirlenir. TB'nin sürdüğü sinyaller (psel,penable,pwrite,paddr,pwdata)output; DUT'tan okunanlar (prdata,pready)inputolarak tanımlanır.modport dut: DUT'un bakış açısını verir; TB'nin sürdüğü sinyalleriinput, ürettiklerinioutputalır. DUT clocking block'u kullanmaz, doğrudan ham sinyalleri kullanır.apb_slavemodülü:posedge apb.clkile çalışır. Her saattepready'yi varsayılan0yapar;apb.psel && apb.penable(APB access fazı) doğruysapwrite'a görereg_file'a yazar ya da oradanprdata'ya okur vepready'yi 1 yapar.apb_writetask'i: APB'nin iki fazını uygular. Önce setup faz (psel=1,penable=0) kurulup@(aif.cb)ile bir saat beklenir; sonra access faz (penable=1) sürülür vewait(aif.cb.pready)ile DUT onayı beklenir. Tüm atamalaraif.cb.*üzerinden, yani clocking block aracılığıyla yapılır.apb_readtask'i: Aynı setup/access akışını okuma için uygular vedata = aif.cb.prdataile veriyi clocking block'tan örneklenmiş olarak alır.clocking_block(top):clküretir,apb_if aif(clk)veapb_slave dut(aif)örneklenir. Başlangıç değerleri verildikten sonra iki yazma ve iki okuma işlemi yürütülür.
Önemli Noktalar
@(aif.cb)ile@(posedge clk)farkı: Clocking block olayını (@(aif.cb)) beklemek, ham saat kenarını beklemekten farklı olarak skew kurallarını devreye sokar ve yarış koşullarını engeller.- Clocking block sinyaline
<=ile yazın:aif.cb.psel <= 1gibi non-blocking atamalar, çıkış skew'ine (output #1) uygun şekilde değeri planlı zamanda sürer. Blocking atama (=) clocking block çıkışlarında uygun değildir. 1stepözel bir değerdir:#1step, zaman birimine bağlı olmayan en küçük adımdır ve Preponed bölgesinde örnekleme yaparak en güvenli giriş okumasını sağlar.- DUT clocking block kullanmaz: Zamanlama bloğu yalnızca testbench tarafının senkronizasyon aracıdır; DUT sentezlenebilir mantıkla ham sinyalleri kullanır.
- Tek merkezden zamanlama: Tüm gecikme kararlarının clocking block'ta toplanması, dağınık
#delayifadelerine kıyasla bakımı kolaylaştırır ve hata riskini azaltır. - Clocking block girişleri okunur, çıkışları yazılır — tersi yasaktır.
aif.cb.pready(input) okunabilir ama ona yazılamaz;aif.cb.psel(output) yazılabilir ama okunması standart dışıdır (araçlar hata ya da uyarı verir). Hem sürüp hem okumanız gereken bir sinyal varsainoutolarak tanımlayın ya da ham sinyali (aif.psel) okuyun. - Clocking olayı dışında yapılan sürüşler ertelenir:
apb_writetask'ının ilk satırındakiaif.cb.psel <= 1çağrısı bir saat kenarında yapılmıyorsa, atama bir sonrakicbolayına (+ output skew) kadar bekletilir. Bu, tüm sürüşlerin otomatik olarak saat hizalı olmasını sağlar. wait(aif.cb.pready)neden güvenli? Clocking block girişleri yalnızca saat kenarında (#1stepöncesinde) örneklendiği içinaif.cb.preadydeğeri çevrim ortasında değişmez;waitbu yüzden burada yarış yaratmaz. Hamaif.preadyüzerinde aynıwaitise yarışa açıktır.
Skew Değerlerinin Anlamı
┌──────────────────┐
clk ──────┘ └───────
▲ ▲ ▲
│ │ └── output #1 : TB çıkışları kenardan 1 birim SONRA sürülür
│ └───────── saat kenarı: DUT örnekler (Active bölge)
└────────────── input #1step : TB girişleri kenardan hemen ÖNCE örneklenir (Preponed)
| Skew | Anlamı | Ne zaman seçilir? |
|---|---|---|
input #1step |
Kenardan hemen önceki kararlı değer (Preponed bölgesi) | Varsayılan ve en güvenli giriş örneklemesi |
input #0 |
Kenarda, Observed bölgesinde | Nadiren; DUT'un aynı kenardaki NBA güncellemesini görmek istiyorsanız |
output #0 |
Kenarda, Re-NBA bölgesinde sürülür | Yaygın; DUT'un Active bölgedeki örneklemesinden sonra gelir, yarış yaratmaz |
output #1 / #2 |
Kenardan N birim sonra sürülür | Gerçekçi hold süresi istendiğinde; dalga formunda okunması daha kolay |
input #2 |
Kenardan 2 birim önce örnekler | Setup süresi modellemek için |
Önemli ayrım: #1step zaman birimine bağlı değildir; simülatörün en küçük zaman çözünürlüğü kadardır. #1 ise timescale'e bağlıdır (1 ns olabilir, 1 ps olabilir).
Benzetme: Clocking block, bir orkestradaki şeftir. Müzisyenler (testbench süreçleri) "şu anda çal" demez; şefin işaretini (
@(aif.cb)) bekler ve şefin kuralına göre işaretten biraz önce nefes alır (input skew), biraz sonra notaya basar (output skew). Kimse şefi beklemeden çalmaya kalkmaz, bu yüzden orkestra (TB ve DUT) birbirine karışmaz.
Sık Yapılan Hatalar
- Clocking çıkışını okumak:
if (aif.cb.psel)yazmak araca göre hata/uyarıdır. Sürdüğünüz değeri biliyorsunuz; gerekirse yerel bir değişkende tutun. - Blocking atama (
=) ile clockvar sürmek:aif.cb.psel = 1;derleme hatasıdır; clocking çıkışları yalnızca<=(synchronous drive) ile sürülür. @(posedge clk)ile@(aif.cb)'yi karıştırmak: Aynı anda uyanırlar gibi görünür ama@(aif.cb)sonrasında okunancbgirişleri örneklenmiş değerlerdir; ham kenar beklemede ise ham sinyal okursunuz ve yarışa düşersiniz.- DUT'u clocking block ile sürmeye çalışmak: Clocking block testbench tarafı içindir; DUT ham sinyalleri kullanır (
modport dut). #1step'i#1sanmak:timescale 1ns/1psile#1= 1 ns,#1step= 1 ps'dir. Preponed örnekleme için daima#1step.
Kendinizi Deneyin
default input #1step output #1satırınıoutput #0yapın ve dalga formundapsel'in saat kenarına göre nerede değiştiğini karşılaştırın.apb_writetask'ındaaif.cb.psel <= 1;yerineaif.psel <= 1;(ham sinyal) yazın. Çalıştı mı?$dumpvarsile hangi anda sürüldüğünü inceleyin; clocking block sürüşünden farkını açıklayın.if (aif.cb.psel)ifadesini bir yere ekleyin; derleyicinin verdiği mesajı not alın. Sonrapsel'iinoutyapıp tekrar deneyin.apb_slave'e "yazma işlemi 2 çevrim sürer" davranışı ekleyin (preadyikinci çevrimde 1 olsun).apb_write'takiwait(aif.cb.pready)bunu değişiklik gerektirmeden kaldırabildi mi?
Hızlı Kontrol: input #1step ne anlama gelir ve neden yarış koşulunu önler?
Girişlerin saat kenarından bir simülasyon adımı önce, yani Preponed bölgesinde örnekleneceği anlamına gelir. Bu anda DUT henüz o kenardaki güncellemelerini yapmamıştır; testbench her zaman önceki çevrimin kararlı değerini okur. DUT'un "aynı kenarda değişen" değerini mi yoksa eskisini mi okuduğunuz belirsizliği böylece kalkar.