2012年7月8日 星期日

數位電路之後,verilog系列文(4)

寫testbench

testbench是verilog另一個很好用的功能,一般來說,如果設計的電路是要完成某個特定的演算法,比如我們在實驗中要實作256bits的montgomery algorithm,把電路透過quartus合成、燒進FPGA執行,透過Logic analyser分析行為實在太曠日費時(那時寫的不好,合成一次就要30分鐘= =)。
這時候testbench出現了,testbench提供了一個方式,讓我們能利用軟體模擬電路的行為,看看電路的反應,每次模擬只需要幾秒鐘,就可以得到電路的行為。

這篇文章就是要來談testbench的譔寫,會分成以前幾個部分:
1. 軟體安裝:
2. testbench譔寫:
3. 看模擬結果:
4. 常見問題:
5. 相關資源:

1. 軟體安裝:
為了要進行verilog code的模擬,我們需要安裝verilog的模擬軟體,有不少公司都有相關的軟體,如學校工作站安裝Cadence公司的NCverilog,但這是需要付費的,也要連結到工作站才能使用;這裡我推薦使用另一套免費提供的開源模擬軟體:iverilog,可以裝在Linux或Windows上使用,如果是在Ubuntu或Debian系列可以使用apt來安裝:
$apt-get install iverilog
個人使用Archlinux的話,要找AUR,請見相關資源。

另外還要安裝gtkwave,才能看到電路輸出訊號的波型檔。
$apt-get install gtkwave
$pacman –S gtkwave

2. testbench譔寫
其實,testbench也就是一個verilog module,用來產生輸入電路的信號,如果把電路燒在FPGA裡面,輸入的信號可能是來自晶體振盪器的時脈信號,按鈕輸入、信號產生器的輸入……,但寫testbench時,就要自己寫信號的輸入,以及時脈信號的模擬。
這個testbench的module會將待測的電路整個包進去,然後把信號輸給它。
注意在譔寫時,給input 的連接需要用reg,輸出則使用wire來連接。

下面是我在測試Montgomery algorithm module的testbench,目標測試的module的輸入是3個256bits的數A,B,N,n_start降低就會開始運算;輸出則是告知計算完成的n_ready與輸出結果的S:


`timescale 1ns/100ps
`define CYCLE 10

module Montgomery_tb ();

//**************************** wire & reg**********************//
reg clk;
reg [255:0] A;
reg [255:0] B;
reg [255:0] N;
reg n_rst;
reg n_start;

wire [255:0] S;
wire n_ready;
//**************************** module **********************//

Montgomery lalala(.clk(clk),.A(A),.B(B),.N(N),.S(S),.n_rst(n_rst),.n_start(n_start),.n_ready(n_ready));

//**************************** clock gen **********************//
always begin #(`CYCLE/2) clk = ~clk; end

//**************************** initial and wavegen **********************//
initial begin
 $dumpfile("montgomery.vcd");
 $dumpvars;
end

//**************************** testdata **********************//
initial begin
 clk = 1'b0;
 A = 256'd0;
 B = 256'd0;
 N = 256'd0;
 n_rst = 1'b0;
 n_start = 1'b1;
 #1 n_rst = 1'b1;
 A = 256'h4;
 B = 256'h8;
 N = 256'd13;
 #100 n_start = 1'b0;
 #10 n_start = 1'b1;
 #100000 $finish;
end

endmodule

第一行的
`timescale 1ns/10ps
告訴iverilog等模擬軟體,以前者(1ns)為單位,以後者(10ps)的時間,查看一次電路的行為。
首先要先連接測試信號,在Module的地方將待測的電路連起來,再次提醒輸入要接reg,這是我們可以指定其值的地方,輸出接wire,只能觀看。
以下這幾行也是很重要的,用來產生給該module的時脈信號:
`define CYCLE 10
reg clk;
always begin #(`CYCLE/2) clk <= ~clk; end
initial begin clk = 1'b0; end
在testbench裡面,#(數字)代表經過多少delay,initial則是在電路開始時賦值,否則會如下圖一樣,輸出預設為X的信號:


這樣就能產生一個週期為CYCLE* (1ns)長的信號。

initial begin
 $dumpfile("montgomery.vcd");
 $dumpvars;
end
則是讓iverilog把記錄的波型寫入montgomery.vcd中,dumpvars則可以指定要記錄哪些信號的輸出,一般,我都不寫直接記下全部的信號。
以上都算是前置作業,最後,就可以對輸入的信號進行給值:
#1 n_rst = 1'b1;
A = 256'h4;
B = 256'h8;
N = 256'd13;
#100 n_start = 1'b0;
#10 n_start = 1'b1;
#100000 $finish;
A給4, B給8,N給13,嗯…非常白爛的測資,哎呀舉例啦
在100ns時把n_start降下來,10ns後升回去,100000ns讓它慢慢算。

3. 看模擬結果
寫好testbench後,就可以用iverilog編譯,記得要把testbench跟所有相關的v檔都包進去。
$iverilog 所有的.v檔 -o 執行檔名稱
這樣iverilog會產生syntax error的訊息,喔不是,是一個執行檔,再執行這個執行檔。
如果有寫dumpfile的話,就會產生相對應的波型檔囉,這一切都是在轉瞬間完成。
用gtkwave打開該波型檔,如下圖所示,這時候可以從左邊把想要看的信號加到右邊去,階層下的module都可以打開,看裡面的信號波型。
像現在我把A,B,N拉出來,看到他們的確變成4,8,13這三個白爛的測資,在信號上點右鍵可以改變表示的方式,我現在開的是16進位,所以13是d。


這樣是不是比直接燒電路快多了呢?
祝大家testbench愉快!

4. 常見問題:
    1. 為什麼執行檔一執行下去就停不下來了?
如果testbench是你從頭到尾寫完的話,有可能是忘了加$finish,個人因為都改用模板去改,這個問題很少出現。
另一個可能就是你的combinational circuit中出現了,條件中修改了條件;這時候在該時間點上,會讓模擬軟體陷到模擬的迴圈中,也就停不下來了。
    2. 其實我也不知道有什麼常見問題…….

5. 相關資源:
    1. iverilog官網:http://iverilog.icarus.com/
    2. Archlinux iverilog AUR:https://aur.archlinux.org/packages.php?ID=3552

2012年7月6日 星期五

數位電路之後,verilog系列文(3)


寫一個module

在上一篇裡面,我們談過了verilog 三大塊的寫法,以及常見的verilog錯誤,那現在就來看看,一個verilog module的構成,
其實一個module,就好像在寫一個完整的電路,有哪些input, output,要有多少個register,之間的接線,甚至要包住其他的module,是一塊很完整的block。
下面是我在vim裡預設的module格式,大家可以參考,不過我覺得其實這部分每個人都有每個人的風格,有人覺得parameter的部分應該提到module之外,因為那些其實不是電路本體的部分,而是在編譯器編譯時就把code裡的變數代換掉了,所以最重要的還是自己習慣就好:)

module /*module name*/(
/*parameter*/
);

/*================================================================*
 * PARAMETER declarations
 *================================================================*/
/*================================================================*
 * REG/WIRE declarations
 *================================================================*/
/*================================================================* 
 * Module 
 *================================================================*/          
/*================================================================* 
 * Combinational circuit 
 *================================================================*/          
always @(*)begin
end  
/*================================================================*
 * Sequential circuit
 *================================================================*/ 
always @(posedge clk or negedge n_rst) begin
end 
/*================================================================* 
 * Output circuit
 *================================================================*/ 
always @(*)begin
end  
    
endmodule

1. inout宣告,行1~3:
第一行宣告module 名字就不用說了。
從第二行開始是input與output的宣告,其實這好像也沒什麼好說的,就直接宣告各個input 與output:
Ex: input clk_50M,
有兩點可以注意的是:
第一在top module,input與output的名字,最好能和原廠提供的pin assignment的qsf檔或是xls檔相對應,才能大量節省pin assignment的時間,記得剛開始實驗時,都會把input, output名字取得比較好記,像送給七段顯示器的秒數資料,我就叫second1, second2這樣,當然pin assignment 的時候就要一個一個去對照pdf 檔,把名字對應起來,這樣當然很智障= =。
好的做法是,取名就跟pin assignment一樣,如果覺得名字實在太鳥了,下面再用assign的方式,承接input的值或是對output給值。
當然也可以直接去改pin assignment的檔案,不過何苦呢XD?

第二點是註記:
如果是top module,因為input output是來自於開發板上的各元件,通常就依照元件分群;如果是其他的module,則依信號給的對象分群,例如現在寫了一個「輸入為4bits的數,輸出為七段顯示器的信號」。則我會這樣註記,把對象分開:
//main
input clk,
input [3:0] number,
//seven segment
output [6:0] segment_signal

2. Parameter 和reg, wire,行5~10:
這個好像真的沒什麼好講的…
大概只有wire,如果是一個行為很簡單的wire,例如要連接到inout的輸出:
wire sram_data = (state == STATE_WRITE)? data_in_buf : 16'bz;
就可以直接補完。

3. Module,行11~13:
就連接module嘛 = =,好像也沒什麼好講的。

4. 電路設計,行14~28:
這裡包括了next state logic, sequential block, output logic,其實不一定每一塊都要有,看電路的設計。
據我強者同學phoning表示,DSD的建議是,next state logic跟output logic寫在一起,不過我習慣還是會分開寫;但我的確有在想,把sequential block的部分移到電路最後,因為這個部分通常是最不常改到的地方,我想這也是一樣,大家習慣就好。

我倒覺得這裡正好講一下reset的觀念,也就是code裡的n_rst:
在verilog裡面,因為寫的是電路,當電路通電的那一瞬間,所有register裡面的值都是亂七八糟的,而不是像c code,可以在宣告時寫int a=1,它就是1;偏偏這時候,verilog又用一個很容易誤會的關鍵字:initial。
所以就很常有人以為,只要寫:
initial begin
    State <= 0;
end
就可以讓reg回到原來值…(其實這部分個人不確定,因為這樣寫過,而且好像還真的能用)

比較正確的作法是:用一個開關之類的為reset信號
然後在sequential電路的部分都加上
if(~n_rst)begin     //要negedge reset或posedge reset隨你高興啦……
    state<=0;
end
else …………其他的內容
這樣一但發出reset 信號,所有的register都會清成起始值了。

5. 註解:
這個在prototype上面沒有,我也是最近才學到的,在寫完module,別忘了加上instance,把這個module該如何引用寫在最前面,方便其他人的引用啊,如下所示:
/* instance
module_name(
// front comment
.port1(), // back comment
.port2(),
.port3()
);
*/

2012年7月4日 星期三

數位電路之後,verilog系列文(2)


常見的verilog 譔寫錯誤:
感謝鄭為中大神的提醒,要寫這篇verilog常見錯誤文,也感謝鄭為中大神對我verilog觀念的澄清:)

譔寫verilog最常見的錯誤,當然就是syntax error……= =
當然這裡不討論這些,雖然他們很常出現,像忘了加分號、拼錯字之類的,我們延續上一篇對verilog結構的討論,再來看看,寫得不好的verilog code會造成怎麼樣硬體上的後果,與上一篇結構問題相同,需要轉成硬體的結果造成這個verilog獨有的錯誤。

這篇討論使用Altera Quartus II編譯時,兩種常見的錯誤:
1.產生latch
2.產生combinational loop

1.產生latch:
產生Latch最主要的原因是沒有把所有條件寫乾淨。
我們考慮電路合成的情形,當我們寫一個if,或者case,這些東西在電路內都會轉成mux,例如以下的code:
if(in=1) begin counter_next = counter +1; end
else begin counter_next = counter; end
這會變成下面的電路(好醜的multiplexer…..):

如果是case,就是多對一的mux。
例如常見的七段顯示器的code,輸入一個數,轉成相對應7個光點的輸出,這個大概就會合成類似,16對1的mux:
case(digit)
   4'd0: segment_out = 7'b1000000; //1 -&gt; dark 0-&gt; light
    4'd1: segment_out = 7'b1111001;//      ___a___
    4'd2: segment_out = 7'b0100100;//    |       |
    4'd3: segment_out = 7'b0110000;//   f|       |b
    4'd4: segment_out = 7'b0011001;//    |       |
    4'd5: segment_out = 7'b0010010;//    |_______|
    4'd6: segment_out = 7'b0000010;//    |   g   |
    4'd7: segment_out = 7'b1111000;//   e|       |c
    4'd8: segment_out = 7'b0000000;//    |       |
    4'd9: segment_out = 7'b0010000;//    |_______|
    default: segment_out = 7'b1111111;//      d
endcase

注意到,上面無論是if還是case,最後都有一個else/default,再次重申,verilog和一般的C是不一樣的,在C裡面的if 就是判斷條件,符合就跳到指定的程式碼,不符合就繼續執行;但verilog是要轉成電路的,你不指定else,mux的輸入要接給誰?
一般verilog的編譯器會指給輸出,也就是如下的電路:

如此有個訊號被鎖在這個mux裡面,就是基本的latch,雖然一般來說,這樣的latch對電路的行為影響不算太大,有時候充滿latch的code還是可以正常運作;但個人習慣上還是強烈建議把if, case全都加上else,消掉所有的latch;因為有latch 的code的行為較難預料,輸出變得依據”上一個狀態”來決定,而不是單純的combinational circuilt。

2.Combinational loop:
另一個情況是產生combinational loop。
理論上,用我上一篇所說的FSM和三大塊架構下去寫,是不會出現combinational loop的,三大塊的架構最主要的核心,就是把”變數” (state)和”變數的下一個狀態”(stata_next)的運算分開,在combinational 的部分就只靠state(和其他變數)去運算state_next。
所以說,在verilog 裡面,絕對絕對絕對沒有assign的左右兩邊是相同的狀況,這也是verilog最難讓人習慣的地方,因為一般程式寫a=a+1實在太常見了。
比如說
always @(*) begin
    state = (counter=2’d2)?state+1’b1:state;
end
這樣的code是絕對不可以出現的狀況,會產生另一個常見的combinational loop的警告。有時候這樣的code也可以存活,但出現combinational loop一般都會大大加重quartus編譯時的負擔,他會針對電路去做很多的模擬,確認電路工作沒有異常,造成編譯效率的下降,曾經遇過充滿combinational loop的code,編一次耗時40分鐘,修掉之後卻只需要10分鐘,影響之大由此可見;寫verilog時,養成良好的習慣,把”會受到自己狀態,而影響到下個狀態的變數”,都產生兩組,X,跟X_next,才不會把自己搞得暈頭轉向。