?
?
要想深入理解Verilog就必須正視Verilog語言同時(shí)具備硬件特性和軟件特性。在當(dāng)下的教學(xué)過程中,教師和教材都過于強(qiáng)調(diào)Verilog語言的硬件特性和可綜合特性。將Verilog語言的行為級(jí)語法只作為語法設(shè)定來介紹,忽略了Verilog語言的軟件特性和仿真特性。使得初學(xué)者無法理解Verilog語言在行為級(jí)語法(過程塊、賦值和延遲)背后隱藏的設(shè)計(jì)思想。本文嘗試從仿真器的角度對(duì)Verilog語言的語法規(guī)則進(jìn)行一番解讀。
“精分”的Verilog語言 ?在集成電路的設(shè)計(jì)流程中,Verilog源文件有兩個(gè)主要作用:綜合和仿真。在圖1中,數(shù)字①②③④標(biāo)注的位置都可以使用Verilog作為設(shè)計(jì)的描述方法。- 綜合工具讀入源文件,通過綜合算法將設(shè)計(jì)轉(zhuǎn)化為網(wǎng)表,比如DC。能夠綜合的特性要求Verilog語言能夠描述信號(hào)的各種狀態(tài)(0,1,x,z)、信號(hào)和模塊的連接(例化)以及模塊的邏輯(賦值以及各種運(yùn)算符)。
- 仿真器讀入源文件,生成一個(gè)可執(zhí)行程序用于仿真硬件的行為,比如VCS。能夠仿真的特性要求Verilog語言又具有軟件特性,對(duì)每一條語句的執(zhí)行語義和順序給出定義(延遲語句)。同時(shí),軟件特性使得Verilog語言更加靈活,具備了豐富的行為級(jí)仿真能力(條件分支、循環(huán)等)。
?
仿真器基本架構(gòu)?
Verilog語言確實(shí)不是一種可執(zhí)行語言。圖2展示了利用Verilog源文件進(jìn)行仿真的過程。絕大多數(shù)仿真器都遵循這一思路,比如VCS、iVerilog、ModelSim、Vivado和Quartus等。首先,準(zhǔn)備Verilog源文件以及一些Verilog庫(kù)文件(標(biāo)準(zhǔn)單元等)。仿真器接收這些Verilog文件并將其轉(zhuǎn)化為可執(zhí)行的仿真源文件(C/C++等)。在這一過程中,仿真器解析Verilog文件的語法結(jié)構(gòu),并且根據(jù)Verilog語法的規(guī)范,將語法結(jié)構(gòu)轉(zhuǎn)化為仿真器中的事件響應(yīng)函數(shù)或代碼段。這些函數(shù)和代碼段與仿真器框架源文件一起成為可執(zhí)行仿真程序的源文件。接下類這些源文件經(jīng)過編譯得到可執(zhí)行的仿真程序。VCS和iVerilog可以看到生成的可執(zhí)行文件。ModelSim、Vivado和Quartus使用GUI管理設(shè)計(jì)流程,從而將這個(gè)可執(zhí)行文件屏蔽了,使其對(duì)于用戶可透明。用戶可以在工程中找到生成的可執(zhí)行文件。最后,運(yùn)行可執(zhí)行的仿真程序,進(jìn)行軟件仿真。?
圖2 從Verilog源文件到可執(zhí)行仿真程序的流程?
可執(zhí)行仿真源文件和仿真器框架源文件一般是不可見的。不過在開源軟件(例如iVerilog)中可以找到生成可執(zhí)行仿真源文件的代碼。仿真程序通常采用基于事件的仿真架構(gòu)。這種仿真架構(gòu)的核心是事件隊(duì)列。事件隊(duì)列中按照事件的響應(yīng)時(shí)間排列著一系列的事件。響應(yīng)時(shí)間相同的事件之間不應(yīng)該有決定性的事件依賴關(guān)系。如果需要確定這些事件之間的順序,可以引入Δ時(shí)間。響應(yīng)時(shí)間為t+Δ的事件必然晚于響應(yīng)時(shí)間為t的事件。但是從仿真時(shí)間上,仍然表現(xiàn)為在相同時(shí)刻響應(yīng)。事件隊(duì)列按照時(shí)間先后順序逐個(gè)響應(yīng)事件隊(duì)列中的事件。每一個(gè)事件,除了標(biāo)注事件響應(yīng)時(shí)間,還會(huì)標(biāo)注事件類型以及其他需要的參數(shù)。通過事件類型,仿真引擎可以找到對(duì)應(yīng)的響應(yīng)函數(shù)。其他的參數(shù)則作為事件響應(yīng)函數(shù)的輸入?yún)?shù)。事件響應(yīng)函數(shù)會(huì)產(chǎn)生新的事件。這些新的事件還會(huì)插入到事件隊(duì)列中,并且按照其響應(yīng)時(shí)間排序。圖3 事件隊(duì)列仿真框架的示意圖圖3展示仿真引擎響應(yīng)一個(gè)事件的過程。仿真引擎響應(yīng)事件隊(duì)列中的第一個(gè)事件e1。事件e1被從隊(duì)列中移除。事件隊(duì)列從事件e2開始。仿真引擎根據(jù)e1的類型找到了事件響應(yīng)函數(shù)。這個(gè)響應(yīng)函數(shù)又調(diào)用了3個(gè)模塊中的事件響應(yīng)函數(shù)。這些事件響應(yīng)函數(shù)模擬硬件電路的行為,并且產(chǎn)生了新的事件。模塊1產(chǎn)生了事件e197和e199,分別插入到t1時(shí)刻和t99時(shí)刻;模塊2產(chǎn)生了事件e198,插入t1+Δ時(shí)刻;模塊3產(chǎn)生了事件e200,插入t100時(shí)刻。通過“讀出第一個(gè)事件-響應(yīng)事件-插入新事件”的循環(huán),事件隊(duì)列可以一直運(yùn)行下去,直到事件隊(duì)列為空或者達(dá)到了仿真結(jié)束的時(shí)間。另一方面,在仿真開始的時(shí)候,必須向事件隊(duì)列中插入起始事件,從而開始仿真循環(huán)。Verilog仿真器提供了仿真引擎(在圖2中的仿真器框架源文件部分),所以大家在寫Verilog的時(shí)候不用去自己“造輪子”。但是仿真引擎并不知道事件和響應(yīng)函數(shù)的對(duì)應(yīng)關(guān)系以及響應(yīng)函數(shù)的具體功能。仿真器的工作就是將Verilog文件轉(zhuǎn)化為仿真響應(yīng)函數(shù)并且與仿真引擎進(jìn)行連接。生成的可執(zhí)行仿真源文件和仿真器框架文件一起構(gòu)成了完整的仿真器。接下來,分析一下Verilog的語法結(jié)構(gòu)(過程塊、賦值和延遲)如何變成仿真器的源文件。?過程塊always過程塊是Verilog最基本的行為級(jí)描述結(jié)構(gòu)。通過在always語句中設(shè)置敏感列表,可以在恰當(dāng)?shù)臅r(shí)刻觸發(fā)過程塊內(nèi)的操作。敏感列表中使用的條件主要是信號(hào)沿(上升沿、下降沿)以及信號(hào)值變化兩種。如果敏感列表有多個(gè)條件,這些條件是“或”的關(guān)系,也就說只要有一個(gè)條件滿足,always過程塊中的語句就會(huì)執(zhí)行一次。對(duì)應(yīng)到仿真器中,always過程塊的語義就是給仿真中的特定事件綁定響應(yīng)函數(shù)。always過程塊中的語句序列是事件響應(yīng)函數(shù)的函數(shù)體,而always語句的敏感列表確定了這個(gè)事件響應(yīng)函數(shù)與哪些事件綁定。例如下面的D觸發(fā)器。
always @ (posedge clk) begin
q <= d;
end
經(jīng)過仿真器的轉(zhuǎn)換就變成為如下的響應(yīng)函數(shù):
function always_block1 :
q = d;
這個(gè)響應(yīng)函數(shù)會(huì)與clk信號(hào)的上升沿事件(positive)進(jìn)行綁定。當(dāng)響應(yīng)clk信號(hào)的上升沿事件的時(shí)候,仿真器會(huì)調(diào)用always_block1這個(gè)函數(shù)。
?一個(gè)條件可以被綁定多個(gè)事件響應(yīng)函數(shù)。比如時(shí)鐘信號(hào)的事件可以與所有的always塊的事件響應(yīng)函數(shù)綁定。當(dāng)時(shí)鐘信號(hào)的事件發(fā)生的時(shí)候,與其綁定的事件響應(yīng)函數(shù)會(huì)逐個(gè)被調(diào)用。如果一個(gè)信號(hào)在多個(gè)always過程塊中都被賦值,那么一個(gè)變量會(huì)被多個(gè)事件響應(yīng)函數(shù)修改。在硬件上,這些響應(yīng)函數(shù)之間應(yīng)該是并發(fā)的,沒有先后關(guān)系。但是,串行執(zhí)行函數(shù)的軟件是做不到的這樣的并發(fā)的。在仿真器中,always過程塊之間也是有順序的。Verilog規(guī)定,always塊之間的執(zhí)行順序是按照always塊在Verilog文件中的先后順序。這僅僅是為了適應(yīng)軟件仿真器所引入的設(shè)定。
?如果敏感列表中有多個(gè)條件,表示always塊與這些信號(hào)都綁定。如果always塊沒有執(zhí)行敏感列表或者是給出一個(gè)星號(hào)(*),表示always塊應(yīng)該與過程塊中所有的右值變量綁定。在這種情況下,由每個(gè)事件都直接觸發(fā)事件響應(yīng)函數(shù)可能會(huì)引起重復(fù)響應(yīng),即在某個(gè)時(shí)刻事件響應(yīng)函數(shù)被多次觸發(fā)的情況。為了避免這樣的錯(cuò)誤,仿真器中引入了仿真階段的概念。同一個(gè)仿真階段中響應(yīng)的事件,響應(yīng)時(shí)間必須,而且Δ時(shí)間也必須相同。在同一個(gè)仿真階段中,每個(gè)事件響應(yīng)信號(hào)只能被觸發(fā)一次。每個(gè)仿真階段中,首先在事件隊(duì)列中找到需要響應(yīng)事件,然后累計(jì)需要調(diào)用的事件響應(yīng)函數(shù)。最后再依次調(diào)用這些事件響應(yīng)函數(shù)。這樣就保證了同一個(gè)時(shí)間的信號(hào)變化只會(huì)觸發(fā)同一個(gè)always過程塊一次。
?除了always過程塊,在Verilog中還定義了其他的過程塊。與always過程塊不同,這些過程塊不由信號(hào)的事件觸發(fā),而是要單獨(dú)在事件隊(duì)列上插入事件,并且與過程塊轉(zhuǎn)化成的響應(yīng)函數(shù)綁定。initial過程塊只在仿真開始的時(shí)候執(zhí)行一次。也就是說,如果定義initial過程塊,那么事件隊(duì)列上的第一個(gè)事件就是initial過程塊的事件。repeat過程塊和forever過程塊在事件響應(yīng)函數(shù)結(jié)束時(shí)向電路中添加觸發(fā)下一次響應(yīng)函數(shù)的事件。這個(gè)事件在下一個(gè)Δ時(shí)刻就會(huì)響應(yīng),由此往復(fù)。當(dāng)重復(fù)了足夠多次數(shù)后,repeat過程塊會(huì)停止向事件隊(duì)列中添加事件,從而結(jié)束repeat語句。forever過程塊的循環(huán)不會(huì)結(jié)束。?
賦值語句?
Verilog語言提供了阻塞賦值和非阻塞賦值兩種賦值語句。 ?
a = b; // 阻塞賦值
a <= b; // 非阻塞賦值
按照語法定義,阻塞賦值會(huì)阻塞之后語句的執(zhí)行;非阻塞賦值則不會(huì)阻塞之后語句的執(zhí)行。阻塞語句達(dá)成的效果是下一條語句執(zhí)行之前信號(hào)a已經(jīng)變修改。非阻塞賦值達(dá)成的效果是,信號(hào)a的值只有到整個(gè)過程塊執(zhí)行完,才會(huì)被修改。需要注意的是,非阻塞賦值雖然被延后,但是所賦的值仍是之前得到的值。這一段話著實(shí)令人感到疑惑?,F(xiàn)在我們從軟件仿真器的角度重新來解析賦值語句。賦值語句其實(shí)包含兩個(gè)過程:評(píng)估和更新。評(píng)估過程確定了需要賦給信號(hào)的值,而更新過程才真正的修改了信號(hào)的值。評(píng)估過程和更新過程是相互獨(dú)立的。這兩個(gè)過程中的關(guān)聯(lián)只有需要賦的值。阻塞賦值的評(píng)估過程和更新過程是連續(xù)執(zhí)行的,評(píng)估之后立即更新。所以,在執(zhí)行下一條語句的時(shí)候,信號(hào)已經(jīng)被修改了。在轉(zhuǎn)換成仿真器代碼時(shí),阻塞賦值不需要特殊處理。例如
always @(a, b, c) begin : add_mux1
t = a + b;
d = t * c;
end
上述代碼轉(zhuǎn)化后的事件響應(yīng)函數(shù)為
function add_mux1 :
t = a + b;
d = t * c;
非阻塞賦值的評(píng)估過程和更新過程是分開的。過程塊中執(zhí)行到賦值語句的時(shí)候,只進(jìn)行了評(píng)估過程,確定需要賦給信號(hào)的值,然后繼續(xù)向后執(zhí)行。更新過程被延后到整個(gè)過程塊執(zhí)行之后。例如
always @(a, b, c) begin : add_mux2
t <= a + b;
d <= t * c;
end
上述代碼轉(zhuǎn)化后的事件響應(yīng)函數(shù)為
function add_mux2 :
t_update = a + b;
d_update = t * c;
t = t_update;
d = d_update;
當(dāng)阻塞賦值和非阻塞賦值混合的時(shí)候,也遵循同樣的規(guī)則。例如
always @(a, b, c) begin : add_mux3
t <= a + b;
d = t * c;
end
上述代碼轉(zhuǎn)化后的事件響應(yīng)函數(shù)為
function add_mux3 :
t_update = a + b;
d = t * c;
t = t_update;
對(duì)信號(hào)的賦值會(huì)產(chǎn)生一個(gè)事件,事件表示被賦值的信號(hào)發(fā)生了變化。如果有其他的過程塊依賴于被賦值的信號(hào),那么這個(gè)事件會(huì)被添加到事件隊(duì)列中;反之,這個(gè)事件會(huì)被忽略。事件的響應(yīng)時(shí)間為當(dāng)前時(shí)間加Δ。賦值語句是仿真引擎能夠持續(xù)運(yùn)行的關(guān)鍵。大部分always塊都是通過賦值語句向事件隊(duì)列添加新的事件的。?
延遲行為Verilog語言的延遲語句雖然不能綜合,但是在仿真過程中應(yīng)用得很多。延遲語句可以用在testbench中構(gòu)建時(shí)鐘信號(hào)和激勵(lì),也可以用在Verilog模塊中模擬實(shí)際電路的延遲。延遲語句可以出現(xiàn)在兩條賦值語句之間,也可以出現(xiàn)一條賦值語句中間。
#3 a = b; //延遲語句在賦值語句之間
a = #3 b; //延遲語句在賦值語句內(nèi)部
在賦值語句之間的延遲語句可以延遲語句的執(zhí)行。對(duì)于仿真器來說,處于賦值語句之間的延遲語句有兩個(gè)作用。首先,延遲語句會(huì)暫停當(dāng)前always塊的執(zhí)行,結(jié)束當(dāng)前的仿真階段,更新之前沒有完成的賦值,完成當(dāng)前事件的響應(yīng)并將控制前交還給事件隊(duì)列。然后,延遲語句會(huì)在事件隊(duì)列中添加一個(gè)新的事件。這個(gè)事件表示在延遲語句指定的時(shí)刻開始執(zhí)行這函數(shù)剩下的部分。例如
always @(a, b, c) begin : add_mux4
t <= a + b;
#1 d = t * c;
end
上述代碼轉(zhuǎn)化后的事件響應(yīng)函數(shù)為
function add_mux4_1 :
t_update = a + b;
t = t_update;
addEvent( curr_time + 1, add_mux4_2 );
function add_mux4_2 :
d = t * c;
Verilog文件中的1個(gè)過程塊被轉(zhuǎn)換為兩個(gè)函數(shù)。第一個(gè)函數(shù)add_mux4_1
對(duì)應(yīng)于延遲語句之前的部分,第二個(gè)函數(shù)add_mux4_2
對(duì)應(yīng)于延遲語句之后的部分。
add_event
是本文定義的一個(gè)原語,表示向事件隊(duì)列中添加一個(gè)事件。第一個(gè)參數(shù)表示事件響應(yīng)的時(shí)間,第二個(gè)參數(shù)表示響應(yīng)事件需要調(diào)用的事件響應(yīng)函數(shù)。從第三個(gè)參數(shù)開始,之后的參數(shù)會(huì)作為事件響應(yīng)函數(shù)的參數(shù),傳遞給事件響應(yīng)函數(shù)。
add_event( curr_time + 1,?
add_mux4_2?)
表示在當(dāng)前時(shí)間(curr_time
)后1個(gè)時(shí)間單位的時(shí)候響應(yīng)這個(gè)事件。事件需要調(diào)用add_mux4_2
函數(shù)。響應(yīng)函數(shù)不需要額外的參數(shù)。在調(diào)用add_mux4_2
時(shí),信號(hào)t已經(jīng)完成更新。
在賦值語句中間的延遲語句將評(píng)估和更新階段分割到兩個(gè)時(shí)刻進(jìn)行。評(píng)估過程仍然在語句執(zhí)行的時(shí)候進(jìn)行,但是更新過程延后到延遲語句指定的時(shí)刻進(jìn)行。延遲語句是否阻塞過程塊的執(zhí)行,取決于賦值語句本身。如果是阻塞賦值語句,賦值語句中間的延遲語句會(huì)阻塞過程塊的執(zhí)行;如果是非阻塞賦值,延遲語句不會(huì)阻塞過程塊的執(zhí)行。例如
always @(a, b, c) begin : add_mux5
t <= #1 a + b;
d = #2 t * c;
end
上述代碼轉(zhuǎn)化后的事件響應(yīng)函數(shù)為
function add_mux5_1:
t_update = a + b; // 1
d_update = t * c; // 2
addEvent( curr_time + 1, update_t, t_update );
addEvent( curr_time + 2, add_mux5_2, d_update );
function update_t( t_update ) :
t = t_update; // 3
function add_mux5_2( d_update ) :
d = d_update; // 4
如果沒有延遲語句,事件響應(yīng)函數(shù)的執(zhí)行順序應(yīng)該是 1->2->4->3。由于第一個(gè)語句中的延遲語句,語句3需要在當(dāng)前時(shí)刻之后1個(gè)時(shí)間單位時(shí)執(zhí)行,即update_t
。t_update
作為事件響應(yīng)函數(shù)的參數(shù),在update_t
中更新給信號(hào)t。由于第二個(gè)語句中的延遲語句,過程塊被打斷為兩個(gè)部分,第二個(gè)函數(shù)需要在當(dāng)前時(shí)刻之后2個(gè)時(shí)間單位時(shí)執(zhí)行,即add_mux5_2
。add_mux5_2
需要使用d_update
作為參數(shù)。理解到這一層,就可以處理更加復(fù)雜的波形了。例如下面這一段代碼。
module test;
reg x,y,z;
assign #25 a = 1;
always begin
#20;
x = #10 a;
#3 y = a;
#3 z = a;
#7;
end
endmodule
經(jīng)過仿真器的轉(zhuǎn)換,上面的Verilog語句會(huì)形成如下的事件響應(yīng)函數(shù)。
function assign1 :
a = 1;
function always1_1 :
addEvent( curr_time + 20, always1_2 );
function always1_2 :
x_update = a;
addEvent( curr_time + 10, always1_3, x_update );
function always1_3( x_update ) :
x = x_update;
addEvent( curr_time + 3, always1_4 );
function always1_4 :
y = a;
addEvent( curr_time + 3, always1_5 );
function always1_5 :
z = a;
addEvent( curr_time + 7, always1_6 );
function always1_6 :
addEvent( curr_time + delta, always1_1 );
在仿真開始時(shí)候,首先向事件隊(duì)列中添加兩個(gè)事件,分別是在0+25時(shí)刻調(diào)用assign1
,以及在0+0時(shí)刻調(diào)用always1_1
。事件響應(yīng)過程如圖4所示。always過程塊被延遲語句分割成了6個(gè)響應(yīng)函數(shù)。每個(gè)部分都向事件隊(duì)列添加能夠觸發(fā)下一個(gè)響應(yīng)函數(shù)的事件。信號(hào)x
的第1次評(píng)估發(fā)生在20時(shí)刻,而第1次更新發(fā)生在30時(shí)刻,所以信號(hào)x的第一次賦值仍為X
。直到第2次評(píng)估時(shí)(63時(shí)刻)才能獲得有效的信號(hào)1
,并且在73時(shí)刻更新給信號(hào)x。
?
圖4 示例過程的事件隊(duì)列響應(yīng)過程和波形圖需要說明的是,雖然本文提供了一種思路能夠比較輕松地理解行為級(jí)描述的執(zhí)行過程,但是仍然不建議大家在過程塊中混用阻塞賦值和非阻塞賦值?;煊觅x值語句是危險(xiǎn)的。
?
Assign賦值前面介紹的側(cè)重于過程塊。對(duì)于Assign賦值語句,原理其實(shí)是也一樣的。例如
assign a = #5 b & c;
這條assign語句同樣可以看做一個(gè)事件響應(yīng)函數(shù)。這個(gè)函數(shù)綁定的事件是信號(hào)b
或信號(hào)c
發(fā)生變化。延遲語句的效果也是一樣的。延遲語句將評(píng)估和更新過程分開。當(dāng)信號(hào)b
或信號(hào)c
發(fā)生變化時(shí)進(jìn)行評(píng)估,并在事件隊(duì)列中添加一個(gè)新的更新事件。5個(gè)時(shí)間單位之后,響應(yīng)更新事件,將評(píng)估的值更新給信號(hào)a
。
轉(zhuǎn)換后的事件響應(yīng)函數(shù)如下。
function assign1:
a_update = b & c;
addEvent( curr_time + 5, update_a, a_update );
function update_a( a_update ) :
a = a_update;
?
?
調(diào)試?
Verilog仿真器普遍提供了Verilog代碼的調(diào)試能力,比如斷點(diǎn)和單步運(yùn)行。在VCS、ModelSim、Vivado和Quartus中都能找到調(diào)試模式。斷點(diǎn)和單步運(yùn)行是典型的軟件調(diào)試手段,是軟件工程師的看家本領(lǐng)。但是對(duì)于硬件來說,斷點(diǎn)和單步運(yùn)行卻是不可理解的,因?yàn)橛布遣⑿械摹H绻麑帱c(diǎn)理解為硬件電路在某一個(gè)時(shí)刻的狀態(tài),那么此時(shí)應(yīng)該有多條語句被同時(shí)中斷。硬件電路不會(huì)像軟件一樣在某個(gè)函數(shù)中中斷并且單步執(zhí)行,而且其他過程塊或語句毫無影響。 ?前面已經(jīng)介紹過,Verilog并不是可執(zhí)行語言。真正的可執(zhí)行仿真程序是由仿真器提供的仿真框架源代碼和由Verilog語言轉(zhuǎn)換而來的仿真程序源代碼構(gòu)成的。實(shí)際上,Verilog語言調(diào)試的斷點(diǎn)并不是添加給硬件的或者Verilog源文件的,而是添加到可執(zhí)行仿真程序中對(duì)應(yīng)的事件響應(yīng)函數(shù)的。單步調(diào)試的對(duì)象也是可執(zhí)行仿真程序中的事件響應(yīng)函數(shù)。所以,Verilog代碼可以引入斷點(diǎn)和單步調(diào)試。 ?在進(jìn)一步解釋Verilog調(diào)試器的機(jī)制之前,必須先解釋一下軟件調(diào)試器是如何調(diào)試程序的。為了使得可執(zhí)行文件可調(diào)試,編譯器會(huì)在可執(zhí)行程序中添加調(diào)試信息。以C語言為例,編譯器會(huì)在可執(zhí)行文件中添加調(diào)試信息(如圖5所示)。添加的位置是在對(duì)應(yīng)于C語言語句的匯編代碼段起始的位置。在進(jìn)行軟件調(diào)試的時(shí)候,軟件調(diào)試器會(huì)在有調(diào)試信息的地方暫停(比如0x4005a5)。進(jìn)行單步調(diào)試的時(shí)候,每一步也都是停在C語言語句開始的地方(比如0x4005bf)。 ?圖5 可調(diào)試程序中添加的調(diào)試信息(利用objdump命令得到) ?Verilog調(diào)試器的作用就是將可執(zhí)行仿真程序和Verilog語言對(duì)應(yīng)起來。一種思路是將編譯器插入的調(diào)試信息與Verilog語言對(duì)應(yīng)起來。編譯器插入的調(diào)試信息是對(duì)應(yīng)于可執(zhí)行的仿真源文件,而這些源文件是由仿真器生成的。所以Verilog調(diào)試器可以獲得調(diào)試信息與Verilog語句的對(duì)應(yīng)關(guān)系。另一種思路是通過編譯器直接給可執(zhí)行仿真程序添加與Verilog語言對(duì)應(yīng)的調(diào)試信息。這樣Verilog調(diào)試器從可執(zhí)行程序就可以獲得必要的信息,而不需要額外的消息來源。 ?Verilog調(diào)試器只能對(duì)可以與Verilog語言對(duì)應(yīng)的代碼部分添加斷點(diǎn),也就是只能對(duì)事件響應(yīng)函數(shù)添加斷點(diǎn)。Verilog調(diào)試器不能調(diào)試由仿真器提供的仿真框架源代碼。當(dāng)單步調(diào)試遇到always塊結(jié)束或者assign語句之后,調(diào)試器不會(huì)進(jìn)入仿真引擎,而是直接跳轉(zhuǎn)到下一個(gè)事件響應(yīng)函數(shù)。此外,Verilog調(diào)試器還限制了斷點(diǎn)和單步調(diào)試的粒度只能以Verilog語句為單位,而不能進(jìn)一步縮小粒度到可執(zhí)行仿真程序的語句甚至匯編層面。 ?圖6 Verilog程序加斷點(diǎn)的過程 ?以圖6中的Verilog程序?yàn)槔?jīng)過Verilog仿真器得到右邊所示的仿真源文件,再經(jīng)過編譯得到可執(zhí)行程序。在左邊Verilog程序中的一行設(shè)置了一個(gè)斷點(diǎn)(圖5中左邊第7行),這個(gè)斷點(diǎn)實(shí)際上是設(shè)置在右邊的可執(zhí)行程序的事件響應(yīng)函數(shù)always1_4中的(圖5中右邊第2行)。每當(dāng)仿真程序運(yùn)行到always1_4時(shí)就觸發(fā)中斷,暫停程序執(zhí)行。通過仿真器添加的調(diào)試提示信息,調(diào)試器能夠知道中斷的位置是Verilog語言的第7行,從而在圖形界面上顯示。 ?從斷點(diǎn)的位置開始單步調(diào)試。從可執(zhí)行仿真程序的層面來說應(yīng)該在第3行暫停,并且呈現(xiàn)第3行執(zhí)行后狀態(tài)。但是,Verilog調(diào)試器會(huì)過濾仿真框架的程序,也就是過濾掉無法對(duì)應(yīng)到Verilog程序的語句。所以,可執(zhí)行程序不會(huì)在第3行之后暫停,而是繼續(xù)執(zhí)行。第3行結(jié)束后,程序從事件響應(yīng)函數(shù)返回,進(jìn)入仿真框架的部分。仿真器框架的代碼也會(huì)被調(diào)試器忽略,直到仿真程序進(jìn)入下一個(gè)事件響應(yīng)函數(shù)。最終,程序進(jìn)入always1_5。調(diào)試器會(huì)在第6行暫停,并且將中斷的位置對(duì)應(yīng)到Verilog軟件的第8行。程序運(yùn)行的標(biāo)志會(huì)顯示在圖6中第8行的位置。 ?以上的過程對(duì)于用戶來說都是不可見的。從用戶的角度看來,只能看到程序指針從第7行跳到第8行,并且第7行語句的效果在波形圖上展現(xiàn)了出來。這就是Verilog語言調(diào)試背后隱藏的過程,其核心仍然是軟件調(diào)試。?
結(jié)語?
本文的初衷是提供通過仿真器理解Verilog語言的思路。文中關(guān)于Verilog仿真器的描述采用了最簡(jiǎn)單、最直接的思路,當(dāng)然也是效率最低的。實(shí)際的仿真器會(huì)通過各種軟件技巧進(jìn)行優(yōu)化,提高仿真效率。文中使用的一些概念借鑒自SystemC,比如仿真階段和“評(píng)估-更新”機(jī)制。電路仿真器的設(shè)計(jì)思路和概念都是類似的或者相通的,可以觸類旁通。 ?如果有讀者想進(jìn)一步理解Verilog仿真器,不妨看一下開源Verilog仿真器iVerilog的源碼。此外,SystemC也是一套很好的硬件電路仿真框架,建議學(xué)習(xí)SystemC標(biāo)準(zhǔn)。IEEE的SystemC標(biāo)準(zhǔn)會(huì)闡述SystemC需要的仿真引擎以及編程規(guī)范。 ?作者才疏學(xué)淺,掛一漏萬,請(qǐng)大家多批評(píng)指正。 ? ?作者簡(jiǎn)介
?
作者:王君實(shí)?
電子科技大學(xué)博士。主要研究方向?yàn)槠匣ヂ?lián)結(jié)構(gòu)、計(jì)算機(jī)體系結(jié)構(gòu),已在IEEE Transactions on Computers (CCF A 類期刊)等高水平期刊和CODES+ISSS、ISCAS等頂級(jí)會(huì)議上發(fā)表高水平論文10 余篇,申請(qǐng)專利4項(xiàng)。長(zhǎng)期從事片上系統(tǒng)建模和模型開發(fā)工作。博士期間主導(dǎo)開發(fā)了多核片上系統(tǒng)高層次建模與設(shè)計(jì)工具ESYSim。該系列的工具獲得中國(guó)研究生電子設(shè)計(jì)大賽集成電路專項(xiàng)賽(也就是現(xiàn)在的中國(guó)研究生“創(chuàng)芯”大賽的前身)特等獎(jiǎng)?,F(xiàn)從事CPU性能建模和優(yōu)化工作。?
審核編輯 :李倩
?
評(píng)論
查看更多