案例編號:001600000064
至簡設(shè)計系列_鬧鐘
--作者:小黑同學(xué)
本文為明德?lián)P原創(chuàng)及錄用文章,轉(zhuǎn)載請注明出處!
1.1 總體設(shè)計
1.1.1 概述
數(shù)字時鐘是采用數(shù)字電路技術(shù)實現(xiàn)時、分、秒計時顯示的裝置,可以用數(shù)字同時顯示時,分,秒
的精確時間并實現(xiàn)準(zhǔn)確校時,具備體積小、重量輕、抗干擾能力強(qiáng)、對環(huán)境要求高、高精確性、容易
開發(fā)等特性,在工業(yè)控制系統(tǒng)、智能化儀器表、辦公自動化系統(tǒng)等諸多領(lǐng)域取得了極為廣泛的應(yīng)用,
諸如自動報警、按時自動打鈴、時間程序自動控制、定時廣播、自定啟閉路燈、定時開關(guān)烘箱、通斷
動力設(shè)備、甚至各種定時電器的自動啟用等。與傳統(tǒng)表盤式機(jī)械時鐘相比,數(shù)字時鐘具有更高的準(zhǔn)確
性和直觀性,由于沒有機(jī)械裝置,其使用壽命更長。
1.1.2 設(shè)計目標(biāo)
設(shè)計一款具有鬧鐘功能的數(shù)字時鐘,具體要求如下
1、 用 8 個數(shù)碼管實現(xiàn),四個一組,每組有分鐘和秒。左邊一組是時間顯示,右邊一組用來
做鬧鐘時間。
2、 當(dāng)左邊時間等于右邊時,蜂鳴器響 5 秒。
3、 鬧鐘時間和顯示時間均可通過 3 個按鍵設(shè)置。設(shè)置方法:按下按鍵 1,時鐘暫停,跳到
設(shè)置時間狀態(tài),再按下按鍵 1,回到正常狀態(tài)。通過按鍵 2,選擇要設(shè)置的位置,初始
設(shè)置秒個位,按一下,設(shè)置秒十位,再按下,設(shè)置分個位,以此類推,循環(huán)設(shè)置。通過
按鍵 3,設(shè)置數(shù)值,按一下數(shù)值加 1,如果溢出則重新變?yōu)?0。
1.1.3 系統(tǒng)結(jié)構(gòu)框圖
系統(tǒng)結(jié)構(gòu)框圖如下所示:
結(jié)構(gòu)圖共分兩個,如果使用的開發(fā)板上是矩陣鍵盤的時候,對應(yīng)的結(jié)構(gòu)圖是圖一。如果使用的開
發(fā)板上是普通按鍵的時候,對應(yīng)的結(jié)構(gòu)圖是圖二。


1.1.4 模塊功能
? 按鍵檢測模塊實現(xiàn)功能
1、將外來異步信號打兩拍處理,將異步信號同步化。
2、實現(xiàn) 20ms 按鍵消抖功能,并輸出有效按鍵信號。
? 矩陣鍵盤模塊實現(xiàn)功能
1、將外來異步信號打兩拍處理,將異步信號同步化。
2、實現(xiàn) 20ms 按鍵消抖功能。
3、實現(xiàn)矩陣鍵盤的按鍵檢測功能,并輸出有效按鍵信號。
? 時間產(chǎn)生模塊實現(xiàn)功能
1、 產(chǎn)生顯示時間數(shù)據(jù)。
2、 產(chǎn)生鬧鐘時間數(shù)據(jù),
3、根據(jù)接收到的不同的按鍵信號,產(chǎn)生暫停、開啟、設(shè)置時間的功能。
? 數(shù)碼管顯示模塊實現(xiàn)功能
1、 對接收到的時間數(shù)據(jù)進(jìn)行譯碼。
? 蜂鳴器模塊實現(xiàn)功能
1、 將接受到的顯示時間數(shù)據(jù)與鬧鐘時間數(shù)據(jù)進(jìn)行比較,控制蜂鳴器的開啟。
1.1.5 頂層信號

1.1.6 參考代碼
下面是使用普通按鍵的頂層代碼:
1. module alarm_clock( 2. clk , 3. rst_n , 4. key , 5. segment , 6. seg_sel , 7. beep 8. ); 9. input clk ; 10. input rst_n ; 11. input [2:0 ] key ; 12. output [7:0 ] segment ; 13. output [7:0 ] seg_sel ; 14. output beep ; 15. 16. wire [7:0 ] segment ; 17. wire [7:0 ] seg_sel ; 18. wire beep ; 19. wire [3:0 ] xs_sec_low ; 20. wire [3:0 ] xs_sec_high ; 21. wire [3:0 ] xs_min_low ; 22. wire [3:0 ] xs_min_high ; 23. wire [3:0 ] sec_low ; 24. wire [3:0 ] sec_high ; 25. wire [3:0 ] min_low ; 26. wire [3:0 ] min_high ; 27. wire [25:0] counter ; 28. wire [3:0 ] key_vld ; 29. wire flag_set ; 30. 31. key_module u0( 32. .clk (clk ), 33. .rst_n (rst_n ), 34. .key_in (key ), 35. .key_vld (key_vld ) 36. ); 37. time_data u1( 38. .clk (clk ), 39. .rst_n (rst_n ), 40. .key_vld (key_vld ), 41. .flag_set (flag_set ), 42. .counter (counter ), 43. .sec_low (sec_low ), 44. .sec_high (sec_high ), 45. .min_low (min_low ), 46. .min_high (min_high ), 47. .xs_sec_low (xs_sec_low ), 48. .xs_sec_high (xs_sec_high ), 49. .xs_min_low (xs_min_low ), 50. .xs_min_high (xs_min_high ) 51. ); 52. beep u2( 53. .clk (clk ), 54. .rst_n (rst_n ), 55. .flag_set (flag_set ), 56. .counter (counter ), 57. .beep (beep ), 58. .sec_low (sec_low ), 59. .sec_high (sec_high ), 60. .min_low (min_low ), 61. .min_high (min_high ), 62. .xs_sec_low (xs_sec_low ), 63. .xs_sec_high (xs_sec_high ), 64. .xs_min_low (xs_min_low ), 65. .xs_min_high (xs_min_high ) 66. ); 67. seg_disp u3( 68. .clk (clk ), 69. .rst_n (rst_n ), 70. .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}), 71. .segment (segment ), 72. .seg_sel (seg_sel ) 73. ); 74. 75. 76. endmodule
下面是使用矩陣鍵盤的頂層代碼:
1. module alarm_clock_jvzhen( 2. clk , 3. rst_n , 4. key_col , 5. key_row , 6. segment , 7. seg_sel , 8. beep 9. ); 10. input clk ; 11. input rst_n ; 12. input [3:0 ] key_col ; 13. output [3:0 ] key_row ; 14. output [7:0 ] segment ; 15. output [7:0 ] seg_sel ; 16. output beep ; 17. 18. wire [7:0 ] segment ; 19. wire [7:0 ] seg_sel ; 20. wire beep ; 21. wire [3:0 ] xs_sec_low ; 22. wire [3:0 ] xs_sec_high ; 23. wire [3:0 ] xs_min_low ; 24. wire [3:0 ] xs_min_high ; 25. wire [3:0 ] sec_low ; 26. wire [3:0 ] sec_high ; 27. wire [3:0 ] min_low ; 28. wire [3:0 ] min_high ; 29. wire [25:0] counter ; 30. wire [3:0 ] key_vld ; 31. wire flag_set ; 32. wire [15:0] key_out ; 33. 34. key_scan u0( 35. .clk (clk ), 36. .rst_n (rst_n ), 37. .key_col (key_col ), 38. .key_row (key_row ), 39. .key_en (key_vld ) 40. ); 41. time_data u1( 42. .clk (clk ), 43. .rst_n (rst_n ), 44. .key_vld (key_vld ), 45. .flag_set (flag_set ), 46. .counter (counter ), 47. .sec_low (sec_low ), 48. .sec_high (sec_high ), 49. .min_low (min_low ), 50. .min_high (min_high ), 51. .xs_sec_low (xs_sec_low ), 52. .xs_sec_high (xs_sec_high ), 53. .xs_min_low (xs_min_low ), 54. .xs_min_high (xs_min_high ) 55. ); 56. beep u2( 57. .clk (clk ), 58. .rst_n (rst_n ), 59. .flag_set (flag_set ), 60. .counter (counter ), 61. .beep (beep ), 62. .sec_low (sec_low ), 63. .sec_high (sec_high ), 64. .min_low (min_low ), 65. .min_high (min_high ), 66. .xs_sec_low (xs_sec_low ), 67. .xs_sec_high (xs_sec_high ), 68. .xs_min_low (xs_min_low ), 69. .xs_min_high (xs_min_high ) 70. ); 71. seg_disp u3( 72. .clk (clk ), 73. .rst_n (rst_n ), 74. .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}), 75. .segment (segment ), 76. .seg_sel (seg_sel ) 77. ); 78. 79. 80. endmodule
1.2 按鍵檢測模塊設(shè)計
1.2.1 接口信號

1.2.2 設(shè)計思路
在前面的案例中已經(jīng)有按鍵檢測的介紹,所以這里不在過多介紹,詳細(xì)介紹請看下方鏈接:
【每周 FPGA 案例】至簡設(shè)計系列_按鍵控制數(shù)字時鐘
1.2.3 參考代碼
使用明德?lián)P的計數(shù)器模板,可以很快速很熟練地寫出按鍵消抖模塊。
1. module alarm_clock_jvzhen( 2. clk , 3. rst_n , 4. key_col , 5. key_row , 6. segment , 7. seg_sel , 8. beep 9. ); 10. input clk ; 11. input rst_n ; 12. input [3:0 ] key_col ; 13. output [3:0 ] key_row ; 14. output [7:0 ] segment ; 15. output [7:0 ] seg_sel ; 16. output beep ; 17. 18. wire [7:0 ] segment ; 19. wire [7:0 ] seg_sel ; 20. wire beep ; 21. wire [3:0 ] xs_sec_low ; 22. wire [3:0 ] xs_sec_high ; 23. wire [3:0 ] xs_min_low ; 24. wire [3:0 ] xs_min_high ; 25. wire [3:0 ] sec_low ; 26. wire [3:0 ] sec_high ; 27. wire [3:0 ] min_low ; 28. wire [3:0 ] min_high ; 29. wire [25:0] counter ; 30. wire [3:0 ] key_vld ; 31. wire flag_set ; 32. wire [15:0] key_out ; 33. 34. key_scan u0( 35. .clk (clk ), 36. .rst_n (rst_n ), 37. .key_col (key_col ), 38. .key_row (key_row ), 39. .key_en (key_vld ) 40. ); 41. time_data u1( 42. .clk (clk ), 43. .rst_n (rst_n ), 44. .key_vld (key_vld ), 45. .flag_set (flag_set ), 46. .counter (counter ), 47. .sec_low (sec_low ), 48. .sec_high (sec_high ), 49. .min_low (min_low ), 50. .min_high (min_high ), 51. .xs_sec_low (xs_sec_low ), 52. .xs_sec_high (xs_sec_high ), 53. .xs_min_low (xs_min_low ), 54. .xs_min_high (xs_min_high ) 55. ); 56. beep u2( 57. .clk (clk ), 58. .rst_n (rst_n ), 59. .flag_set (flag_set ), 60. .counter (counter ), 61. .beep (beep ), 62. .sec_low (sec_low ), 63. .sec_high (sec_high ), 64. .min_low (min_low ), 65. .min_high (min_high ), 66. .xs_sec_low (xs_sec_low ), 67. .xs_sec_high (xs_sec_high ), 68. .xs_min_low (xs_min_low ), 69. .xs_min_high (xs_min_high ) 70. ); 71. seg_disp u3( 72. .clk (clk ), 73. .rst_n (rst_n ), 74. .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}), 75. .segment (segment ), 76. .seg_sel (seg_sel ) 77. ); 78. 79. 80. endmodule
1.3 矩陣鍵盤模塊設(shè)計
1.3.1 接口信號

1.3.2 設(shè)計思路
在前面的案例中已經(jīng)有矩陣鍵盤的介紹,所以這里不在過多介紹,詳細(xì)介紹請看下方鏈接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310
1.3.3 參考代碼
1. always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1'b0)begin 3. key_col_ff0 <= 4'b1111; 4. key_col_ff1 <= 4'b1111; 5. end 6. else begin 7. key_col_ff0 <= key_col ; 8. key_col_ff1 <= key_col_ff0; 9. end 10. end 11. 12. 13. always @(posedge clk or negedge rst_n) begin 14. if (rst_n==0) begin 15. shake_cnt <= 0; 16. end 17. else if(add_shake_cnt) begin 18. if(end_shake_cnt) 19. shake_cnt <= 0; 20. else 21. shake_cnt <= shake_cnt+1 ; 22. end 23. end 24. assign add_shake_cnt = key_col_ff1!=4'hf; 25. assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS-1 ; 26. 27. 28. always @(posedge clk or negedge rst_n)begin 29. if(rst_n==1'b0)begin 30. state_c <= CHK_COL; 31. end 32. else begin 33. state_c <= state_n; 34. end 35. end 36. 37. always @(*)begin 38. case(state_c) 39. CHK_COL: begin 40. if(col2row_start )begin 41. state_n = CHK_ROW; 42. end 43. else begin 44. state_n = CHK_COL; 45. end 46. end 47. CHK_ROW: begin 48. if(row2del_start)begin 49. state_n = DELAY; 50. end 51. else begin 52. state_n = CHK_ROW; 53. end 54. end 55. DELAY : begin 56. if(del2wait_start)begin 57. state_n = WAIT_END; 58. end 59. else begin 60. state_n = DELAY; 61. end 62. end 63. WAIT_END: begin 64. if(wait2col_start)begin 65. state_n = CHK_COL; 66. end 67. else begin 68. state_n = WAIT_END; 69. end 70. end 71. default: state_n = CHK_COL; 72. endcase 73. end 74. assign col2row_start = state_c==CHK_COL && end_shake_cnt; 75. assign row2del_start = state_c==CHK_ROW && row_index==3 && end_row_cnt; 76. assign del2wait_start= state_c==DELAY && end_row_cnt; 77. assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf; 78. 79. always @(posedge clk or negedge rst_n)begin 80. if(rst_n==1'b0)begin 81. key_row <= 4'b0; 82. end 83. else if(state_c==CHK_ROW)begin 84. key_row <= ~(1'b1 << row_index); 85. end 86. else begin 87. key_row <= 4'b0; 88. end 89. end 90. 91. 92. always @(posedge clk or negedge rst_n) begin 93. if (rst_n==0) begin 94. row_index <= 0; 95. end 96. else if(add_row_index) begin 97. if(end_row_index) 98. row_index <= 0; 99. else 100. row_index <= row_index+1 ; 101. end 102. else if(state_c!=CHK_ROW)begin 103. row_index <= 0; 104. end 105. end 106. assign add_row_index = state_c==CHK_ROW && end_row_cnt; 107. assign end_row_index = add_row_index && row_index == 4-1 ; 108. 109. 110. always @(posedge clk or negedge rst_n) begin 111. if (rst_n==0) begin 112. row_cnt <= 0; 113. end 114. else if(add_row_cnt) begin 115. if(end_row_cnt) 116. row_cnt <= 0; 117. else 118. row_cnt <= row_cnt+1 ; 119. end 120. end 121. assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY; 122. assign end_row_cnt = add_row_cnt && row_cnt == 16-1 ; 123. 124. 125. always @(posedge clk or negedge rst_n)begin 126. if(rst_n==1'b0)begin 127. key_col_get <= 0; 128. end 129. else if(state_c==CHK_COL && end_shake_cnt ) begin 130. if(key_col_ff1==4'b1110) 131. key_col_get <= 0; 132. else if(key_col_ff1==4'b1101) 133. key_col_get <= 1; 134. else if(key_col_ff1==4'b1011) 135. key_col_get <= 2; 136. else 137. key_col_get <= 3; 138. end 139. end 140. 141. 142. always @(posedge clk or negedge rst_n)begin 143. if(rst_n==1'b0)begin 144. key_out <= 0; 145. end 146. else if(state_c==CHK_ROW && end_row_cnt)begin 147. key_out <= {row_index,key_col_get}; 148. end 149. else begin 150. key_out <= 0; 151. end 152. end 153. 154. always @(posedge clk or negedge rst_n)begin 155. if(rst_n==1'b0)begin 156. key_vld <= 1'b0; 157. end 158. else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin 159. key_vld <= 1'b1; 160. end 161. else begin 162. key_vld <= 1'b0; 163. end 164. end 165. 166. 167. always @(*)begin 168. if(rst_n==1'b0)begin 169. key_en = 0; 170. end 171. else if(key_vld && key_out==0)begin 172. key_en = 4'b0001; 173. end 174. else if(key_vld && key_out==1)begin 175. key_en = 4'b0010; 176. end 177. else if(key_vld && key_out==2)begin 178. key_en = 4'b0100; 179. end 180. else begin 181. key_en = 0; 182. end 183. end
1.4 時間產(chǎn)生模塊設(shè)計
1.4.1 接口信號

1.4.2 設(shè)計思路
根據(jù)題目功能要求可知,要顯示的時間就是在完整的數(shù)字時鐘的基礎(chǔ)上,減少了時的高位和低位
的顯示,再介紹架構(gòu)之前,先了解一下本模塊其他幾個信號的作用。
設(shè)置狀態(tài)指示信號 flag_set:該信號初始狀態(tài)為低電平,表示模塊處于正常工作狀態(tài),當(dāng)按下按
鍵 key1 時,設(shè)置狀態(tài)指示信號進(jìn)行翻轉(zhuǎn),變?yōu)楦唠娖剑硎具M(jìn)入到設(shè)置狀態(tài)。
設(shè)置位計數(shù)器 sel_cnt:該計數(shù)器表示要設(shè)置的位,初始狀態(tài)為 0,表示可以設(shè)置鬧鐘的秒低位,
當(dāng)其為 1 時表示可以設(shè)置鬧鐘的秒的高位,按照這樣的順序依次類推,當(dāng)其為 7 的時候,表示可以設(shè)
置顯示時間的分高位。加一條件為 key_vld[1]==1'b1,表示按下按鍵 key2 的時候加一;結(jié)束條件為 8,
顯示時間的四個數(shù)碼管加上鬧鐘的四個數(shù)碼管共 8 個,所以數(shù) 8 個就清零。
由此可提出 5 個計數(shù)器的架構(gòu),如下圖所示:

該架構(gòu)由 5 個計數(shù)器組成:時鐘計數(shù)器 counter、秒低位計數(shù)器 xs_sec_low、秒高位計數(shù)器 xs
_sec_high、分低位計數(shù)器 xs_min_low、分高位計數(shù)器 xs_min_high。
時鐘計數(shù)器 counter:用于計算 1 秒的時鐘個數(shù),加一條件為 flag_set==1'b0,表示剛上電時開
始計數(shù),key1 按下之后,進(jìn)入設(shè)置模式,停止計數(shù),再按下又重新開始計數(shù);結(jié)束條件為 5000000
0,表示數(shù)到 1 秒就清零。
秒低位計數(shù)器 xs_sec_low:用于對 1 秒進(jìn)行計數(shù),加一條件為(sel_cnt==5-1 && set_en) || e
nd_counter,表示在設(shè)置狀態(tài)下可通過按鍵 key3 來控制加一,或者在正常狀態(tài)時數(shù)到 1 秒就加 1;
結(jié)束條件為 10,表示數(shù)到 10 秒就清零。
秒高位計數(shù)器 xs_sec_high:用于對 10 秒進(jìn)行計數(shù),加一條件為(sel_cnt==6-1 && set_en) ||
end_xs_sec_low,表示在設(shè)置狀態(tài)下可通過按鍵 key3 來控制加一,或者在正常狀態(tài)時數(shù)到 10 秒就
加 1;結(jié)束條件為 6,表示數(shù)到 60 秒就清零。
分低位計數(shù)器 xs_min_low:用于對 1 分進(jìn)行計數(shù),加一條件為(sel_cnt==7-1 && set_en) || e
nd_xs_sec_high,表示在設(shè)置狀態(tài)下可通過按鍵 key3 來控制加一,或者在正常狀態(tài)時數(shù)到 1 分就加
1;結(jié)束條件為 10,表示數(shù)到 10 分就清零。
分高位計數(shù)器 xs_min_high:用于對 10 分進(jìn)行計數(shù),加一條件為(sel_cnt==8-1 && set_en) ||
end_xs_min_low,表示在設(shè)置狀態(tài)下可通過按鍵 key3 來控制加一,或者在正常狀態(tài)時數(shù)到 10 分就
加 1;結(jié)束條件為 6,表示數(shù)到 60 分就清零。
上面介紹了顯示時間的計數(shù)器架構(gòu),下面我們來思考一下鬧鐘部分的架構(gòu)。
我們都知道鬧鐘的工作原理,它本身不會自動計數(shù),需要我們手動設(shè)置。根據(jù)本設(shè)計的功能要求,
有四個數(shù)碼管來顯示設(shè)置的鬧鐘秒的高低位和分的高低位,因此我們提出四個計數(shù)器組成的架構(gòu),這
四個計數(shù)器相互獨(dú)立,互不干涉,結(jié)構(gòu)圖如下:

該架構(gòu)由 4 個計數(shù)器組成:秒低位計數(shù)器 sec_low、秒高位計數(shù)器 sec_high、分低位計數(shù)器 mi
n_low、分高位計數(shù)器 min_high。
秒低位計數(shù)器 sec_low:用于對鬧鐘秒的低位進(jìn)行計數(shù),加一條件為 sel_cnt==1-1 && set_en,
表示在設(shè)置狀態(tài)下通過按鍵 key3 來控制加一 ;結(jié)束條件為 10,表示最大能設(shè)置為 9,超過之后便
清零。
秒高位計數(shù)器 sec_high:用于對鬧鐘秒的高位進(jìn)行計數(shù),加一條件為 sel_cnt==2-1 && set_en,
表示在設(shè)置狀態(tài)下可通過按鍵 key3 來控制加一;結(jié)束條件為 6,表示最大能設(shè)置為 5,超過之后便
清零。
分低位計數(shù)器 min_low:用于對鬧鐘分的低位進(jìn)行計數(shù),加一條件為 sel_cnt==3-1 && set_en,
表示在設(shè)置狀態(tài)下可通過按鍵 key3 來控制加一;結(jié)束條件為 10,表示最大能設(shè)置為 9,超過之后便
清零。
分高位計數(shù)器 min_high:用于對鬧鐘分高位進(jìn)行計數(shù),加一條件為 sel_cnt==4-1 && set_en,
表示在設(shè)置狀態(tài)下可通過按鍵 key3 來控制加一;結(jié)束條件為 6,表示最大能設(shè)置為 5,超過之后便
清零。
1.4.3 參考代碼
使用明德?lián)P的計數(shù)器模板,可以很快速很熟練地寫出時間產(chǎn)生模塊。
always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1'b0)begin 3. flag_set<=1'b0; 4. end 5. else if(key_vld[0]==1'b1)begin 6. flag_set<=~flag_set; 7. end 8. else begin 9. flag_set<=flag_set; 10. end 11. end 12. 13. 14. always @(posedge clk or negedge rst_n) begin 15. if (rst_n==0) begin 16. sel_cnt <= 0; 17. end 18. else if(add_sel_cnt) begin 19. if(end_sel_cnt) 20. sel_cnt <= 0; 21. else 22. sel_cnt <= sel_cnt+1 ; 23. end 24. end 25. assign add_sel_cnt = key_vld[1]==1'b1; 26. assign end_sel_cnt = add_sel_cnt && sel_cnt == 8-1 ; 27. 28. 29. always @(posedge clk or negedge rst_n)begin 30. if(rst_n==1'b0)begin 31. set_en<=1'b0; 32. end 33. else if(flag_set==1'b1 && key_vld[2]==1'b1)begin 34. set_en<=1'b1; 35. end 36. else begin 37. set_en<=1'b0; 38. end 39. end 40. 41. 42. always @(posedge clk or negedge rst_n) begin 43. if (rst_n==0) begin 44. counter <= 0; 45. end 46. else if(add_counter) begin 47. if(end_counter) 48. counter <= 0; 49. else 50. counter <= counter+1 ; 51. end 52. end 53. assign add_counter = flag_set==1'b0; 54. assign end_counter = add_counter && counter == 26'd5000_0000-1; 55. 56. 57. always @(posedge clk or negedge rst_n) begin 58. if (rst_n==0) begin 59. sec_low <= 0; 60. end 61. else if(add_sec_low) begin 62. if(end_sec_low) 63. sec_low <= 0; 64. else 65. sec_low <= sec_low+1 ; 66. end 67. end 68. assign add_sec_low = sel_cnt==1-1 && set_en; 69. assign end_sec_low = add_sec_low && sec_low == 10-1 ; 70. 71. 72. always @(posedge clk or negedge rst_n) begin 73. if (rst_n==0) begin 74. sec_high <= 0; 75. end 76. else if(add_sec_high) begin 77. if(end_sec_high) 78. sec_high <= 0; 79. else 80. sec_high <= sec_high+1 ; 81. end 82. end 83. assign add_sec_high = sel_cnt==2-1 && set_en; 84. assign end_sec_high = add_sec_high && sec_high == 6-1 ; 85. 86. 87. always @(posedge clk or negedge rst_n) begin 88. if (rst_n==0) begin 89. min_low <= 0; 90. end 91. else if(add_min_low) begin 92. if(end_min_low) 93. min_low <= 0; 94. else 95. min_low <= min_low+1 ; 96. end 97. end 98. assign add_min_low = sel_cnt==3-1 && set_en; 99. assign end_min_low = add_min_low && min_low == 10-1 ; 100. 101. always @(posedge clk or negedge rst_n) begin 102. if (rst_n==0) begin 103. min_high <= 0; 104. end 105. else if(add_min_high) begin 106. if(end_min_high) 107. min_high <= 0; 108. else 109. min_high <= min_high+1 ; 110. end 111. end 112. assign add_min_high = sel_cnt==4-1 && set_en; 113. assign end_min_high = add_min_high && min_high == 6-1 ; 114. 115. 116. always @(posedge clk or negedge rst_n) begin 117. if (rst_n==0) begin 118. xs_sec_low <= 0; 119. end 120. else if(add_xs_sec_low) begin 121. if(end_xs_sec_low) 122. xs_sec_low <= 0; 123. else 124. xs_sec_low <= xs_sec_low+1 ; 125. end 126. end 127. assign add_xs_sec_low = (sel_cnt==5-1 && set_en) || end_counter; 128. assign end_xs_sec_low = add_xs_sec_low && xs_sec_low == 10-1 ; 129. 130. 131. always @(posedge clk or negedge rst_n) begin 132. if (rst_n==0) begin 133. xs_sec_high <= 0; 134. end 135. else if(add_xs_sec_high) begin 136. if(end_xs_sec_high) 137. xs_sec_high <= 0; 138. else 139. xs_sec_high <= xs_sec_high+1 ; 140. end 141. end 142. assign add_xs_sec_high = (sel_cnt==6-1 && set_en) || end_xs_sec_low; 143. assign end_xs_sec_high = add_xs_sec_high && xs_sec_high == 6-1 ; 144. 145. 146. always @(posedge clk or negedge rst_n) begin 147. if (rst_n==0) begin 148. xs_min_low <= 0; 149. end 150. else if(add_xs_min_low) begin 151. if(end_xs_min_low) 152. xs_min_low <= 0; 153. else 154. xs_min_low <= xs_min_low+1 ; 155. end 156. end 157. assign add_xs_min_low = (sel_cnt==7-1 && set_en) || end_xs_sec_high; 158. assign end_xs_min_low = add_xs_min_low && xs_min_low == 10-1 ; 159. 160. 161. always @(posedge clk or negedge rst_n) begin 162. if (rst_n==0) begin 163. xs_min_high <= 0; 164. end 165. else if(add_xs_min_high) begin 166. if(end_xs_min_high) 167. xs_min_high <= 0; 168. else 169. xs_min_high <= xs_min_high+1 ; 170. end 171. end 172. assign add_xs_min_high = (sel_cnt==8-1 && set_en) || end_xs_min_low; 173. assign end_xs_min_high = add_xs_min_high && xs_min_high == 6-1 ;
1.5 數(shù)碼管顯示模塊設(shè)計
1.5.1 接口信號

1.5.2 設(shè)計思路
在前面的案例中已經(jīng)有數(shù)碼管顯示的介紹,所以這里不在過多介紹,詳細(xì)介紹請看下方鏈接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=399
1.5.3 參考代碼
174. always @(posedge clk or negedge rst_n) begin 175. if (rst_n==0) begin 176. delay <= 0; 177. end 178. else if(add_delay) begin 179. if(end_delay) 180. delay <= 0; 181. else 182. delay <= delay+1 ; 183. end 184. end 185. assign add_delay = 1; 186. assign end_delay = add_delay && delay == 2000-1 ; 187. 188. 189. 190. 191. always @(posedge clk or negedge rst_n) begin 192. if (rst_n==0) begin 193. delay_time <= 0; 194. end 195. else if(add_delay_time) begin 196. if(end_delay_time) 197. delay_time <= 0; 198. else 199. delay_time <= delay_time+1 ; 200. end 201. end 202. assign add_delay_time = end_delay; 203. assign end_delay_time = add_delay_time && delay_time == 8-1 ; 204. 205. 206. assign segment_tmp = segment_data[(1+delay_time)*4-1 -:4]; 207. always @(posedge clk or negedge rst_n)begin 208. if(rst_n==1'b0)begin 209. segment <= ZERO; 210. end 211. else begin 212. case(segment_tmp) 213. 4'd0:segment <= ZERO; 214. 4'd1:segment <= ONE ; 215. 4'd2:segment <= TWO ; 216. 4'd3:segment <= THREE; 217. 4'd4:segment <= FOUR ; 218. 4'd5:segment <= FIVE ; 219. 4'd6:segment <= SIX ; 220. 4'd7:segment <= SEVEN; 221. 4'd8:segment <= EIGHT; 222. 4'd9:segment <= NINE ; 223. default:begin 224. segment <= segment; 225. end 226. endcase 227. end 228. end 229. 230. 231. always @(posedge clk or negedge rst_n)begin 232. if(rst_n==1'b0)begin 233. seg_sel <= 8'b1111_1111; 234. end 235. else begin 236. seg_sel <= ~(8'b1<<delay_time); 237. end 238. end
1.6 蜂鳴器模塊設(shè)計
1.6.1 接口信號

1.6.2 設(shè)計思路
本模塊主要通過將顯示時間與設(shè)置的鬧鐘時間進(jìn)行比較,如果相同的話,就控制 beep 拉低,持
續(xù)時間為 5 秒。由此提出一個計數(shù)器的架構(gòu),如下圖所示。

該架構(gòu)由蜂鳴器控制信號 beep、秒計數(shù)器 miao 和鬧鐘觸發(fā)指示信號 flag_add 組成。
秒計數(shù)器秒:用于對 5 秒的時間進(jìn)行計數(shù),加一條件為 flag_add && end_counter,表示當(dāng)鬧
鐘被觸發(fā),并且經(jīng)過 1 秒的時間就加一;結(jié)束條件為 5,表示數(shù)完 5 秒就清零。
鬧鐘觸發(fā)指示信號 flag:當(dāng)其為高電平時表示鬧鐘被觸發(fā),低電平表示沒有被觸發(fā)。初始狀態(tài)為
低電平,從低變高的條件為 sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min
_low&&min_high==xs_min_high&&init&&!key1_func,表示當(dāng)顯示時間的秒高低位、分高低位和鬧鐘
設(shè)置的秒高低位、分高低位相等,同時不處于剛上電的初始狀態(tài)和設(shè)置狀態(tài)時,鬧鐘被觸發(fā);從高變
低的條件為 end_miao,表示當(dāng) 5 秒數(shù)完之后,就拉低。
蜂鳴器控制信號 beep:當(dāng)其為低電平時,控制蜂鳴器響,為高電平時不響。初始狀態(tài)為高電平,
從高變低的條件為 flag_add,表示計數(shù)器開始計數(shù)之后便將其拉低,當(dāng)檢測到 flag_add=0 的時候,
便將其拉高。
1.6.3 參考代碼
239. always @(posedge clk or negedge rst_n)begin 240. if(rst_n==1'b0)begin 241. flag_add <= 0; 242. end 243. else if(sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min_low&&min_high==xs_min_high&&init&&flag_set==0)begin 244. flag_add <= 1; 245. end 246. else if(end_miao)begin 247. flag_add <= 0; 248. end 249. end 250. 251. 252. always@(*)begin 253. if(!sec_low&&!sec_high&&!min_low&&!min_high)begin 254. init=0; 255. end 256. else begin 257. init=1; 258. end 259. end 260. 261. 262. always @(posedge clk or negedge rst_n) begin 263. if (rst_n==0) begin 264. miao <= 0; 265. end 266. else if(add_miao) begin 267. if(end_miao) 268. miao <= 0; 269. else 270. miao <= miao+1 ; 271. end 272. end 273. assign add_miao = flag_add && end_counter; 274. assign end_miao = add_miao && miao == 5-1 ; 275. 276. 277. always@(posedge clk or negedge rst_n)begin 278. if(rst_n==1'b0)begin 279. beep<=1'b1; 280. end 281. else if(flag_add)begin 282. beep<=1'b0; 283. end 284. else 285. beep<=1'b1; 286. end
1.7 效果和總結(jié)
? 下圖是該工程在 mp801 開發(fā)板上的現(xiàn)象
其中按鍵 s4 控制數(shù)字時鐘的暫停與開始,按鍵 s3 來選擇需要設(shè)置的位,按鍵 s2 設(shè)置數(shù)值。左
邊四個數(shù)碼管顯示的是時鐘的時間,右邊四個數(shù)碼管顯示的是鬧鐘設(shè)置的時間。

? 下圖是該工程在 db603 開發(fā)板上的現(xiàn)象
其中按鍵 s1 控制數(shù)字時鐘的暫停與開始,按鍵 s2 來選擇需要設(shè)置的位,按鍵 s3 設(shè)置數(shù)值。左
邊四個數(shù)碼管顯示的是時鐘的時間,右邊四個數(shù)碼管顯示的是鬧鐘設(shè)置的時間。

? 下圖是該工程在 ms980 試驗箱上的現(xiàn)象
其中按鍵 s1 控制數(shù)字時鐘的暫停與開始,按鍵 s2 來選擇需要設(shè)置的位,按鍵 s3 設(shè)置數(shù)值。左
邊四個數(shù)碼管顯示的是時鐘的時間,右邊四個數(shù)碼管顯示的是鬧鐘設(shè)置的時間。

由于該項目的上板現(xiàn)象是動態(tài)的,開始、暫停、時間設(shè)置等現(xiàn)象無法通過圖片表現(xiàn)出來,想觀看
完整現(xiàn)象的朋友可以看一下現(xiàn)象演示的視頻。
感興趣的朋友也可以訪問明德?lián)P論壇(http://www.fpgabbs.cn/)進(jìn)行 FPGA 相關(guān)工程設(shè)計學(xué)習(xí),
也可以看一下我們往期的文章:
《基于 FPGA 的密碼鎖設(shè)計》
《波形相位頻率可調(diào) DDS 信號發(fā)生器》
《基于 FPGA 的曼徹斯特編碼解碼設(shè)計》
《基于 FPGA 的出租車計費(fèi)系統(tǒng)》
《數(shù)電基礎(chǔ)與 Verilog 設(shè)計》
《基于 FPGA 的頻率、電壓測量》
《基于 FPGA 的漢明碼編碼解碼設(shè)計》
《關(guān)于鎖存器問題的討論》
《阻塞賦值與非阻塞賦值》
《參數(shù)例化時自動計算位寬的解決辦法》
明德?lián)P是一家專注于 FPGA 領(lǐng)域的專業(yè)性公司,公司主要業(yè)務(wù)包括開發(fā)板、教育培訓(xùn)、項目承
接、人才服務(wù)等多個方向。
點撥開發(fā)板——學(xué)習(xí) FPGA 的入門之選。
MP801 開發(fā)板——千兆網(wǎng)、ADDA、大容量 SDRAM 等,學(xué)習(xí)和項目需求一步到位。
網(wǎng)絡(luò)培訓(xùn)班——不管時間和空間,明德?lián)P隨時在你身邊,助你快速學(xué)習(xí) FPGA。
周末培訓(xùn)班——明天的你會感激現(xiàn)在的努力進(jìn)取,升職加薪明德?lián)P來助你。
就業(yè)培訓(xùn)班——七大企業(yè)級項目實訓(xùn),獲得豐富的項目經(jīng)驗,高薪就業(yè)。
專題課程——高手修煉課:提升設(shè)計能力;實用調(diào)試技巧課:提升定位和解決問題能力;FIFO 架構(gòu)
設(shè)計課:助你快速成為架構(gòu)設(shè)計師;時序約束、數(shù)字信號處理、PCIE、綜合項目實踐課等你來選。
項目承接——承接企業(yè) FPGA 研發(fā)項目。
人才服務(wù)——提供人才推薦、人才代培、人才派遣等服務(wù)。