Verilog語法基礎講解之參數化設計


Verilog語法基礎講解之參數化設計

 

在Verilog語法中,可以實現參數化設計。所謂參數化設計,就是在一個功能模塊中,對於一個常量,其值在不同的應用場合需要設置為不同的置,則將此值在設計時使用parameter 關鍵字聲明,那么在上層模塊例化使用該功能模塊時,可以根據具體需求重新配置該常量的值,從而實現不同應用場合對對應常量的靈活調整。

 

以下為使用Verilog設計的一個控制LED閃爍燈的模塊代碼:

 

01    module counter(Clk,Rst_n,led);

02

03        input Clk;    //系統時鍾,50M

04        input Rst_n;    //全局復位,低電平復位

05        

06        output reg led;    //led輸出

07        

08        reg [24:0]cnt;    //定義計數器寄存器

09

10    //計數器計數進程    

11        always@(posedge Clk or negedge Rst_n)

12        if(Rst_n == 1'b0)

13            cnt <= 25'd0;

14        else if(cnt == 25'd24_999_999)

15            cnt <= 25'd0;

16        else

17            cnt <= cnt + 1'b1;

18

19    //led輸出控制進程

20        always@(posedge Clk or negedge Rst_n)

21        if(Rst_n == 1'b0)

22            led <= 1'b1;

23        else if(cnt == 25'd24_999_999)

24            led <= ~led;

25        else

26            led <= led;

27

28    endmodule

 

在此設計中,定義了一個名為cnt的計數器,該cnt在輸入時鍾"Clk"的驅動下,每個時鍾的上升沿執行一次自加操作,當計數到達24999999后,計數器清零,同時led驅動狀態發生翻轉。由於系統時鍾為50M,即周期為20ns,cnt每計數25000000次,即500ms,驅動led狀態翻轉一次,從而可以實現控制led以1s為周期進行亮滅閃爍。在這個系統中,我們可以看到,如果我們希望將閃爍速度進行更改,例如改為每50ms讓led翻轉一次,則需要在代碼中將"cnt == 25'd24_999_999"25'd24_999_999這個常量修改為25'd24_999_99

 

現在考慮另外一種情況,假如某個系統中有三個LED燈需要通過這種方式來進行驅動閃爍,每個LED燈的閃爍頻率還不一樣。現假設LED1閃爍頻率為1s,LED2為0.1s,LED3為0.01秒。那么這三個LED驅動模塊中,"25'd24_999_999"這個常量就需要分別修改為

 

LED1:"25'd24_999_999"

LED2:"25'd24_999_99"

LED3:"25'd24_999_9"

 

因此,必須獨立的設計三個這樣的模塊,然后在上層模塊中分別例化這三個模塊,以實現需求的功能。

 

獨立設計這樣三個模塊並分別例化,確實能夠完全實現上述假設系統需求的功能,但是通過這種方式設計出來的模塊,不具備通用性,如果要求發生變化,則還需要回到底層模塊中去修改對應的常量,而底層模塊中可能不止一個地方使用到了這個常量,例如上述代碼中,第14行和第23行就分別用到了這個常量。如果模塊中對此常量的使用次數較多,在修改的過程即增加了工作量,又極容易發生遺漏,從而導致錯誤。

 

而參數化設計的使用,則可以非常完美的解決這一問題。這里首先放上該設計使用參數化設計之后的該模塊代碼:

01    module counter(Clk,Rst_n,led);

02

03        input Clk;    //系統時鍾

04        input Rst_n;    //全局復位,低電平復位

05        

06        output reg led;    //led輸出

07        

08        reg [24:0]cnt;    //定義計數器寄存器

09        

10        parameter CNT_MAX = 25'd24_999_999;

11

12    //計數器計數進程    

13        always@(posedge Clk or negedge Rst_n)

14        if(Rst_n == 1'b0)

15            cnt <= 25'd0;

16        else if(cnt == CNT_MAX)

17            cnt <= 25'd0;

18        else

19            cnt <= cnt + 1'b1;

20

21    //led輸出控制進程

22        always@(posedge Clk or negedge Rst_n)

23        if(Rst_n == 1'b0)

24            led <= 1'b1;

25        else if(cnt == CNT_MAX)

26            led <= ~led;

27        else

28            led <= led;

29

30    endmodule

這里,我們使用parameter在第16行聲明了一個參數化常量"CNT_MAX",在第16行和第25行進行條件判斷時,直接判斷cnt的值是否與該參數值相等,而不再是直接寫具體數字的方式。到這里大家就可以看到,通過這樣一種方式,首先就能避免一個模塊中多次使用該常量而對修改設計帶來的隱患。因為當我們需要修改該常量的值以適應不同的應用要求時,直接修改這個parameter的值即可,從而可以避免因為該參數使用的地方過多而在修改時容易出現的遺漏的問題。

 

另外,在我們的系統中,假如也需要三個這樣的模塊來對應驅動三個LED,大家可能首先想到的就是將該代碼復制得到三個文件,並將其中的parameter值修改為對應需要的值即可。此種方式雖然能夠避免一個常量在模塊中被多次使用,修改時容易發生的遺漏問題,但仍然還是需要存在三個獨立的子模塊,並未大大減輕設計工作量。其實,修改parameter有更加快捷的方式,這種方式不需要我們針對每一個需求分別復制設計代碼並修改對應參數,只需要在上層模塊例化該模塊時,直接在例化的過程中修改該值即可。具體實現方法,這里以實例代碼的形式進行講解。

 

在上層模塊中,例化子模塊時直接修改其參數有兩種語法結構,這里我們首先看第一種:

01    module LED_flicker(

02        Clk,

03        Rst_n,

04        LED

05    );

06

07        input Clk;

08        input Rst_n;

09        output [1:0]LED;

10

11        counter    counter0(

12            .Clk(Clk),

13            .Rst_n(Rst_n),

14            .led(LED[0])

15        );

16        

17        counter    counter1(

18            .Clk(Clk),

19            .Rst_n(Rst_n),

20            .led(LED[1])

21        );

22        

23        defparam counter0.CNT_MAX = 24_999_99;

24        defparam counter1.CNT_MAX = 24_999_9;

25        

26    endmodule

 

這里,在頂層模塊中例化了兩個counter模塊,並分別取名為counter0counter1counter0LED輸出驅動LED[0]counter1LED輸出驅動LED[1],為了實現LED[0]0.1秒的頻率閃爍,LED[1]0.01秒的頻率閃爍,在第23行和24行分別使用"defparam"來重新定義CNT_MAX的值。這里以第23行修改counter0中的CNT_MAX的值來具體講解此種語法的結構。

 

首先,使用關鍵字"defparam"來聲明這里需要對某個參數的值進行重新定義,空格之后緊跟着的counter0counter模塊被例化的名字,counter0后緊隨一個".""."后緊隨着的就是counter模塊中需要修改的參數的名字,然后使用"="將新的值賦給改參數即可。這里使用模塊名+"."+參數名的方式,與C語言結構體中使用結構體成員的形式很類似,大家可以對比學習。通過這種方式,大家可以在不修改原有設計文件的情況下,例化后,在上層模塊直接修改參數。雖然模塊中設定了有默認值,但是使用defparam修改的值比原始設計文件中的值擁有更高的編譯優先級。當使用defparam修改了原始文件中的參數值后,原始文件中的默認參數值即被忽略。

 

當我們設計完成后,就需要對設計的模塊進行仿真,由於仿真執行所需花費的時間遠遠大於板級系統執行功能時需要的時間,因此仿真時,我們希望對設計模塊中某些耗時比較長的設計在不影響理論正確性的前提下進行簡單的修改,例如,上述counter模塊中,實際仿真如果也按照24999999的計數值進行計數,則需要花費太多的仿真時間,嚴重影響設計效率,因此也可以在仿真時直接在testbench中使用defparam來對設計文件中的參數進行修改,從而降低仿真復雜度。上述LED_flicker系統的testbench代碼如下所示:

01    `timescale 1ns/1ps

02    `define clk_period 20

03

04    module LED_flicker_tb;

05

06    //source define

07        reg Clk;

08        reg Rst_n;

09

10    //probe define

11        wire [1:0]LED;

12        

13    //instant user module

14        LED_flicker LED_flicker0(

15            .Clk(Clk),

16            .Rst_n(Rst_n),

17            .LED(LED)

18        );

19        

20        defparam LED_flicker0.counter0.CNT_MAX = 24;

21        defparam LED_flicker0.counter1.CNT_MAX = 24;

22

23    //generater clock

24        initial Clk = 1;

25        always #(`clk_period/2)Clk = ~Clk;

26

27        initial begin

28            Rst_n = 1'b0;

29            #(`clk_period * 20 + 1);

30            Rst_n = 1'b1;

31            #(`clk_period * 2000);

32            $stop;

33        end

34

35    endmodule

 

20行和第21行,使用defparam對設計模塊中的CNT_MAX進行了重新定義,我們希望通過這種方式,來在仿真的時候,讓計數值非常小,這樣仿真便能很快的得出結果,通過這兩句話大家也可以看到,結構體的形式能夠實現多級調用。然而,當使用此testbench文件直接仿真LED_flicker模塊時,卻並不能實現預期的效果。在modelsim中仿真時,報告如下信息:

雖然並不影響仿真的執行,然而從仿真執行的結果來看,這種在testbench中再次修改設計模塊中參數的方式無法與實體模塊中例化子模塊后進行參數定義同時存在,將實體模塊中使用defparam修改參數的部分屏蔽,則仿真文件中的defparam生效,實現了預期效果。下圖分別為不屏蔽LED_flickerdefparam內容和屏蔽LED_flickerdefparam內容后的仿真結果,從圖中可知,不屏蔽,LED[0]LED[1]的翻轉頻率分別為100ms10ms,結果與LED_flickerdefparam定義的值一致,屏蔽掉后,LED[0]LED[1]的翻轉頻率都是0.001ms,仿真結果與testench文件LED_flicker_tbdefparam定義的值一致。

 

不屏蔽LED_flickerdefparam內容時仿真結果

 

屏蔽LED_flickerdefparam內容時仿真結果

 

在實際使用中,我們往往希望實體設計模塊中能夠設定某參數值為符合實際板級運行要求的值,而在仿真中,為了節約仿真時間,我們又希望修改該參數值以配合仿真通過上面的例子我們發現,使用defparam語句難以同時滿足實體設計和仿真的需求。那么有沒有更好的方法來實現實體設計與仿真驗證中對參數的修改能夠共存且互不干擾呢?

    為了實現此功能,我們調整在實體模塊中例化子模塊時對參數的修改方式,上面我們介紹的是使用defparam語句重新定義子模塊參數內容。接下來我們使用在模塊例化時,直接對參數進行例化的方式來修改子模塊中的參數值。使用參數例化方式修改參數值的LED閃爍燈模塊代碼如下所示:

01    module LED_flicker_inst(

02        Clk,

03        Rst_n,

04        LED

05    );

06

07        input Clk;

08        input Rst_n;

09        output [1:0]LED;

10

11        counter

12        #(

13            .CNT_MAX(24_999_99)

14        )

15        counter0(

16            .Clk(Clk),

17            .Rst_n(Rst_n),

18            .led(LED[0])

19        );    

20        

21        counter

22        #(

23            .CNT_MAX(24_999_9)

24        )

25        counter1(

26            .Clk(Clk),

27            .Rst_n(Rst_n),

28            .led(LED[1])

29        );    

30        

31    endmodule

這里,第11行到19行為例化counter0模塊的代碼。這里對於子模塊中的參數,使用了和端口相類似的形式進行修改。通過這種方式,在例化每個功能模塊的時候,直接將所需修改參數的值也同時通過例化的方式修改了。

下面對這種例化方式的代碼結構進行簡單介紹:

首先,第11行為原始設計模塊的名字,第12行和14行為一對小括號,對參數例化的內容就在此括號中進行。注意11號,即括號前面需要使用"#"符號來聲明這是對參數進行例化。括號內,參數例化形式與端口例化形式一致,使用"."+原始參數名+新值的方式來修改新的值。通過這種方式,能夠避開對於同一個參數多次使用defparam來重定義值而引發的沖突。使用上述testench文件仿真LED_flicker_inst模塊的仿真結果如下圖所示:

使用defparam重新修改CNT_MAX值的仿真結果

 

可以看到,LED[0]LED[1]的翻轉頻率都是0.001ms,為通過testbench修改后的結果。

 

為了驗證使用這種例化的方式是否能夠達到預期的效果,這里將testbenchdefparam內容屏蔽,再來觀察仿真結果即可,屏蔽掉testbench文件中defparam內容后仿真結果如下圖所示:

屏蔽testbench文件中defparam內容后仿真

 

可以看到,屏蔽掉testbench中隊CNT_MAX值的修改內容后,LED[0]LED[1]的翻轉頻率分別為100ms10ms,仿真結果就與實際設計值一致。但是在我們實際板級使用的時候,testbench中的值並不會對實際設計內容產生影響,因此,即使不屏蔽testbench中對參數值的修改,也不會影響板級設計結果。

 

因此我們可以總結得出,為了同時保證實際邏輯設計和仿真驗證時對參數的修改能夠共存,互不影響,在實體模塊設計中,使用參數例化的方式修改被例化模塊中的參數。在仿真驗證用的testbench中,使用defparam語句來修改對應的參數。

 

通過本教程內容,講解了在Verilog中使用參數化設計的方法,展示了參數化設計在實際使用時候的巨大便利。介紹了兩種修改參數值的方法,並通過實際的例子展示了兩種修改方式的特點,並最終給出了能夠同時兼容實際邏輯設計和仿真驗證的參數修改方法。希望大家以后。在設計自己的代碼的時候,多使用這種參數化的設計方式。

 

如有更多問題,歡迎加入芯航線FPGA技術支持群:472607506

 

小梅哥

2015年11月7日於芯航線電子工作室


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM