《SystemVerilog驗證測試平臺編寫指南》學習筆記——連線設計和測試平臺(二)
技術標籤:SystemVerilog
一、介面的驅動和取樣
測試平臺需要驅動和取樣設計的訊號,主要是通過帶有時鐘塊的介面做到的。非同步訊號通過介面時沒有任何延時,比如rst,而時鐘塊中的訊號將得到同步。
1、介面同步
可以使用Verilog的@和wait來同步測試平臺中的訊號
訊號同步
program automatic test(bus.if.TB bus); initial begin @bus.cb; //在時鐘塊的有效時鐘沿繼續 repeat (3) @bus.cb; //等待三個有效時鐘沿 @bus.cb.grant; //在任何邊沿繼續 @(postedge bus.cb.grant); //上升沿繼續 @(negegde bus.cb.grant); //下降沿繼續 wait(bus.cb.grant == 1); //等待表示式被執行,如果已經是真,不做任何延時 @(postedge bus.cb.grant or negedge bus.rst) //等待幾個訊號 end endprogram
2、介面訊號取樣
模組中同步介面的取樣和驅動
'timescale 1ns/1ns
program test(arb_if.TEST arbif);
initial begin
$monitor("@%0t: grant=%h", $time, arbif.cb.grant);
# 50ns $display("End of test");
endprogram
module arb(arb_if.DUT arbif);
initial begin
# 7 arbif.grant = 1; //@7ns;
# 10 arbif.grant = 2; //@17ns;
# 8 arbif.grant = 3; //@25ns;
end
endmodule
波形如下:
波形表明,arbif.cb.grant在時鐘邊沿到來之前獲得數值,在5ns時獲取的值時之前的X值,所以5ns~15ns顯示的值為X,在15ns時獲取的值時15ns到來之前的值1,所以15ns ~ 25ns顯示的值為1,以此類推。
arb模組在一個時鐘週期的中間產生grant訊號的值1和2,然後在時鐘沿產生值3。
3、介面訊號驅動
使用帶有時鐘塊介面的測試平臺
program automatic test(arb_if.TEST arbif);
initial begin
arbif. cb.request <= 2'b01; //在時鐘塊中使用modport的時候,任何同步介面訊號都必須加上介面名和時鐘塊名的字首
$display("@%0t: Drove req = 01", $time);
repeat(2) @arbif.cb;
if(arbif.cb.grant != 2'b01)
$display("@%0t: al: grant != 2'b01", $time);
end
endprogram : test
4、通過時鐘塊驅動介面訊號
在時鐘塊中應當使用同步驅動,即“<=”操作符來驅動訊號。訊號在賦值後並不會立即改變——別忘了測試平臺在Reactive區域執行而設計的程式碼在Active區域執行。如果在時鐘沿之間產生一個request訊號,那麼該變化知道下一個時鐘沿才會傳遞給設計。因此驅動總是同步的。
如果測試平臺在時鐘的有效沿驅動同步介面訊號,其值會立即傳遞到設計中,這是因為時鐘塊的預設輸出延時是# 0。
驅動一個同步介面
program test(arb_if.TEST arbif);
initial begin
# 7 arbif.cb.request <= 3; //@7ns;
# 10 arbif.cb.request <= 2; //@17ns;
# 8 arbif.cb.request <= 1; //@25ns;
# 15 finish;
end;
endprogram
module arb(arb_if.DUT arbif);
initial
$monitor("@%0t: req=%h", $time, arbif.request);
波形圖如下:
這裡注意看波形中在一個週期中即5 ~ 15ns中,第7ns時TEST產生的值3,在下一個週期也就是15ns~25ns時被DUT捕獲輸出。但是為什麼TEST在17ns產生的值2沒有被DUT捕獲輸出呢?那是因為之前說的**如果測試平臺在時鐘的有效沿驅動同步介面訊號,其值會立即傳遞到設計中,這是因為時鐘塊的預設輸出延時是# 0。**也就是說TEST在17ns時產生的值2不會立即傳遞給設計,會在下一個時鐘沿傳遞給設計。但是DUT在25ns時準備捕獲TEST的值時,TEST又在25ns上升沿驅動同步訊號讓值變為了1,此時值會立即傳遞到設計中,相當於值2被值1“覆蓋”了,所以DUT捕獲到了值1沒有捕獲到值2。
非同步地驅動時鐘塊訊號會導致數值丟失,應該使用時鐘延時字首以保證在時鐘UA沿驅動訊號。
# # 2 arbif.cb.request <= 0; //等待兩個時鐘週期然後賦值,只能在時鐘塊裡作為驅動訊號的字首來使用,因為它需要知道使用哪個時鐘來做延時
# # 3; //非法,必須跟賦值語句同時使用
5、介面中的雙向訊號
在程式中對線網(net)賦值的時候,SystemVerilog實際上將值寫到了一個驅動該線網的臨時變數中。所有驅動器輸出的值經過判決後,程式可以直接通過連線讀取該值。
程式和介面中的雙向訊號
interface master_if(input bit clk);
wire[7:0] data; //雙向訊號
clocking cb @(postedge clk);
inout data;
endclocking
modport TEST(clocking cb);
endinterface
program test (master_if mif);
initial begin
mif.cb.data <= 'z; //三態匯流排
@mif.cb;
$displayh(mif.cb.data); //從匯流排讀取
mif.cb.data <= 7'h5a; //驅動匯流排
@mif.cb;
mif.cb.data <= 'z; //釋放匯流排
end
endprogram
6、為什麼在程式中不允許使用always塊
program中可以使用initial塊但是不允許使用always塊。因為SystemVerilog程式比由許多並行執行的塊構成的Verilog更加接近C程式,它擁有一個(或者多個)程式入口。在一個設計中,一個always塊可能從模擬的開始就會在每一個時鐘的上升沿觸發執行,但是一個測試平臺的執行過程是經過初始化、驅動、響應設計行為等步驟後結束模擬的。在這裡一個連續執行的always模組不能正常工作。但是可以使用initial forever來替代always塊。
7、時鐘發生器
時鐘發生器應當定義成一個模組,不應該把時鐘發生器放在程式塊裡。功能驗證關心的是在正確的時鐘週期內提供正確的值,而不是納秒級的延時和時鐘的相對偏移。
模組中的時鐘發生器
module clock_generator(output bit clk);
initial
always # 5 clk = ~clk; //在時間0之後生成時鐘沿
endmodule
二、將這些模組都連線起來
編譯器不會成功編譯任何一個在埠列表中含有介面的模組或程式塊。
含有介面的模組
//沒有介面宣告,該模組不會被正確編譯,必須完成介面的連線工作
module uses_an_interface(arb_ifc.DUT ifc);
initial ifc.grant = 0;
endmodule
連線DUT和介面的頂層模組
module top;
bit clk;
always # 10 clk = !clk;
arb_ifc ifc(clk); //帶有時鐘塊的介面
uses_an_interface ul(ifc); //必須這樣定義才能被編譯
endmodule
三、頂層作用域
編譯單元,它是一起編譯的原始檔的一個組合。任何module,macromodule,interface,program,package或者primitive邊界之外的作用域被稱為編譯單元作用域,也稱為$unit
仲裁器設計的頂層作用域
'timescale 1ns/1ns
parameter int TIMEOUT = 1000000;
const string time_out_msg = "ERROR: Time Out!";
module top;
test t1();
endmodule
program automatic test;
...
initial begin
# TIMEOUT;
$display("%s", time_out_msg);
$finish;
end
endprogram
例項名$root允許從頂層作用域開始明確地引用系統中的成員名。可以指定絕對路徑或者相對路徑來明確地引用跨模組的變數。
四、程式——模組互動
程式塊可以讀寫模組中的所有訊號,但是模組卻看不到程式塊。程式可以呼叫模組中的例程來執行不同的動作,這個例程可以改變訊號的值,也稱為後門。