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

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


div01

除4的除頻器
除3的除頻器因為是奇數,所以較麻煩,我們先看除4的除頻器後,再回頭看除3的除頻器。

div4.v / Verilog

1 /* 
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : div4.v
5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
6 Description : Demo how to write frequency divider by 4
7 Release     : 07/16/2008 1.0
8 */
9 
10 module div4    (
11   input clk,
12   input rst_n,
13   output reg o_clk
14 );
15 
16 reg [1:0] cnt;
17 
18 always@(posedge clk or negedge rst_n) begin
19   if (!rst_n)
20     cnt <= 0;
21   else if (cnt == 3) // 0 ~ 3
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 < 2) // 0 ~ 1
31     o_clk = 0;
32   else              // 2 ~ 3
33     o_clk = 1;   
34 end
35 endmodule


若已經完全搞懂除2的除頻器,這個除4的除頻器也很好懂。只是變成計數器從0數到3,當0與1時輸出1,而2與3時輸出0

testbench
div4_tb.v / Verilog

1 /* 
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : div4_tb.v
5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
6 Description : Demo how to write frequency divider by 4 testbench
7 Release     : 07/16/2008 1.0
8 */
9 
10 `timescale 1ns/10ps
11 module div4_tb;
12 reg clk;
13 reg rst_n;
14 wire o_clk;
15 
16 div4 u0 (
17   .clk(clk),
18   .rst_n(rst_n),
19   .o_clk(o_clk)
20 );
21 
22 initial begin
23   clk   = 1'b1;
24   rst_n = 1'b1;
25 end
26 
27 // 50MHz clk
28 always #10 clk = ~clk;
29 endmodule


div01 

看到這裡,你應該會想用parameter打造一個萬用(泛型)的除偶數的除頻器,不過先別急,等我們看了除3的除頻器後,最後一起打造一個除N的萬用除頻器。

除3的除頻器
根據前面的經驗,除2的除頻器就是每1個clock就0變1、1變0,除4的除頻器就是每2個clock就0變1、1變0,所以除3的除頻器應該是每1.5的clock就0變1、1變0,但問題來了,哪來的1.5個clock?計數器並不能產生1.5!!

回想一下波形,正源觸發與負源觸發的間隔時間是不是剛好是0.5個clock?所以我們產生兩個clock,一個是posedge clk,一個是negedge clk,最後將兩個clock做or,這樣就可以產生出0.5個clock了,很巧妙吧!!

div3.v / Verilog

1 /* 
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : div3.v
5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
6 Description : Demo how to write frequency divider by 3
7 Release     : 07/12/2008 1.0
8 */
9 
10 module div3    (
11   input  clk,
12   input  rst_n,
13   output o_clk
14 );
15 
16 reg [1:0] cnt_p;
17 reg [1:0] cnt_n;
18 reg       clk_p;
19 reg       clk_n;
20 
21 assign o_clk = clk_p | clk_n;
22 
23 always@(posedge clk or negedge rst_n) begin
24   if (!rst_n)
25     cnt_p <= 0;
26   else if (cnt_p == 2) // 0 ~ 2
27     cnt_p <= 0;
28   else
29     cnt_p <= cnt_p + 1;
30 end
31 
32 always@(posedge clk or negedge rst_n) begin
33   if (!rst_n)
34     clk_p <= 1;
35   else if (cnt_p < 1) // 0
36     clk_p = 1;
37   else                // 1 2
38     clk_p = 0;   
39 end
40 
41 always@(negedge clk or negedge rst_n) begin
42   if (!rst_n)
43     cnt_n <= 0;
44   else if (cnt_n == 2) // 0 ~ 2
45     cnt_n <= 0;
46   else
47     cnt_n <= cnt_n + 1;
48 end
49 
50 always@(negedge clk or negedge rst_n) begin
51   if (!rst_n)
52     clk_n <= 1;
53   else if (cnt_n < 1) // 0
54     clk_n = 1;
55   else                // 1 2
56     clk_n = 0;
57 end
58 
59 endmodule


23行

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後的波形,神奇吧!!

div02

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

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的除頻器

div03 

產生除6的除頻器

div04

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的除頻器

wave8

這種方法的缺點是只能做除偶數的除頻器,因為計數器無法算出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分頻器代碼

posted on 2008-07-31 09:51  真 OO无双  阅读(139064)  评论(15编辑  收藏  举报

导航