(原創) 如何設計電子鐘(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
這是一個簡化的系統方塊圖,因為寬度的關係,我將input與output都與以省略,上圖的divn是個除頻器,負責將DE2提供的50MHz clock除頻程1 Hz,seg7_lut則是負責將數字顯示在7段顯示器上。clock的細部實現為下圖,由兩個60計數器負責秒和分,24計數器負責時。
digi_clock.v / Verilog
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行
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
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
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
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 co = w_hco & w_mco & w_sco;
co為整個電子鐘的進位功能,也就是當時、分、秒皆有進位時才進位,也就是23:59:59時才進位。
w_clr為整個電子鐘歸0的連線,除了手動clear外,當co為1,也就是23:59:59時, 整個電子鐘也會歸0。
下面都是對輸入防呆的程式,也就是分的輸入的的部分,最多只能輸入到59,時的部分最多只能輸入到23。
77行
(md0 < 10) ? md0 : 9;
assign w_md1 = (!load) ? 0 :
(md1 < 6) ? md1 : 5;
分的部分比較單純,所以用 ?: 寫法即可,因為分最多只能到59分,個位數最多只能到9,超過9的部分一率只能輸入9。十位部分最多只能到5,超過5的部分一率只能輸入5。
81行
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
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行
進位的地方較特別,當59時要送出進位carry,若用2進位表示就是101 1001,所以只要將q1[2] & q1[0] & q0[3] & q0[0]即可,這就是2進位好用的地方。
counter24.v / Verilog
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,儒林圖書公司