您的位置:首页 > 其它

(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)

2008-07-31 09:51 531 查看
Abstract

循序電路第一個應用是拿來做計數器((筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)),有了計數器的基礎後,就可以拿計數器來設計除頻器,最後希望能做出能除N的萬用除頻器。

Introduction

使用環境:Quartus II 7.2 SP3 + ModelSim-Altera 6.1g


除頻器在實務中隨時會用到,如DE2只提供50MHz與27MHz的clock,如CMOS用的是25MHz,因此就必須手動作一個除頻器產生25MHz,利用計數器的基礎,就可以設計一個除頻器。

Method 1:

使用Verilog


除2的除頻器

最簡單的除頻器,還不需要到計數器就可以完成。

div2.v / Verilog (沒用到計數器)

1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : div2.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by 2

7 Release : 07/12/2008 1.0

8 */

9

10 module div2 (

11 input clk,

12 input rst_n,

13 output reg o_clk

14 );

15

16 always@(posedge clk or negedge rst_n) begin

17 if (!rst_n)

18 o_clk <= 0;

19 else

20 o_clk <= ~o_clk;

21 end

22

23 endmodule

頻率要變一半,也就是周期要變兩倍,也就是本來一個clock的時間,變成半個clock的時間,所以每次clock正源觸發時,剛好是0變1、1變0的時機。由於除2的除頻器很簡單,所以不需要用到記數器就可完成,但更複雜的除頻器一定要用到計數器,所以我們也用計數器寫一個除2除頻器,幫助了解後,才能寫更複雜的除頻器。

div2_v2.v / Verilog (使用計數器)



1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : div2_v2.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by 2

7 Release : 07/12/2008 1.0

8 */

9

10 module div2_v2 (

11 input clk,

12 input rst_n,

13 output reg o_clk

14 );

15

16 reg cnt;

17

18 always@(posedge clk or negedge rst_n) begin

19 if (!rst_n)

20 cnt <= 0;

21 else if (cnt == 1) // 0 ~ 1

22 cnt <= 0;

23 else

24 cnt <= cnt + 1;

25 end

26

27 always@(posedge clk or negedge rst_n) begin

28 if (!rst_n)

29 o_clk <= 0;

30 else if (cnt < 1) // 0

31 o_clk = 0;

32 else // 1

33 o_clk = 1;

34 end

35

36 endmodule

18行

always@(posedge clk or negedge rst_n) begin

if (!rst_n)

cnt <= 0;

else if (cnt == 1) // 0 ~ 1

cnt <= 0;

else

cnt <= cnt + 1;

end

一個簡單的計數器,從0數到1,然後又重頭從0數到1,因為目前要做的是除2的除頻器,所以只需0和1兩個狀態即可。

27行

always@(posedge clk or negedge rst_n) begin

if (!rst_n)

o_clk <= 0;

else if (cnt < 1) // 0

o_clk = 0;

else // 1

o_clk = 1;

end

利用計數器產生新的clock,當計數器是0時,輸出1,當計數器是1時,輸出0。如此就完成duty cycle為50%的除2除頻器電路。

當然我可以將兩個always寫在一起,不過好的Verilog coding style建議每個always都短短的,最好一個always只處理一個register,第一個always block處理reg cnt,第二個處理reg o_clk,這樣一目了然,對於合成器來說,也較容易合成出好的電路,對於可讀性來說,人類也較容易理解,甚至看完code後,自己都可以當合成器,合出一個電路,這也是為什麼說寫HDL要『心中有電路』,而不是像寫軟體一樣,只要考慮語法就好,反正編譯器會幫你解決,這也是寫硬體和寫軟體另一個差異很大的地方。

testbench

div2_tb.v / Verilog



1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : div2_tb.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by 2 testbench

7 Release : 07/16/2008 1.0

8 */

9 `timescale 1ns/10ps

10 module div2_tb;

11 reg clk;

12 reg rst_n;

13 wire o_clk;

14

15 div2 u0 (

16 .clk(clk),

17 .rst_n(rst_n),

18 .o_clk(o_clk)

19 );

20

21 initial begin

22 clk = 1'b1;

23 rst_n = 1'b1;

24 end

25

26 // 50MHz clk

27 always #10 clk = ~clk;

28 endmodule

always@(posedge clk or negedge rst_n) begin

if (!rst_n)

cnt_p <= 0;

else if (cnt_p == 2) // 0 ~ 2

cnt_p <= 0;

else

cnt_p <= cnt_p + 1;

end

由於要將兩個clock做or,cnt_p是前半段clock的計數器,由於是除3,所以需要0~2三個數。

32行

always@(posedge clk or negedge rst_n) begin

if (!rst_n)

clk_p <= 1;

else if (cnt_p < 1) // 0

clk_p = 1;

else // 1 2

clk_p = 0;

end

因為要產生duty cycle為50%的clock,所以一半要1,一半要0,但0、1、2該怎麼平分呢?由於前半部的波形1做or會變大,所以就少分一點,也就是當計數器為0產生1,1、2時產生0。

41行

always@(negedge clk or negedge rst_n) begin

if (!rst_n)

cnt_n <= 0;

else if (cnt_n == 2) // 0 ~ 2

cnt_n <= 0;

else

cnt_n <= cnt_n + 1;

end

注意到negedge clk了嗎?要不是因為這個0.5 clockk,我們幾乎不會這樣寫程式,cnt_n是後半段clock的計數器,也同樣需要0~2三個數。

50行

always@(negedge clk or negedge rst_n) begin

if (!rst_n)

clk_n <= 1;

else if (cnt_n < 1) // 0

clk_n = 1;

else // 1 2

clk_n = 0;

end

因為是後半段clock,所以一樣是negedge clk,一樣是因為後半部的波形1做or會變大,所以就少分一點,也就是當計數器為0產生1,1、2時產生0。

21行

assign o_clk = clk_p | clk_n;

最後將兩個clock做or。

testbench

div3_tb.v / Verilog



1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : div3_tb.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by 3 testbench

7 Release : 07/12/2008 1.0

8 */

9

10

11 `timescale 1ns/10ps

12 module div3_tb;

13 reg clk;

14 reg rst_n;

15 wire o_clk;

16

17

18 div3 u0 (

19 .clk(clk),

20 .rst_n(rst_n),

21 .o_clk(o_clk)

22 );

23

24 initial begin

25 clk = 1'b1;

26 rst_n = 1'b1;

27 end

28

29 // 50MHz clk

30 always #10 clk = ~clk;

31 endmodule

因為將clk_p與clk_n做or產生新的clk的方式實在太特別,所以特別將clk_p與clk_n也用modelsim顯示出來,這樣可以明顯地看到or後產生除3後的波形,神奇吧!!



除任意正整數除頻器

若前面的範例都已經徹底了解,要寫個除任意整數的除頻器就不難了。

divn.v / Verilog

1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : divn.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by n

7 Release : 07/16/2008 1.0

8 */

9

10 module divn (

11 input clk,

12 input rst_n,

13 output o_clk

14 );

15

16 parameter WIDTH = 3;

17 parameter N = 6;

18

19 reg [WIDTH-1:0] cnt_p;

20 reg [WIDTH-1:0] cnt_n;

21 reg clk_p;

22 reg clk_n;

23

24 assign o_clk = (N == 1) ? clk :

25 (N[0]) ? (clk_p | clk_n) : (clk_p);

26

27 always@(posedge clk or negedge rst_n) begin

28 if (!rst_n)

29 cnt_p <= 0;

30 else if (cnt_p == (N-1))

31 cnt_p <= 0;

32 else

33 cnt_p <= cnt_p + 1;

34 end

35

36 always@(posedge clk or negedge rst_n) begin

37 if (!rst_n)

38 clk_p <= 1;

39 else if (cnt_p < (N>>1))

40 clk_p = 1;

41 else

42 clk_p = 0;

43 end

44

45 always@(negedge clk or negedge rst_n) begin

46 if (!rst_n)

47 cnt_n <= 0;

48 else if (cnt_n == (N-1))

49 cnt_n <= 0;

50 else

51 cnt_n <= cnt_n + 1;

52 end

53

54 always@(negedge clk or negedge rst_n) begin

55 if (!rst_n)

56 clk_n <= 1;

57 else if (cnt_n < (N>>1))

58 clk_n = 1;

59 else

60 clk_n = 0;

61 end

62

63 endmodule

16行

parameter WIDTH = 3;

parameter N = 6;

多加了兩個參數,WIDTH代表計數器的寬度,N代表要要除的任意正整數,以後若要產生各種除頻器,只要改這兩個參數即可。

36行

always@(posedge clk or negedge rst_n) begin

if (!rst_n)

clk_p <= 1;

else if (cnt_p < (N>>1))

clk_p = 1;

else

clk_p = 0;

end

N>>1稍微做一下解釋,其實就是 N / 2,好吧,我承認是syntax sugar,不過這在Verilog倒很常見。

24行

assign o_clk = (N == 1) ? clk :

(N[0]) ? (clk_p | clk_n) : (clk_p);

整個程式的唯一關鍵在此,若N為1,表示不用除頻,直接輸出clk即可,N[0]是Verilog個小技巧,判斷N是否為奇數,若為奇數,則clk_p | clk_n,若為偶數,則clk_p即可。這樣無論N為奇數或者偶數都沒問題。其實C/C++也可以用這個小技巧判斷是否為奇數,只要x & 1即可,以後就不要靠x % 2了。

testbench

divn_tb.v / Verilog


1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : divn_tb.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by n testbench

7 Release : 07/16/2008 1.0

8 */

9

10 `timescale 1ns/10ps

11 module divn_tb;

12 reg clk;

13 reg rst_n;

14 wire o_clk;

15

16 divn u0 (

17 .clk(clk),

18 .rst_n(rst_n),

19 .o_clk(o_clk)

20 );

21

22

23 initial begin

24 clk = 1'b1;

25 rst_n = 1'b1;

26 end

27

28 // 50MHz clk

29 always #10 clk = ~clk;

30 endmodule

產生除5的除頻器





產生除6的除頻器



Method 2:

使用Megafunction


1.lpm_counter + lpm_ff

前面有歸納一個結論:除2的除頻器就是每1個clock就0變1、1變0,除4的除頻器就是每2個clock就0變1、1變0,這在除偶數的除頻器有效,計數器部分我們使用lpm_counter,0變1、1變0我們就是用lpm_ff這個T-FF。

divn_mf.v / Verilog

1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : divn_mf.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by lpm

7 Release : 07/16/2008 1.0

8 */

9

10 module divn_mf (

11 input clk,

12 input rst_n,

13 output o_clk

14 );

15

16 parameter WIDTH = 3;

17 parameter N = 8;

18

19 wire w_clk;

20

21 lpm_counter # (

22 .lpm_width(WIDTH),

23 .lpm_direction("UP"),

24 .lpm_modulus(N>>1)

25 ) u0 (

26 .clock(clk),

27 .cout(w_clk)

28 );

29

30 lpm_ff # (

31 .lpm_width(1),

32 .lpm_fftype("TFF")

33 )

34 u1 (

35 .clock(clk),

36 .data(w_clk),

37 .q(o_clk)

38 );

39

40 endmodule

22行

lpm_counter # (

.lpm_width(WIDTH),

.lpm_direction("UP"),

.lpm_modulus(N>>1)

) u0 (

.clock(clk),

.cout(w_clk)

);

這是一個計數器,詳細參數使用我就不多談,請參考(轉貼) LPM Quick Reference Guide (SOC) (MegaCore)

30行

lpm_ff # (

.lpm_width(1),

.lpm_fftype("TFF")

)

u1 (

.clock(clk),

.data(w_clk),

.q(o_clk)

);

這是一個T-FF,負責0變1,1變0。

testbench

divn_mf_tb.v / Verilog


1 /*

2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3

4 Filename : divn_mf_tb.v

5 Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g

6 Description : Demo how to write frequency divider by n testbench

7 Release : 07/16/2008 1.0

8 */

9

10

11 `timescale 1ns/10ps

12 module divn_mf_tb;

13 reg clk;

14 reg rst_n;

15 wire o_clk;

16

17 divn_mf u0 (

18 .clk(clk),

19 .rst_n(rst_n),

20 .o_clk(o_clk)

21 );

22

23

24 initial begin

25 clk = 1'b1;

26 rst_n = 1'b1;

27 end

28

29 // 50MHz clk

30 always #10 clk = ~clk;

31 endmodule

產生除8的除頻器



這種方法的缺點是只能做除偶數的除頻器,因為計數器無法算出0.5。

2.ALTPLL

使用ALTPLL,基本上任意除頻倍頻都可產生,只要透過MegaWizard選一選即可。不過它的除頻必須搭配輸入頻率,如50MHz就無法除7,但100MHz就可以除7,且需求改變,就得必須MegaWizard重做一遍。

Conclusion

這樣子所有除頻器都討論過了, 尤其除奇數的除頻器非常的tricky,真的非常佩服想出這種方法的人。

See Also

(筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)

(轉貼) LPM Quick Reference Guide (SOC) (MegaCore)

(筆記) 更快更好的判斷奇數偶數的小技巧 (C/C++) (C) (SOC) (Verilog)

(原創) 如何設計電子鐘? (SOC) (Verilog) (DE2)

(原創) 如何設計電子鐘(II)? (SOC) (Verilog) (MegaCore) (DE2)

Reference

陸自強 2007,數位系統實習 Quartus II,儒林圖書公司

王钿、卓興旺 2007,基於Verilog HDL的數字應用設計,國防工業出版社

鄭信源 2006,Verilog硬體描述語言數位電路 設計實務,儒林圖書公司

潘煜熙任意整数分频模块

~*shěll*~ の blogVerilog分頻器代碼
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐