ModelSim入門FPGA仿真基礎教程之一:軟件介紹

編寫這個教程之前,爲了讓不同水平階段的人都能閱讀,我儘量做到了零基礎入門這個目標,所有的操作步驟都經過縝密的思考,做到了詳細再詳細的程度。

如果您是FPGA開發方面的初學者,那麼這個教程一定能夠幫助你在仿真技術上越過新人的臺階;如果您是FPGA開發的老手,這篇文檔也並非對您沒有幫助,您可以把教程發給其他剛入門的同事,免去您親自上陣指導的麻煩,把主要的精力放在更有價值的地方。

一、FPGA設計仿真驗證簡介

嚴格來講,FPGA設計驗證包括功能仿真、時序仿真和電路驗證,它們分別對應整個開發流程的每一個步驟。仿真是指使用設計軟件包對已實現的設計進行完整的測試,並模擬實際物理環境下的工作情況。

功能仿真是指僅對邏輯功能進行模擬測試,以瞭解其實現的功能是否滿足原設計的要求,仿真過程沒有加入時序信息,不涉及具體器件的硬件特性,如延時特性等,因此又叫前仿真,它是對HDL硬件描述語言的功能實現情況進行仿真,以確保HDL語言描述能夠滿足設計者的最初意圖。

時序仿真則是在HDL可以滿足設計者功能要求的基礎上,在佈局佈線後,提取有關的器件延遲、連線延時等時序參數信息,並在此基礎上進行的仿真,也成爲後仿真,它是接近於器件真實運行狀態的一種仿真。

二、仿真軟件ModelSim及其應用

HDL的仿真軟件有很多種,如VCS、VSS、NC-Verilog、NC-VHDL、ModelSim等,對於開發FPGA來說,一般是使用FPGA廠家提供的集成開發環境,他們都有自己的仿真器,如Xilinx公司的ISE,Altera公司的Quartus II等,但是這些廠家開發的仿真器的仿真功能往往比不上專業的EDA公司的仿真工具,如ModelSim AE(Altera Edition)、ModelSim XE(Xilinx Edition)等。Quartus II設有第三方仿真工具的接口,可以直接調用其他EDA公司的仿真工具,這極大地提高了EDA設計的水平和質量。

ModelSim是Model Technology(Mentor Graphics的子公司)的HDL硬件描述語言的仿真軟件,該軟件可以用來實現對設計的VHDL、Verilog HDL 或是兩種語言混合的程序進行仿真,同時也支持IEEE常見的各種硬件描述語言標準。

無論是從使用界面和調試環境,還是從仿真速度和效果上看,ModelSim都可以算的上是業界比較優秀的HDL語言仿真軟件。它是唯一的單內核支持VHDL和Verilog HDL混合仿真的仿真器,是做FPGA/ASIC設計的RTL級和門級電路仿真的好選擇,它採用直接優化的編譯技術,Tcl/Tk技術和單一內核仿真技術,具有仿真速度快,編譯的代碼與仿真平臺無關,便於IP核的保護和加快錯誤程序定位等優點。

ModelSim分幾種不同的版本:ModelSim SE、ModelSim PE、ModelSim LE和ModelSim OEM,其中的SE、PE、LE是其最高版本,編譯速度是所有版本中最快的,而OEM版本就是集成在FPGA廠家設計工具中的版本,它們專門和某個廠家的FPGA配套來使用,如後面使用到的ModelSim AE就是專門針對Altera公司QuartusII的配套的OEM產品。

三、ModelSim的仿真流程

ModelSim不僅可以用於數字電路系統設計的功能仿真,還可以應用於數字電路系統設計的時序仿真。 ModelSim的使用中,最基本的步驟包括創建工程、編寫源代碼、編譯、啓動仿真器和運行仿真五個步驟,仿真流程如圖1所示:

在這裏插入圖片描述
圖1 ModelSim仿真的基本流程(基於工程的)

這個是基於工程的流程,還有一種是基於庫文件的,和基於工程的相比,它需要自己創建工作庫,另外關閉ModelSim軟件後,下次還得自己手動打開設計文件,而基於工程的就不會這樣,工程是一直保持的狀態,不用每次啓動軟件後再手工加載,除非我們自己關掉這個工程。還有另外兩個流程,這裏不提了,詳細內容可參看ModelSim AlteraTutorial.PDF 。在軟件的安裝目錄的DOCS文件夾內是全部的參考文檔,包括使用手冊等。

四、仿真測試文件(Test Bench)程序的設計方法

隨着設計量和複雜度的不斷增加,數字驗證變得越來越難,所消耗的成本也越來越高,面對這種挑戰,驗證工程師必須依靠相應的驗證工具和方法。對於大型的設計,比如上百萬門的設計驗證,工程師必須使用一整套規範的驗證工具,而對於較小的設計,使用具有HDL Test Bench的仿真器是一個不錯的選擇。

一般來說,Test Bench使用工業標準VHDL或者Verilog HDL語言來描述,簡單的Test Bench通過調用用戶設計的功能模塊,然後進行仿真,較爲複雜的Test Bench還包括一些其他的功能,比如包含特定的激勵向量或者進行實際輸出與期望的比較等。

在開始寫Test Bench之前,很重要的一點就是要設計實例化DUT(Design Under Test,即就是被測元件),還要詳細瞭解整個的測試計劃和測試案例。整個的測試Test Bench環境如圖2所示:

在這裏插入圖片描述
圖2 Test Bench的測試環境

從圖中可以看見,Test Bench和被測對象Counter構成了一個封閉的循環,Test Bench負責向被測元器件的輸入端口提供激勵(時鐘)和一些控制信號(復位和置位信號),另外Test Bench還監測被測元器件的輸出端口所輸出的信號值是否和我們的設計預期相符,並把監測的情況顯示給我們。

由於Test Bench程序和被測對象構成了一個封閉的循環,因此Test Bench的輸入端口需要與被測對象的輸出端口連接,Test Bench的輸出端口則要與被測對象的輸入端口相連接。所以在端口的定義上,Test Bench程序需要和被測對象相對應。

被測元器件是一個已經設計好的電路或系統,Test Bench是用元件例化語句將其嵌入程序中。VerilogHDL測試平臺是一個設有輸入輸出端口的設計模塊,被測元器件的輸入端定義爲reg(寄存器)型變量,在always塊或initial塊中賦值(產生測試條件),被測元器件的輸出端定義爲wire(線網)型變量,產生相應輸入變化的輸出結果(波形)。

4.1 組合邏輯電路Test Bench的設計

組合邏輯的設計驗證,主要就是檢查設計結果是不是符合該電路的真值表功能,因此在編寫組合邏輯Test Bench時,用initial塊把被測電路的輸入按照真值表提供的數據變化作爲測試條件,就能實現Test Bench的設計。

例1. 編寫一位全加器的Test Bench程序

全加器的A和B兩個是1位二進制加數的輸入端,CI是低位來的進位輸入端,CO是向高位進位的輸出端,SO是全加器的本位和值。

用Verilog HDL語言編寫的全加器程序adder.v如下:

//-----------------------------------------------------

// DesignName : adder1

// FileName : adder1.v

//Function : 1 bit full adder

//Coder : Cheng xu

//-----------------------------------------------------

moduleadder1(

a ,

b ,

ci ,

so ,

co

);

// Portdeclarations

input a ;

input b ;

input ci ;

output so ;

output co ;

//InternalVariables

wire a ;

wire b ;

wire ci ;

wire so ;

wire co ;

//CodeStarts Here

assign {co, so} = a + b + ci;

endmodule

根據全加器的真值表寫的全加器的Test Bench程序test_adder1.v如下:

//test_adder1.v

`timescale1ns/1ns

moduletest_adder1;

wire so ;

wire co ;

reg a ;

reg b ;

reg ci ;

adder1 U(

      .a(a),

      .b(b),

      .ci(ci),

      .so(so),

      .co(co)

      );

initial

begin

      #20 a = 0; b = 0; c = 0;

      #20 a = 0; b = 0; c = 1;

      #20 a = 0; b = 1; c = 0;

      #20 a = 0; b = 1; c = 1;

      #20 a = 1; b = 0; c = 0;

      #20 a = 1; b = 0; c = 1;

      #20 a = 1; b = 1; c = 0;

      #20 a = 1; b = 1; c = 1;

      #200 $stop;

end

endmodule

下面我們就以ModelSim爲EDA平臺,仿真上面的程序。這一講先暫時不講仿真測試的方法,留到下一講再來詳述,這一講主要講述的內容是Test Bench程序的編寫方法,故現在僅僅給出仿真的波形圖,全加器的仿真波形如圖3所示:
在這裏插入圖片描述
現在對着這個圖,我們返回來再來看看我們編寫的test_adder1.v這個Test Bench程序究竟完成了哪些工作,是不是按照我們的要求來工作的:

①首先看程序第二行的timescale1ns/1ns這句代碼,這個是時間尺度指令,它是用來定義模塊的仿真時間單位和時間精度的,其使用格式爲:timescale 仿真時間單位/時間精度,用於說明仿真時間單位和時間精度的數字只能是1、10或100,不能爲其它的數字,單位可以是s、ms、us、ns、ps和fs。仿真時間單位是指的模塊仿真時間和延時的基準單位,也就是說只有定義了仿真時間單位,程序中的延時符號"#"纔有意義,如程序中的一行 #20 a = 0; b = 0; c = 0; 前面的 #20 就是延時20個時間基準單位,按照程序中的1ns這個基準,就延時了20個ns。需要說明的是該行程序的下一行 #20 a =0; b = 0; c = 1; 前面的延時20個ns是相對於前一個的延時來說的,也就是第二行在第一行完了之後延時20ns執行。這時候再看看仿真的波形圖就不難理解最開始的線爲什麼是紅色而不是正常的綠色的原因了,因爲我們在程序中begin的下一行就是 #20 a =0; b = 0; c = 0; 它前面的延時20個ns是相對於begin的延時,也就是說程序開始的時候是什麼都不做的,輸出爲不確定的值,過了20個ns纔將全0賦給了a、b和ci,這個時候纔是最開始的綠線的部分。

②在Test Bench程序中,把全加器的輸入a、b和ci定義爲了reg型變量,把輸出so和co定義爲了wire型變量,這個和被測元件的定義情況剛好是反的,這樣也說明了TestBench程序和被測元件是封閉的一個循環。用元件例化語句adder1U( .a(a), .b(b), .ci(ci), .so(so), .co(co) ) ; 把全加器設計電路嵌入到Test Bench程序中。

③程序的後面有一句 #200 stop;仿仿仿stop; 這個是一個系統任務,用來暫停仿真過程的,將控制權交還給用戶,用戶在取得控制權以後可以輸入其它的控制命令或者查看仿真結果等,之後可以從暫停的地方恢復仿真過程。stop有兩種表達形式,帶參數的和不帶參數的:

$stop;

$stop(n); //n可以取0、1或2

不帶參數的stopstop等同於stop(0),在暫停時不輸出任何信息;stop1仿stop(1)在暫停時輸出當前仿真時刻和暫停處在程序中的位置;stop(2)不僅有$stop(1)的作用,還能輸出仿真時佔用內存大小和CPU時間。

而用於退出仿真過程的系統任務是 $finish,我們在點擊Run(開始運行)的時候,系統會詢問我們是否要結束仿真,假如我們選"是",這個系統任務會把ModelSim軟件在完成仿真後關閉,假如我們選"否",則可以繼續留在仿真界面。

和一位全加器的真值表進行全部的對比後發現和該仿真波形完全一致,仿真結束。

4.2 時序邏輯電路Test Bench的設計

時序邏輯電路Test Bench的設計要求和組合邏輯電路基本相同,主要區別在於時序邏輯電路Test Bench軟件中,需要用always塊語句生成時鐘信號。

例2所編寫的程序,就是在下一講當中的實例,利用這個實例來講解軟件的全部操作流程和使用方法,這一講先來分析這個程序以及和它相配套的Test Bench程序,看看它們是否能夠按照我們設計期望的那樣輸出仿真結果。

例2. 編寫8位加法器的Test Bench程序

第一個文件,源程序:

//-----------------------------------------------------

// DesignName : counter8

// FileName : counter8.v

//Function : 8 bits counter with asyncclear and sync load

//Coder : Cheng xu

//-----------------------------------------------------

modulecounter8(

clk ,

aclr ,

load ,

load_din ,

dout

);

// Portdeclarations

input clk ;

input aclr ;

input load ;

input [7:0] load_din ;

output [7:0] dout ;

//InternalVariables

wire clk ;

wire aclr ;

wire load ;

wire [7:0] load_din ;

wire [7:0] dout ;

reg [7:0] counter = 0 ;

//CodeStarts Here

always @(posedge clk or negedge aclr)

if(!aclr)

      counter <= 0;

else if(load == 1)

      counter <= load_din;

else

      counter <= counter + 1;

assigndout = counter;

endmodule

第二個文件,Test Bench仿真測試程序:

//test_counter8.v

`timescale1ns/1ns //注意最前面的符號是數字鍵"1"左邊的那個符號,不是單引號

moduletest_counter8;

reg clk ;

reg aclr ;

reg load ;

reg [7:0] load_din ;

wire [7:0] dout ;

initial

begin

      clk = 0;

      aclr = 1;

      load = 0;

      load_din = 0;

#120 aclr = 0;

#40 aclr = 1;

#20 load = 1;

      load_din = 100;

#20 load = 0;

#100 $stop;

end

always#10 clk = ~clk;

counter8U(

      .clk(clk),

      .aclr(aclr),

      .load(load),

      .load_din(load_din),

      .dout(dout)

      );

endmodule

在這裏插入圖片描述
八位加法器仿真波形圖

現在就對着這個圖,來看看我們編寫的test_counter8.v文件是不是按照我們的設計要求的那樣來工作的:

①和組合邏輯的設計一樣,我們要在test_counter8.v中例化被測元件counter8,把八位加法器元件嵌入到test_counter8.v這個Test Bench中來。

②和組合邏輯不同的是,我們要利用always #10 clk = ~clk; 這個語句來產生週期爲20個時間基準單位(1ns)的時鐘(方波),即就是20ns的時鐘信號。注意:時鐘只能用always塊才能生成,但要在initial塊中賦給時鐘的初始值(如clk=0或clk=1),如果不設置時鐘初始值,則在仿真的時鐘輸出端是一個未知x(不變,就是例1中的那段紅線了)。

③在initial塊中生成復位信號和加載信號,注意:一定要給復位信號和加載信號賦給初始值,否則和不設置時鐘初始值一樣會出現問題的。

④在initial塊的begin語句一開始就設置相關的初始值是一個好習慣。

和test_counter8.v進行全部的對比後發現和該仿真波形完全一致,仿真結束。

至此,第一講全部內容結束,主要是講了Test Bench程序的編寫方法,下一講我們將介紹ModelSim軟件的使用方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章