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

Abstract
學會計數器除頻電路後,就能以這兩個電路為基礎,設計一個電子鐘,並可自行調整目前時間。

Introduction
使用環境:Quartus II 7.2 SP3 + DE2(Cyclone II EP2C35F627C6)

(筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)討論過計數器,接著在(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)討論過萬用除頻器,利用這兩個基礎,就可以設計一個簡易的電子鐘,如同電子錶一樣顯示時間。

Specification

SW17 enable
SW16 reset
SW15 load (設定時間)
SW[3:0] 設定分的個位數
SW[6:4] 設定分的十位數
SW[10:7] 設定時的個位數
SW[13:11] 設定時的十位數
HEX[2] 顯示秒的個位數
HEX[3] 顯示秒的十位數
HEX[4] 顯示分的個位數
HEX[5] 顯示分的十位數
HEX[6] 顯示時的個位數
HEX[7] 顯示時的十位數

 

Block Diagram

 

 

 digi_clock00

digi_clock01

這是一個簡化的系統方塊圖,因為寬度的關係,我將input與output都與以省略,上圖的divn是個除頻器,負責將DE2提供的50MHz clock除頻程1 Hz,seg7_lut則是負責將數字顯示在7段顯示器上。clock的細部實現為下圖,由兩個60計數器負責,24計數器負責

digi_clock.v / Verilog

1 /* 
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : digi_clock.v
5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
6 Description : Demo how to write digital clock top module
7 Release     : 07/27/2008 1.0
8 */
9 
10 module digi_clock (
11   input         CLOCK_50,
12   input  [17:0] SW,
13   input  [3:0]  KEY,
14   output [6:0]  HEX2,
15   output [6:0]  HEX3,
16   output [6:0]  HEX4,
17   output [6:0]  HEX5,
18   output [6:0]  HEX6,
19   output [6:0]  HEX7
20 );
21 
22 wire       clk_1;
23 wire [3:0] w_sq0;
24 wire [2:0] w_sq1;
25 wire [3:0] w_mq0;
26 wire [2:0] w_mq1;
27 wire [3:0] w_hq0;
28 wire [2:0] w_hq1;
29 
30 // 1Hz clock
31 divn # (.WIDTH(26), .N(50000000))
32 u0 (
33   .clk(CLOCK_50),
34   .rst_n(KEY[0]),
35   .o_clk(clk_1)
36 );
37 
38 clock u1 (
39   .clk(clk_1),
40   .en(SW[17]),     // input  enable
41   .clr(SW[16]),    // input  clear
42   .load(SW[15]),   // input  load
43   .sd0(4'h0),      // input  second digit 0
44   .sd1(3'h0),      // input  second digit 1
45   .md0(SW[3:0]),   // input  minute digit 0
46   .md1(SW[6:4]),   // input  minute digit 1
47   .hd0(SW[10:7]),  // input  hour digit   0
48   .hd1(SW[13:11]), // input  hour digit   1
49   .sq0(w_sq0),     // output second digit 0
50   .sq1(w_sq1),     // output second digit 1
51   .mq0(w_mq0),     // output minute digit 0
52   .mq1(w_mq1),     // output minute digit 1
53   .hq0(w_hq0),     // output minute digit 0
54   .hq1(w_hq1)      // output minute digit 1
55 );
56 
57 // sec. dig0 to seg7
58 seg7_lut u2 (
59   .i_dig(w_sq0),
60   .o_seg(HEX2)
61 );
62 
63 // sec. dig1 to seg7
64 seg7_lut u3 (
65   .i_dig({1'b0, w_sq1}),
66   .o_seg(HEX3)
67 );
68 
69 // min. dig0 to seg7
70 seg7_lut u4 (
71   .i_dig(w_mq0),
72   .o_seg(HEX4)
73 );
74 
75 // min. dig1 to seg7
76 seg7_lut u5 (
77   .i_dig({1'b0, w_mq1}),
78   .o_seg(HEX5)
79 );
80 
81 // hour dig0 to seg7
82 seg7_lut u6 (
83   .i_dig(w_hq0),
84   .o_seg(HEX6)
85 );
86 
87 // hour dig1 to seg7
88 seg7_lut u7 (
89   .i_dig({1'b0, w_hq1}),
90   .o_seg(HEX7)
91 );
92 
93 endmodule


這是整個project的top module,為了簡化pin assignment的動作,所以port的命名方式和DE2_pin_assignments.csv相同,或許你跟我一樣不喜歡port用大寫的coding style,但為了pin assignment方便,只好在top module配合一下大寫,其他module的port一樣可以用小寫。

30行

// 1Hz clock
divn # (.WIDTH(26), .N(50000000))
u0 (
  .clk(CLOCK_50),
  .rst_n(KEY[
0]),
  .o_clk(clk_1)
);


divn為(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)所寫過的萬用除頻器,由於DE2提供的clock是50MHz,但電子鐘只希望每秒變化一次,所以要除頻剩下1Hz,所以要將50MHz除50M,經過計算,這樣需26位才夠,所以傳進26與50000000。

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
64 


dinn.v請參考(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)中的討論。

seg7_lut.v / Verilog

1 /* 
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : seg7_lut.V
5 Compiler    : Quartus II 7.2 SP3
6 Description : Demo how to use 8 bit 7 segment display decimal
7 Release     : 07/20/2008 1.0
8 */
9 module seg7_lut (
10   input      [3:0] i_dig,
11   output reg [6:0] o_seg
12 );
13 
14 always@(i_dig) begin
15   case(i_dig)
16     4'h1: o_seg = 7'b111_1001;  // ---t----
17     4'h2: o_seg = 7'b010_0100;  // |      |
18     4'h3: o_seg = 7'b011_0000;  // lt    rt
19     4'h4: o_seg = 7'b001_1001;  // |      |
20     4'h5: o_seg = 7'b001_0010;  // ---m----
21     4'h6: o_seg = 7'b000_0010;  // |      |
22     4'h7: o_seg = 7'b111_1000;  // lb    rb
23     4'h8: o_seg = 7'b000_0000;  // |      |
24     4'h9: o_seg = 7'b001_1000;  // ---b----
25     4'ha: o_seg = 7'b000_1000;
26     4'hb: o_seg = 7'b000_0011;
27     4'hc: o_seg = 7'b100_0110;
28     4'hd: o_seg = 7'b010_0001;
29     4'he: o_seg = 7'b000_0110;
30     4'hf: o_seg = 7'b000_1110;
31     4'h0: o_seg = 7'b100_0000;
32   endcase
33 end
34 
35 endmodule


這是一個7段顯示器的lookup table,請參考(原創) 如何以16進位顯示8位數的七段顯示器? (SOC) (Verilog) (DE2)

clock.v / Verilog

  1 /* 
  2 (C) OOMusou 2008 http://oomusou.cnblogs.com
  3 
  4 Filename    : clock.v
  5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
  6 Description : Demo how to write clock counter
  7 Release     : 07/28/2008 1.0
  8 */
  9 
10 module clock (
11   input        clk,
12   input        en, 
13   input        clr,
14   input        load,
15   input  [3:0] sd0,
16   input  [2:0] sd1,
17   input  [3:0] md0,
18   input  [2:0] md1,
19   input  [3:0] hd0,
20   input  [2:0] hd1,
21   output [3:0] sq0,
22   output [2:0] sq1, 
23   output [3:0] mq0,
24   output [2:0] mq1,
25   output [3:0] hq0,
26   output [1:0] hq1,
27   output       co  
28 );
29 
30 wire       w_clr;
31 wire [3:0] w_md0;
32 wire [2:0] w_md1;
33 reg  [3:0] w_hd0;
34 reg  [2:0] w_hd1;
35 wire       w_sco; // second carry
36 wire       w_mco; // minute carry
37 wire       w_hco; // hour carry
38 
39 counter60 sec (
40   .clk(clk),
41   .load(load),
42   .clr(w_clr),
43   .en(en),
44   .d0(sd0),
45   .d1(sd1),
46   .q0(sq0),
47   .q1(sq1),
48   .co(w_sco)
49 );
50 
51 counter60 min (
52   .clk(clk),
53   .load(load),
54   .clr(w_clr),
55   .en(en & w_sco),
56   .d0(w_md0),
57   .d1(w_md1),
58   .q0(mq0),
59   .q1(mq1),
60   .co(w_mco)
61 );
62 
63 counter24 hour (
64   .clk(clk),
65   .load(load),
66   .clr(w_clr),
67   .en(en & w_mco & w_sco),
68   .d0(w_hd0),
69   .d1(w_hd1),
70   .q0(hq0),
71   .q1(hq1),
72   .co(w_hco)
73 );
74 
75 assign w_clr = clr | co;
76 assign co    = w_hco & w_mco & w_sco;
77 assign w_md0 = (!load) ? 0 :
78                (md0 < 10) ? md0 : 9;
79 assign w_md1 = (!load) ? 0 :
80                (md1 < 6? md1 : 5;
81                      
82 always@(load or hd0 or hd1) begin
83   if (!load) begin
84     w_hd0 = 0;
85     w_hd1 = 0;
86   end
87   else begin
88     if (hd1 <= 1) begin // 0 1
89       w_hd1 = hd1;
90      
91       if (hd0 < 10)
92         w_hd0 = hd0;
93       else
94         w_hd0 = 9;
95     end
96     else begin // >= 2
97       w_hd1 = 2;
98      
99       if (hd0 < 4)
100         w_hd0 = hd0;
101       else
102         w_hd0 = 3;
103     end
104   end
105 end                       
106 
107 endmodule


clock.v事實上已經是一個完整功能的電子鐘,可以單獨用testbench測試,但為了要和DE2周邊搭配,所以才又包了一個digi_clock.v。

clock.v主要有兩個功能:
1.例化時、分、秒3個instance。
2.對輸入做防呆的動作。

75行

assign w_clr = clr | co;
assign co    = w_hco & w_mco & w_sco;


co為整個電子鐘的進位功能,也就是當時、分、秒皆有進位時才進位,也就是23:59:59時才進位。

w_clr為整個電子鐘歸0的連線,除了手動clear外,當co為1,也就是23:59:59時, 整個電子鐘也會歸0。

下面都是對輸入防呆的程式,也就是的輸入的的部分,最多只能輸入到59,的部分最多只能輸入到23。

77行

assign w_md0 = (!load) ? 0 :
               (md0
< 10) ? md0 : 9;
assign w_md1 = (!load) ? 0 :
               (md1
< 6? md1 : 5;


的部分比較單純,所以用 ?: 寫法即可,因為分最多只能到59分,個位數最多只能到9,超過9的部分一率只能輸入9。十位部分最多只能到5,超過5的部分一率只能輸入5。

81行

always@(load or hd0 or hd1) begin
 
if (!load) begin
    w_hd0
= 0;
    w_hd1
= 0;
 
end
 
else begin
   
if (hd1 <= 1) begin // 0 1
      w_hd1 = hd1;
     
     
if (hd0 < 10)
        w_hd0
= hd0;
     
else
        w_hd0
= 9;
   
end
   
else begin // >= 2
      w_hd1 = 2;
     
     
if (hd0 < 4)
        w_hd0
= hd0;
     
else
        w_hd0
= 3;
   
end
 
end
end         


的部分比較複雜,所以用always block來寫。因為時最多只能到23,所以個位數能輸入的數字還要看當時的十位數而定。若十位數為0或1,則個位數做多到9;若十位數大於2,個位數最多只能到3,超過3的部分一率只能輸3。

counter60.v / Verilog

1 /* 
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : counter60.v
5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
6 Description : Demo how to write 60 counter
7 Release     : 07/27/2008 1.0
8 */
9 
10 module counter60 (
11   input            clk,
12   input            clr,
13   input            load,
14   input            en,
15   input      [3:0] d0,
16   input      [3:0] d1,
17   output reg [3:0] q0,
18   output reg [2:0] q1,
19   output           co
20 );
21 
22 assign co = q1[2] & q1[0] & q0[3] & q0[0]; // 101 1001 = 59
23 
24 always@(posedge clk) begin
25   if (clr) begin
26     q0 <= 0;
27     q1 <= 0;
28   end
29   else if (load) begin
30     q0 <= d0;
31     q1 <= d1;
32   end
33   else if (en) begin
34     if (q0 == 9) begin
35       q0 <= 0;
36      
37       if (q1 == 5)
38         q1 <= 0;
39       else
40         q1 <= q1 + 1;
41     end
42     else 
43       q0 <= q0 + 1;
44   end 
45   else begin
46     q0 <= q0;
47     q1 <= q1;
48   end
49 end
50 
51 endmodule


由於分和秒為60進制,所以需要一個60計數器,並且能進位,請參考(筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)

22行

assign co = q1[2] & q1[0] & q0[3] & q0[0]; // 101 1001 = 59


進位的地方較特別,當59時要送出進位carry,若用2進位表示就是101 1001,所以只要將q1[2] & q1[0] & q0[3] & q0[0]即可,這就是2進位好用的地方。

counter24.v / Verilog

1 /* 
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : counter24.v
5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
6 Description : Demo how to write 24 counter
7 Release     : 07/27/2008 1.0
8 */
9 
10 module counter24 (
11   input            clk,
12   input            clr,
13   input            load,
14   input            en,
15   input      [3:0] d0,
16   input      [1:0] d1,
17   output reg [3:0] q0,
18   output reg [1:0] q1,
19   output           co
20 );
21 
22 assign co = q0[1] & q0[0] & q1[1]; // 010   11 = 23
23 
24 always@(posedge clk) begin
25   if (clr) begin
26     q0 <= 0;
27     q1 <= 0;
28   end
29   else if (load) begin
30     q0 <= d0;
31     q1 <= d1;
32   end
33   else if (en) begin
34     if (q0 == 9) begin
35       q0 <= 0;
36       q1 <= q1 + 1;
37     end
38     else if (q1 == 2 & q0 == 3) begin // 23
39       q1 <= 0;
40       q0 <= 0;
41     end 
42     else 
43       q0 <= q0 + 1;
44   end
45   else begin
46     q0 <= q0;
47     q1 <= q1;
48   end
49 end
50 
51 endmodule


原理和60計數器一樣,我就不在多做解釋。

完整程式碼下載
digi_clock.7z

Conclusion
原來每天帶的電子錶,就是以clock為基礎,搭配計數器做出來的,透過FPGA,我們也可以自己設計一個簡易的電子鐘。

See Also
(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)
(原創) 如何以16進位顯示8位數的七段顯示器? (SOC) (Verilog) (DE2)
(筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)
(原創) 如何設計電子鐘(II)? (SOC) (Verilog) (MegaCore) (DE2)

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

posted on 2008-07-27 17:50  真 OO无双  阅读(24279)  评论(3编辑  收藏  举报

导航