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


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

除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


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

看到這裡,你應該會想用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行

(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)always@(posedge clk or negedge rst_n) begin
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore) 
if (!rst_n)
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)    cnt_p
<= 0;
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore) 
else if (cnt_p == 2) // 0 ~ 2
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)
    cnt_p <= 0;
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore) 
else
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)    cnt_p
<= cnt_p + 1;
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)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行

(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)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後的波形,神奇吧!!

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

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

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

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

產生除6的除頻器

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

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

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

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

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

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

See Also
(筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)
(轉貼) LPM Quick Reference Guide (SOC) (MegaCore)
(原創) 如何設計電子鐘(II)? (SOC) (Verilog) (MegaCore) (DE2) 

Reference
陸自強 2007,數位系統實習 Quartus II,儒林圖書公司
王钿、卓興旺 2007,基於Verilog HDL的數字應用設計,國防工業出版社
鄭信源 2006,Verilog硬體描述語言數位電路 設計實務,儒林圖書公司
潘煜熙任意整数分频模块
~*shěll*~  の blogVerilog分頻器代碼

相关文章:

  • 2021-06-04
  • 2021-09-10
  • 2021-06-22
  • 2021-06-28
  • 2021-12-25
  • 2021-09-22
猜你喜欢
  • 2021-12-09
  • 2021-12-25
  • 2021-06-10
  • 2021-09-29
  • 2022-02-22
  • 2021-08-27
相关资源
相似解决方案