(原創) 如何設計除頻器? (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 (沒用到計數器)
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 (使用計數器)
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行
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行
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
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
除4的除頻器
除3的除頻器因為是奇數,所以較麻煩,我們先看除4的除頻器後,再回頭看除3的除頻器。
div4.v / Verilog
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
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
看到這裡,你應該會想用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
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行
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行
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行
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行
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行
最後將兩個clock做or。
testbench
div3_tb.v / Verilog
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
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 N = 6;
多加了兩個參數,WIDTH代表計數器的寬度,N代表要要除的任意正整數,以後若要產生各種除頻器,只要改這兩個參數即可。
36行
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行
(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
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
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_width(WIDTH),
.lpm_direction("UP"),
.lpm_modulus(N>>1)
) u0 (
.clock(clk),
.cout(w_clk)
);
這是一個計數器,詳細參數使用我就不多談,請參考(轉貼) LPM Quick Reference Guide (SOC) (MegaCore)。
30行
.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
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*~ の blog的Verilog分頻器代碼
posted on 2008-07-31 09:51 真 OO无双 阅读(139064) 评论(15) 编辑 收藏 举报