至简设计系列_简易计算器
–作者:小黑同學
本文為明德揚原創及錄用文章,轉載請注明出處!
1.1 總體設計
1.1.1 概述
計算器是近代人發明的可以進行數字運算的機器。現代的電子計算器能進行數學運算的手持電子機器,擁有集成電路芯片,但結構比電腦簡單得多,可以說是第一代的電子計算機,且功能也較弱,但較為方便與廉價,可廣泛運用于商業交易中,是必備的辦公用品之一。除顯示計算結果外,還常有溢出指示、錯誤指示等。計算器電源采用交流轉換器或電池。為了節省電能,計算器都采用CMOS工藝制作的大規模集成電路。
計算器一般由運算器、控制器、存儲器、鍵盤、顯示器、電源和一些可選外圍設備及電子配件,通過人工或機器設備組成,抵擋計算器的運算器、控制器由數字邏輯電路實現簡單的串行運算
計算器是最早的計算工具,例如:古代印加人利用許多顏色的繩結來計數或者記錄歷史,還有古希臘人的安提凱希拉裝置,中國的算盤等。中國古代最早采用的一種計算工具叫籌策,又被叫做算籌。
1.1.2 設計目標
簡易計算器支持簡單的四則運算(支持負數),在此基礎上,添加了連續運算功能。計算器面板如下:
1、計算器通過矩陣鍵盤模擬按鍵輸入,并通過數碼管顯示。
2、計算器有“0、1、2、3、4、5、6、7、8、9、+、-、*、/、C、=”共16個按鍵。
3、計算器不支持輸入負數,運算結果支持負數但不支持小數。
4、運算數1、運算數2以及運算結果最大支持8位。其中,運算數1和運算結果的位數包括符號位“-”。
5、運算數1和運算數2的默認值為0.
6、計算器支持連續運算,允許在輸入運算數2后按下運算符,或者得出運算結果后按下運算符。
7、當運算結果溢出時,數碼管顯示8個F。
8、當操作數1或者操作數2的長度溢出時,蜂鳴器會響。
1.1.3 系統結構框圖
系統結構框圖如下所示:
圖一
1.1.4 模塊功能
?鍵盤掃描模塊實現功能
1、將外來異步信號打兩拍處理,將異步信號同步化。
2、實現20ms按鍵消抖功能。
3、實現矩陣鍵盤的按鍵檢測功能,并輸出有效按鍵信號。
?工作狀態選擇模塊實現功能
1、根據接收的不同的按鍵信號,判斷和決定計算器的工作狀態。共有5種狀態,輸入運算數1(OP_1)、運算符(OPER)、輸入運算數2(OP_2)、輸出結果(RESULT)、結果錯誤(ERROR)
?運算數1模塊實現功能
1、當計算器處于運算數1狀態下,任何連續輸入的數字(不超過8位)都將存放在該模塊中,作為運算數1.
2、當運算數已經到達8位時,此時無論輸入任何數字,運算數1不變。
3、當計算器經過一次運算后(按下等號或者在運算數2狀態下按下運算符),運算數去存放結果result。
?運算符模塊實現功能
1、保存最新按下的運算符。
?運算數2模塊實現功能
1、當計算器處于運算數2狀態下,任何連續輸入的數字(不超過8位)都將存放在該模塊中,作為運算數2,默認值為0。
2、當運算數2已經到達8(包括符號位“-”),此時無論輸入任何數字,運算數2不變。
?運算單元模塊實現功能
1、當計算器處于運算數2狀態下按下運算符或者在任何狀態下按下等號時,該模塊根據此時運算數1、運算數2以及運算符的值,進行運算。
2、若運算結果溢出,或者長度大于8位(包括符號位“-”)或者除數為0時,輸出8個F。
3、最多保留運算結果整數部分的8個有效數字,不保留任何小數。
?顯示對象選則模塊實現功能
1、該模塊的作用是根據當前計算器的工作狀態來選擇數碼管的顯示內容。
?數碼管顯示模塊實現功能
1、該模塊的作用是對顯示對象選擇模塊的顯示數據輸出信號進行數碼管顯示。
?蜂鳴器模塊實現功能
1、該模塊的作用是對各種錯誤輸入或輸出進行響鈴警告。
1.1.5 頂層信號
| clk | 輸入 | 系統時鐘,50Mhz |
| rst_n | 輸入 | 低電平復位信號 |
| Key_col | 輸入 | 4位矩陣鍵盤列信號,默認高電平,開發板按鍵為普通按鍵時,不需要該信號 |
| Key_row | 輸出 | 4位矩陣鍵盤行信號,默認低電平,開發板按鍵為普通按鍵時,不需要該信號 |
| Segment | 輸出 | 8位數碼管段選信號 |
| Seg_sel | 輸出 | 8位數碼管位選信號 |
| beep | 輸出 | 1位蜂鳴器控制信號 |
1.1.6 參考代碼
1.module calc_project( 2. clk , 3. rst_n , 4. key_col , 5. key_row , 6. seg_sel , 7. segment , 8. beep 9. ); 10. 11. parameter KEY_WID = 4 ; 12. parameter STATE_WID = 5 ; 13. parameter NUM_WID = 27 ; 14. parameter SEG_NUM = 8 ; 15. parameter SEG_WID = 8 ; 16. 17. input clk ; 18. input rst_n ; 19. input [KEY_WID-1:0] key_col ; 20. output [KEY_WID-1:0] key_row ; 21. output [SEG_NUM-1:0] seg_sel ; 22. output [SEG_WID-1:0] segment ; 23. output beep ; 24. 25. wire [KEY_WID-1:0] key_num ; 26. wire key_vld ; 27. wire [KEY_WID-1:0] key_num_out ; 28. wire [KEY_WID-1:0] key_vld_out ; 29. wire [STATE_WID-1:0] state_c ; 30. wire [NUM_WID-1:0] op_1 ; 31. wire op_1_err ; 32. wire [KEY_WID-1:0] oper ; 33. wire [NUM_WID-1:0] op_2 ; 34. wire op_2_err ; 35. wire [NUM_WID-1:0] result ; 36. wire result_err ; 37. wire result_neg ; 38. wire [SEG_NUM*4-1:0] display ; 39. wire display_vld ; 40. 41. key_scan key_scan_prj( 42. .clk (clk ) , 43. .rst_n (rst_n ) , 44. .key_col (key_col) , 45. .key_row (key_row) , 46. .key_out (key_num) , 47. .key_vld (key_vld) 48. ); 49. 50. work_state work_state_prj( 51. .clk (clk ) , 52. .rst_n (rst_n ) , 53. .key_num (key_num ) , 54. .key_vld (key_vld ) , 55. .result_err (result_err ) , 56. .key_num_out(key_num_out) , 57. .key_vld_out(key_vld_out) , 58. .state_c (state_c ) 59. ); 60. 61. op_1 op_1_prj( 62. .clk (clk ) , 63. .rst_n (rst_n ) , 64. .key_num_out(key_num_out) , 65. .key_vld_out(key_vld_out) , 66. .state_c (state_c ) , 67. .result (result ) , 68. .op_1 (op_1 ) , 69. .op_1_err (op_1_err ) 70. ); 71. 72. oper oper_prj( 73. .clk (clk ) , 74. .rst_n (rst_n ) , 75. .key_num_out(key_num_out) , 76. .key_vld_out(key_vld_out) , 77. .state_c (state_c ) , 78. .oper (oper ) 79. ); 80. 81. op_2 op_2_prj( 82. .clk (clk ) , 83. .rst_n (rst_n ) , 84. .key_num_out(key_num_out) , 85. .key_vld_out(key_vld_out) , 86. .state_c (state_c ) , 87. .op_1 (op_1 ) , 88. .op_2 (op_2 ) , 89. .op_2_err (op_2_err ) 90. ); 91. 92. result result_prj( 93. .clk (clk ) , 94. .rst_n (rst_n ) , 95. .key_num_out(key_num_out) , 96. .key_vld_out(key_vld_out) , 97. .state_c (state_c ) , 98. .op_1 (op_1 ) , 99. .oper (oper ) , 100. .op_2 (op_2 ) , 101. .result (result ) , 102. .result_err (result_err ) , 103. .result_neg (result_neg ) 104. ); 105. 106. display_sel display_sel_prj( 107. .clk (clk ) , 108. .rst_n (rst_n ) , 109. .state_c (state_c ) , 110. .op_1 (op_1 ) , 111. .op_2 (op_2 ) , 112. .result (result ) , 113. .result_neg (result_neg ) , 114. .display (display ) , 115. .display_vld(display_vld) 116. ); 117. 118. segment segment_prj( 119. .rst_n (rst_n ) , 120. .clk (clk ) , 121. .display (display ) , 122. .display_vld(display_vld) , 123. .seg_sel (seg_sel ) , 124. .segment (segment ) 125. ); 126. 127. beep beep_prj( 128. .clk (clk ) , 129. .rst_n (rst_n ) , 130. .op_1_err (op_1_err ) , 131. .op_2_err (op_2_err ) , 132. .result_err (result_err ) , 133. .beep (beep ) 134. ); 135. 136.endmodule1.2 鍵盤掃描模塊設計
1.2.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| key_col | 輸入 | 矩陣鍵盤列輸入信號 |
| Key_row | 輸出 | 矩陣鍵盤行輸出信號 |
| Key_out | 輸出 | 按鍵位置輸出信號,key_vld有效時,該信號有效。 |
| Key_vld | 輸出 | 按鍵有效指示信號,高電平有效 |
1.2.2 設計思路
在前面的案例中已經有矩陣鍵盤的介紹,所以這里不在過多介紹,詳細介紹請看下方鏈接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310
1.2.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.end1.3 工作狀態選擇模塊設計
1.3.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| key_vld | 輸入 | 按鍵按下指示信號 |
| Key_num | 輸入 | 按鍵位置輸入信號,key_vld有效時,該信號有效 |
| Result_err | 輸入 | 運算結果錯誤指示信號 |
| Key_num_out | 輸出 | 計算器按下位置輸出信號,key_vld_out有效時,該信號有效。 |
| Key_vld_out | 輸出 | 計算器按鍵按下有效指示信號,高電平有效。 |
| State_c | 輸出 | 計算器工作狀態指示信號 |
1.3.2 設計思路
該模塊的主要功能是根據按下的按鍵進行不同來判斷和決定計算器的工作狀態。一條等式可以寫成:運算數1+操作符+運算數2+等號+結果的形式。考慮到結果錯誤的情況,我將本模塊的狀態劃分為5個,分別是:輸入運算數1(OP_1)、運算符(OPER)、輸入運算數2(OP_2)、輸出結果(RESULT)、結果錯誤(ERROR)。
下圖為本模塊的狀態跳轉圖:
復位后,狀態機進入OP_1狀態,即初始狀態為OP_1;
在OP_1狀態下:
A.按下等號,跳到RESULT狀態;
B.按下運算符,跳到OPER狀態;
在OPER狀態下:
A.按下數字,跳到OP_2狀態;
B.按下等號,跳到RESULT狀態;
在OP_2狀態下:
A.按下等號,跳到RESULT狀態;
B.按下運算符,跳到OPER狀態;
在RESULT狀態下:
A.按下數字,跳到OP_1狀態;
B.按下運算符,跳到OPER狀態;
C.按下等號,停留在RESULT狀態;
在ERROR狀態下:
A.按下數字,跳到OP_1狀態;
B.按下其他按鍵,停留在ERROR狀態;
無論當前處于什么狀態,只要檢測到運算結果錯誤指示信號有效,即刻跳轉到ERROR狀態。
1.3.3 參考代碼
使用GVIM,在命令模式下輸入如下內容,即可生成本模塊所需要的狀態機代碼。
使用明德揚的狀態機模板,可以很快速的寫出此模塊代碼。
165.always @(*)begin 166. case(key_num) 167. 4'd0 :key_num_chg = 4'd7 ; 168. 4'd1 :key_num_chg = 4'd8 ; 169. 4'd2 :key_num_chg = 4'd9 ; 170. 4'd3 :key_num_chg = 4'd10 ; 171. 4'd7 :key_num_chg = 4'd11 ; 172. 4'd8 :key_num_chg = 4'd1 ; 173. 4'd9 :key_num_chg = 4'd2 ; 174. 4'd10 :key_num_chg = 4'd3 ; 175. 4'd11 :key_num_chg = 4'd14 ; 176. 4'd12 :key_num_chg = 4'd0 ; 177. 4'd13 :key_num_chg = 4'd12 ; 178. 4'd14 :key_num_chg = 4'd13 ; 179. default:key_num_chg = key_num; 180. endcase 181.end 182. 183.assign key_num_en = (key_num_chg==0 || key_num_chg==1 || key_num_chg==2 || key_num_chg==3 || key_num_chg==4 || key_num_chg==5 || key_num_chg==6 || key_num_chg==7 || key_num_chg==8 || key_num_chg==9) && key_vld==1; 184.assign key_op_en = (key_num_chg==10 || key_num_chg==11 || key_num_chg==12 || key_num_chg==13) && key_vld==1; 185.assign key_cal_en = key_num_chg==15 && key_vld==1; 186.assign key_back_en = key_num_chg==14 && key_vld==1; 187. 188. 189. 190.always @(posedge clk or negedge rst_n) begin 191. if (rst_n==0) begin 192. state_c <= OP_1 ; 193. end 194. else begin 195. state_c <= state_n; 196. end 197.end 198. 199.always @(*) begin 200. if(result_err)begin 201. state_n = ERROR; 202. end 203. else begin 204. case(state_c) 205. OP_1 :begin 206. if(op_12oper_start) 207. state_n = OPER ; 208. else if(op_12result_start) 209. state_n = RESULT ; 210. else 211. state_n = state_c ; 212. end 213. OPER :begin 214. if(oper2op_2_start) 215. state_n = OP_2 ; 216. else if(oper2result_start) 217. state_n = RESULT ; 218. else 219. state_n = state_c ; 220. end 221. OP_2 :begin 222. if(op_22oper_start) 223. state_n = OPER ; 224. else if(op_22result_start) 225. state_n = RESULT ; 226. else 227. state_n = state_c ; 228. end 229. RESULT :begin 230. if(result2op_1_start) 231. state_n = OP_1 ; 232. else if(result2oper_start) 233. state_n = OPER ; 234. else 235. state_n = state_c ; 236. end 237. ERROR :begin 238. if(error2op_1_start) 239. state_n = OP_1 ; 240. else 241. state_n = state_c ; 242. end 243. default : state_n = OP_1 ; 244. endcase 245.end 246.end 247. 248.assign op_12oper_start = state_c==OP_1 && key_op_en ; 249.assign op_12result_start = state_c==OP_1 && key_cal_en; 250.assign oper2op_2_start = state_c==OPER && key_num_en; 251.assign oper2result_start = state_c==OPER && key_cal_en; 252.assign op_22oper_start = state_c==OP_2 && key_op_en ; 253.assign op_22result_start = state_c==OP_2 && key_cal_en; 254.assign result2op_1_start = state_c==RESULT && key_num_en; 255.assign result2oper_start = state_c==RESULT && key_op_en ; 256.assign error2op_1_start = state_c==ERROR && key_num_en; 257. 258.always @(posedge clk or negedge rst_n)begin 259. if(rst_n==1'b0)begin 260. key_num_out <= 0; 261. end 262. else begin 263. key_num_out <= key_num_chg; 264. end 265.end 266. 267.always @(posedge clk or negedge rst_n)begin 268. if(rst_n==1'b0)begin 269. key_vld_out <= 0; 270. end 271. else begin 272. key_vld_out <= key_vld; 273. end 274.end1.4 運算數1模塊設計
1.4.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| Key_num_out | 輸入 | 計算器按下位置輸出信號,key_vld_out有效時,該信號有效。 |
| Key_vld_out | 輸入 | 計算器按鍵按下有效指示信號,高電平有效。 |
| State_c | 輸入 | 計算器工作狀態指示信號 |
| Op_1 | 輸出 | 運算數1輸出信號 |
| Result | 輸入 | 運算結果輸出信號 |
| Op_1_err | 輸出 | 運算數1溢出信號 |
1.4.2 設計思路
該模塊主要的作用是根據當前狀態和輸入的按鍵,來決定運算數1要輸出的結果。由于本工程需要實現連續運算的功能,所以在這個模塊中要區分是否已經得出了運算結果。
下面是計算完成指示信號flag_calc的設計思路:
1、該信號為高時,表示完成一次計算過程得到了結果。初始狀態為低電平;
2、當輸入操作數2后又按下了等號或者其他操作符的時候,變為高電平,所以變高的條件為(state_c_ffOP_2 && state_cOPER) || state_cRESULT;
3、當處在操作數1狀態時,為低電平,所以變低的條件為state_cOP_1。其他情況保持不變。
下面是運算數1輸出信號op_1的設計思路:
1、該信號表示運算數1要輸出的值。初始狀態為0;
2、在結果錯誤狀態的時候,給一個不超過范圍的任意值,此代碼中給的10;
3、在得到計算結果或者計算結果錯誤的時候,輸入數字,輸出為按下按鍵的對應值(key_num_out);
4、在輸入操作數1之后,按下退格鍵,op_1輸出的值除以10進行取整;
5、在輸入操作數1狀態下通過鍵盤輸入數字,需要判斷是否超過顯示范圍,如果沒有超過的話就需要將當前op_1的值乘以10,然后加上按下的數字的值,進行輸出;
6、當計算完成時,即flag_calc==1,操作數1輸出計算的結果result;
7、其他時侯操作數1保持不變。
下面是運算數1溢出信號op_1_err的設計思路:
1、初始狀態為0,表示沒有溢出。
2、當一直處于操作數1狀態,按下鍵盤輸入數字之后,操作數1的值溢出了,則將運算數1溢出信號拉高。
3、其他時刻保持為低電平。
1.4.3 參考代碼
275.assign key_num_en = (key_num_out==0 || key_num_out==1 || key_num_out==2 || key_num_out==3 || key_num_out==4 || key_num_out==5 || key_num_out==6 || key_num_out==7 || key_num_out==8 || key_num_out==9) && key_vld_out==1; 276.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 277.assign key_cal_en = key_num_out==15 && key_vld_out==1; 278.assign key_back_en = key_num_out==14 && key_vld_out==1; 279. 280.always @(posedge clk or negedge rst_n)begin 281. if(rst_n==1'b0)begin 282. state_c_ff <= 0; 283. end 284. else begin 285. state_c_ff <= state_c; 286. end 287.end 288. 289.always @(posedge clk or negedge rst_n)begin 290. if(rst_n==1'b0)begin 291. flag_calc <= 0; 292. end 293. else if(state_c==OP_1)begin 294. flag_calc <= 1'b0; 295. end 296. else if(state_c_ff==OP_2 && state_c==OPER || state_c==RESULT)begin 297. flag_calc <= 1'b1; 298. end 299. else begin 300. flag_calc <= flag_calc; 301. end 302.end 303. 304.always @(posedge clk or negedge rst_n)begin 305. if(rst_n==1'b0)begin 306. op_1 <= 0; 307. end 308. else if(state_c==ERROR)begin 309. op_1 <= 10; 310. end 311. else if((state_c_ff==RESULT || state_c_ff==ERROR) && state_c==OP_1)begin 312. op_1 <= key_num_out; 313. end 314. else if(state_c==OP_1 && key_back_en==1)begin 315. op_1 <= op_1 / 10; 316. end 317. else if(state_c==OP_1 && key_num_en==1)begin 318. op_1 <= (op_1>9999999) ? op_1 : (op_1*10+key_num_out); 319. end 320. else if(flag_calc==1)begin 321. op_1 <= result; 322. end 323. else begin 324. op_1 <= op_1; 325. end 326.end 327. 328.always @(posedge clk or negedge rst_n)begin 329. if(rst_n==1'b0)begin 330. op_1_err <= 0; 331. end 332. else if(state_c==OP_1 && state_c_ff==OP_1 && key_num_en==1 && op_1>9999999)begin 333. op_1_err <= 1'b1; 334. end 335. else begin 336. op_1_err <= 1'b0; 337. end 338.end1.5 運算符模塊設計
1.5.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| Key_num_out | 輸入 | 計算器按下位置輸出信號,key_vld_out有效時,該信號有效。 |
| Key_vld_out | 輸入 | 計算器按鍵按下有效指示信號,高電平有效。 |
| State_c | 輸入 | 計算器工作狀態指示信號 |
| oper | 輸出 | 運算符輸出信號 |
1.5.2 設計思路
本模塊的設計思路比較簡單,只需要判斷哪些按鍵是運算符,然后再這些運算符被按下的時候,將他們對應的值輸出就可以了。
下面是運算符指示信號設計思路:
1、當“加”“減”“乘”“除”四個按鍵的任意一個被按下之后,該信號置為高電平;
2、當“加”“減”“乘”“除”四個按鍵沒有一個被按下的時候,該信號置為低電平。
下面是運算符輸出信號oper設計思路:
初始狀態,該信號輸出0;
1、當處于操作數1狀態時,輸出0;
2、當“加”“減”“乘”“除”任意按鍵被按下之后,輸出該按鍵對應的值;
3、其他時候保持不變;
1.5.3 參考代碼
339.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 340. 341.always @(posedge clk or negedge rst_n)begin 342. if(rst_n==1'b0)begin 343. oper <= 0; 344. end 345. else if(state_c==OP_1)begin 346. oper <= 0; 347. end 348. else if(key_op_en==1)begin 349. oper <= key_num_out; 350. end 351. else begin 352. oper <= oper; 353. end 354.End1.6 運算數2模塊設計
1.6.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| Key_num_out | 輸入 | 計算器按下位置輸出信號,key_vld_out有效時,該信號有效。 |
| Key_vld_out | 輸入 | 計算器按鍵按下有效指示信號,高電平有效。 |
| State_c | 輸入 | 計算器工作狀態指示信號 |
| Op_2 | 輸出 | 運算數2輸出信號 |
| Op_2_err | 輸出 | 運算數2溢出信號 |
1.6.2 設計思路
該模塊主要的作用是根據當前狀態和輸入的按鍵,來決定運算數2要輸出的結果。
下面是運算數2輸出信號op_2的設計思路:
1、該信號表示運算數2要輸出的值。初始狀態為0;
2、在運算符狀態下,此時數碼管不顯示運算數2的值,讓它輸出0;
3、輸入運算符之后,之后再輸入的就是運算數2的值,此時運算數2就等于按下按鍵所對應的數值。
4、在輸入運算數2之后,按下退格鍵,運算數2的值除以10進行取整;
5、在輸入運算數2狀態下通過鍵盤輸入數字,需要判斷是否超過顯示范圍,如果沒有超過的話就需要將
當前運算數2的值乘以10,然后加上按下的數字的值,進行輸出;
6、其他時侯運算數2保持不變。
下面是運算數2溢出信號op_2_err的設計思路:
1、初始狀態為0,表示沒有溢出。
2、當一直處于運算數2狀態,按下鍵盤輸入數字之后,運算數2的值溢出了,則將運算數2溢出信號拉高。
3、其他時刻保持為低電平。
1.6.3 參考代碼
1.assign key_num_en = (key_num_out==0 || key_num_out==1 || key_num_out==2 || key_num_out==3 || key_num_out==4 || key_num_out==5 || key_num_out==6 || key_num_out==7 || key_num_out==8 || key_num_out==9) && key_vld_out==1; 2.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 3.assign key_cal_en = key_num_out==15 && key_vld_out==1; 4.assign key_back_en = key_num_out==14 && key_vld_out==1; 5. 6.always @(posedge clk or negedge rst_n)begin 7. if(rst_n==1'b0)begin 8. state_c_ff <= 0; 9. end 10. else begin 11. state_c_ff <= state_c; 12. end 13.end 14. 15.always @(posedge clk or negedge rst_n)begin 16. if(rst_n==1'b0)begin 17. op_2 <= 0; 18. end 19. else if(state_c==OPER)begin 20. op_2 <= 0; 21. end 22. else if(state_c_ff==OPER && state_c==OP_2)begin 23. op_2 <= key_num_out; 24. end 25. else if(state_c==OP_2 && key_back_en==1)begin 26. op_2 <= op_2 / 10; 27. end 28. else if(state_c==OP_2 && key_num_en==1)begin 29. op_2 <= (op_2>9999999) ? op_2 : (op_2*10+key_num_out); 30. end 31. else begin 32. op_2 <= op_2; 33. end 34.end 35. 36.always @(posedge clk or negedge rst_n)begin 37. if(rst_n==1'b0)begin 38. op_2_err <= 0; 39. end 40. else if(state_c==OP_2 && key_num_en==1 && op_2>9999999)begin 41. op_2_err <= 1'b1; 42. end 43. else begin 44. op_2_err <= 1'b0; 45. end 46.end1.7 運算單元模塊設計
1.7.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| Key_num_out | 輸入 | 計算器按下位置輸出信號,key_vld_out有效時,該信號有效。 |
| Key_vld_out | 輸入 | 計算器按鍵按下有效指示信號,高電平有效。 |
| State_c | 輸入 | 計算器工作狀態指示信號 |
| oper | 輸出 | 運算符輸出信號 |
| Op_1 | 輸入 | 運算數1輸入信號 |
| Op_2 | 輸入 | 運算數2輸入信號 |
| Result | 輸出 | 運算結果輸出信號 |
| Result_err | 輸出 | 運算結果錯誤信號,運算結果溢出或者除數為0時,該信號輸出一個時鐘周期的高電平 |
| Result_neg | 輸出 | 運算結果符號位指示信號,當運算結果為負數時,該信號為高電平 |
1.7.2 設計思路
本模塊的作用是根據運算符,對運算數1和運算數2進行操作得出結果。
由于再進行計算的時候考慮小數減去大數的情況,所以運算結果允許為負數,因此需要有符號位指示信號,下面是運算結果符號位指示信號result_neg的設計思路:
1、只有當運算結果為負數的時候,才顯示“負號”,因此初始狀態為低電平;
2、當算式輸入完成按下等號之后,如果運算符是“減”,并且運算數1小于運算數2,則運算結果為負數,將result_neg信號拉高。
3、由于該計算器支持連續輸入,如果當前計算的結果為負數,接著輸入的運算符為“加”,下一次進行加法運算,并且運算數1(此時比較不考慮符號位)小于或等于運算數2,則表示運算結果為正數,此時將result_neg信號拉低。
4、在進行連續計算的時候,如果得到的結果超過顯示上限,要進入錯誤狀態,這個時候符號位指示信號應該為低電平。
5、無論在計算中得到的結果是正還是負,如果下次輸入的為運算數1,都需要result_neg信號為低電平。
6、由于除法不支持小數顯示,只取整數部分,所以當運算結果為負數,并進行除法運算的時候,如果得到的結果為0,不應該顯示為“負0”,應當將符號位指示信號置為低電平。
本模塊主要的功能是實現加減乘除運算,下面是對運算結果輸出信號result的設計思路:
1、初始狀態沒有經過計算,自然輸出為0。
2、在進行加法的時候,由于存在連續計算的情況,需要考慮符號位。當符號位指示信號為0,直接將運算數1和運算數2相加即可;當符號位指示信號為1,則需要判斷運算數1和運算數2的大小,確保是大的減去小的。
3、在進行減法的時候,同樣需要考慮符號位。當符號位指示信號為0的時候,需要判斷運算數1和運算數2的大小,保證大的減去小的;當符號位指示信號位1的時候,直接將運算數1和運算數2相加即可。
4、乘法運算直接將運算數1和運算數2相乘即可。
5、在進行除法運算時,由于無法表示小數,因此這里需要采用運算數1除以運算數2取整的方法,即op_1/op_2。
在計算過程中,如果得到的結果超過顯示上限或者錯誤的計算方法,需要做出錯誤提示,下面是對于運算結果錯誤信號result_err的設計思路:
1、初始狀態下,該信號為0,表示沒有錯誤。
2、得到運算結果后,若繼續輸入數字,則會進入到運算數1狀態,這個時候不進行錯誤提示。
3、在運算數2狀態下輸入運算符,或者在結果不是錯誤的狀態下輸出“等號”(表示進行連續計算)。根據輸入的運算符進行相應的判斷:
加:如果運算結果為正數,則判斷運算數1加上運算數2之后會不會溢出,若溢出則做出錯誤提示;如果運算結果為負數,則不進行錯誤提示。
減:如果運算結果為負數,則判斷運算數1加上運算數2之后會不會溢出,若溢出則做出錯誤提示;如果運算結果為正數,則判斷兩個數相減之后的結果是否會溢出。
乘:無論運算結果為何值,都只需要判斷兩數相乘之后的的結果會不會溢出就可以了。
除:在進行除法運算的時候,需要避免出現除數為0的情況,如果出現此情況,則進行錯誤指示。
1.7.3 參考代碼
1.assign key_op_en = (key_num_out==10 || key_num_out==11 || key_num_out==12 || key_num_out==13) && key_vld_out==1; 2.assign key_cal_en = key_num_out==15 && key_vld_out==1; 3.assign calculate = (state_c_ff==OP_2 && state_c==OPER || key_cal_en==1); 4. 5.always @(posedge clk or negedge rst_n)begin 6. if(rst_n==1'b0)begin 7. state_c_ff <= 0; 8. end 9. else begin 10. state_c_ff <= state_c; 11. end 12.end 13. 14.always @(posedge clk or negedge rst_n)begin 15. if(rst_n==1'b0)begin 16. result <= 0; 17. end 18. else if(calculate==1)begin 19. case(oper) 20. ADD:begin 21. if(result_neg==0) 22. result <= op_1 + op_2; 23. else 24. result <= (op_1>op_2) ? (op_1 - op_2) : (op_2 - op_1); 25. end 26. DEV:begin 27. if(result_neg==0) 28. result <= (op_1>op_2) ? (op_1 - op_2) : (op_2 - op_1); 29. else 30. result <= op_1 + op_2; 31. end 32. MUL:begin 33. result <= op_1 * op_2; 34. end 35. DIV:begin 36. result <= op_1 / op_2; 37. end 38. default:result <= op_1; 39. endcase 40. end 41. else begin 42. result <= result; 43. end 44.end 45. 46.always @(posedge clk or negedge rst_n)begin 47. if(rst_n==1'b0)begin 48. result_neg <= 0; 49. end 50. else if(state_c==OP_1)begin 51. result_neg <= 1'b0; 52. end 53. else if(state_c_ff==ERROR)begin 54. result_neg <= 1'b0; 55. end 56. else if(calculate==1 && oper==DEV && op_1<op_2)begin 57. result_neg <= 1'b1; 58. end 59. else if(calculate==1 && result_neg==1 && oper==ADD && op_1<=op_2)begin 60. result_neg <= 1'b0; 61. end 62. else if(result==0)begin 63. result_neg <= 1'b0; 64. end 65. else begin 66. result_neg <= result_neg; 67. end 68.end 69. 70.always @(posedge clk or negedge rst_n)begin 71. if(rst_n==1'b0)begin 72. result_err <= 0; 73. end 74. else if(state_c==OP_1)begin 75. result_err <= 1'b0; 76. end 77. else if((state_c_ff==OP_2 && state_c==OPER) || (key_cal_en==1 && state_c_ff!=ERROR))begin 78. case(oper) 79. ADD:begin 80. if(result_neg==0) 81. result_err <= (op_1+op_2)>9999_9999 ? 1'b1 : 1'b0; 82. else 83. result_err <= 1'b0; 84. end 85. DEV:begin 86. if(result_neg==1) 87. result_err <= (op_1+op_2)>999_9999 ? 1'b1 : 1'b0; 88. else if(op_2>op_1) 89. result_err <= (op_2-op_1)>999_9999 ? 1'b1 : 1'b0; 90. else 91. result_err <= 1'b0; 92. end 93. MUL:begin 94. if(result_neg==1) 95. result_err <= (op_1*op_2)>999_9999 ? 1'b1 : 1'b0; 96. else 97. result_err <= (op_1*op_2)>9999_9999 ? 1'b1 : 1'b0; 98. end 99. DIV:begin 100. if(op_2==0) 101. result_err <= 1'b1; 102. else 103. result_err <= 1'b0; 104. end 105. default:result_err <= 1'b0; 106. endcase 107. end 108. else begin 109. result_err <= 1'b0; 110. end 111.end1.8 顯示對象選擇模塊設計
1.8.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| Op_1 | 輸入 | 運算數1輸入信號 |
| Op_2 | 輸入 | 運算數2輸入信號 |
| State_c | 輸入 | 計算器工作狀態指示信號 |
| Result_neg | 輸入 | 運算結果符號位指示信號 |
| Disply | 輸出 | 顯示數據輸出信號 |
| Display_vld | 輸出 | 顯示數據有效指示信號 |
1.8.2 設計思路
該模塊的作用是根據當前計算器的工作狀態來選擇數碼管的顯示內容。
1、復位后,該模塊輸出0;
2、當計算器處于OP_1狀態下,該模塊選擇輸出運算數1。
3、當計算器處于OPER狀態下,該模塊選擇輸出運算數1。
4、當計算器處于OP_2狀態下,該模塊選擇輸出運算數2。
5、當計算器處于RESULT狀態下,該模塊選擇輸出運算數1。
6、當計算器處于ERROR狀態下,該模塊選擇輸出8個F。
要將數據送到數碼管顯示,需要將收到的數據進行拆分,比如輸入進來的是“12”,需要拆成一個4bit的“1”和一個4bit的“2”送給數碼管顯示模塊。因此設計一個計數器的架構,如下圖所示:
架構中使用到了一個時鐘計數器dis_cnt、一個采集狀態指示信號flag_add、dis_sel為輸入要顯示的數據、dis_sel_tmp為輸入數據打一拍之后的數據、result_neg為運算結果符號位指示信號、result_neg_tmp為運算結果符號位指示信號打一拍之后的信號。下面分別介紹一下這些信號的設計思路:
采集狀態指示信號flag_add:初始狀態為0,表示不對數據進行采集顯示。如果檢測到輸入的數據或者符號位發生變化,表示要在數碼管上顯示的數據有變化,該信號拉高,計數器可以進行計數,所以由0變1的條件為dis_sel!=dis_sel_tmp || result_neg!=result_neg_tmp。當計數器數完之后,表示要顯示的數據已經全部顯示,則將此信號拉低,所以由1變0的條件是end_dis_cnt。
顯示數據dis_sel:該信號根據工作狀態進行選擇,當目前處于OP_2狀態時,選擇運算數2輸入數據,其他情況都選擇運算數1輸入數據。
顯示數據打一拍之后的信號dis_sel_tmp:該信號存在的目的就是為了檢測顯示數據是否發生變化。
運算結果符號位指示信號result_neg:輸入信號。
符號位指示信號打一拍之后的信號result_neg_tmp:該信號存在的意義就是為了檢測符號位是否發生變化。
時鐘計數器dis_cnt:該計數器的作用有兩個,延時和控制輸入數據賦值給顯示數據輸出信號的對應位。加一條件為flag_add && (dis_seldis_sel_tmp && result_negresult_neg_tmp),表示處在采集狀態時,如果顯示數據和符號位指示信號穩定,則開始計數。結束條件為數10個,由于計數器剛開始計數的時候,顯示數據存在變化的可能,因此這里選擇延遲兩個時鐘在對顯示數據輸出信號進行賦值(由于后面數據都是保持不變的,因此這個延時時間不是固定的,可以多延時一些),共有8個數碼管,因此要賦值8次,所以計數器共需要數10個。
前面提到過,需要將顯示數據顯示到數碼管上的話,需要將每一個數字進行拆分,一般采用除以10取余和取整的方法,本工程使用除法器的IP核,該IP核的作用就是將輸入的數據除以10,得到商和余數。生成過程如下:
第一步、使用軟件為Quartus Prime Lite Edition 18.1版本。首先打開軟件之后,在主頁面的右邊找到“IP Catalog”窗口,在搜索欄中輸入“div”進行搜索,然后雙擊“LPM_DIVIDE”。如果沒有找到“IP Catalog”窗口,可在上方工具欄“Tools”中選擇“IP Catalog”調出。
第二步、選擇IP核生成的路徑,并將其命名為“div”,注意這里的名字不能有中文字符或者全數字。在下方文件類型中選擇“Verilog”,然后點擊OK。
第三步、在之后出現的IP核設置界面中,“How wide should the numerator input”表示需要設置的分子的位寬,這里設置為27。“How wide should the denominator input”表示需要設置的分母的位寬這里設置為4。在下方分子和分母的表示都選用“Unsigned”無符號類型。然后點擊Next
第四步、下圖中的1處表示是否需要對輸出進行打拍,這里選擇打一拍之后輸出。2處表示要進行的優化,這里選擇默認優化。3處表示是否總是返回正余數,選擇是。然后點擊Next。
第五步、方框出表示該IP核在仿真的時候需要調用的庫,直接點擊Next即可。
第六步、這一界面是設置需要生成的文件,本工程只需要生成默認的即可,所以不用勾選。點擊Finish。
1.8.3 參考代碼
112.always @(posedge clk or negedge rst_n)begin 113. if(rst_n==1'b0)begin 114. result_neg_tmp <= 0; 115. end 116. else begin 117. result_neg_tmp <= result_neg; 118. end 119.end 120. 121.always @(*)begin 122. if(state_c==OP_2)begin 123. dis_sel = op_2; 124. end 125. else begin 126. dis_sel = op_1; 127. end 128.end 129. 130.always @(posedge clk or negedge rst_n)begin 131. if(rst_n==1'b0)begin 132. dis_sel_tmp <= 0; 133. end 134. else begin 135. dis_sel_tmp <= dis_sel; 136. end 137.end 138. 139. 140.div div_prj( 141. .clock (clk ) , 142. .numer (dis_tmp ) , 143. .denom (10 ) , 144. .quotient (div_quo ) , 145. .remain (div_rem ) 146. ); 147. 148.always @(posedge clk or negedge rst_n)begin 149. if(rst_n==1'b0)begin 150. flag_add <= 0; 151. end 152. else if(dis_sel!=dis_sel_tmp || result_neg!=rssult_neg_tmp)begin 153. flag_add <= 1; 154. end 155. else if(end_dis_cnt)begin 156. flag_add <= 0; 157. end 158.end 159. 160. 161.always @(posedge clk or negedge rst_n) begin 162. if (rst_n==0) begin 163. dis_cnt <= 0; 164. end 165. else if(add_dis_cnt) begin 166. if(end_dis_cnt) 167. dis_cnt <= 0; 168. else 169. dis_cnt <= dis_cnt+1 ; 170. end 171.end 172.assign add_dis_cnt = flag_add && (dis_sel==dis_sel_tmp && result_neg==result_neg_tmp); 173.assign end_dis_cnt = add_dis_cnt && dis_cnt == 10-1 ; 174. 175. 176.assign dis_tmp = add_dis_cnt && dis_cnt==1 ? dis_sel : div_quo; 177. 178.always @(posedge clk or negedge rst_n)begin 179. if(rst_n==1'b0)begin 180. display <= 4'b0; 181. end 182. else if(state_c==ERROR)begin 183. display[4*(dis_cnt)-1 -:4] <= 4'b1111; 184. end 185. else if(end_dis_cnt && result_neg==1 && state_c!=OP_2)begin 186. display[31:28] <= 4'b1010; 187. end 188. else begin 189. display[4*(dis_cnt-1)-1 -:4] <= div_rem; 190. end 191.end 192. 193. 194.always @(posedge clk or negedge rst_n)begin 195. if(rst_n==1'b0)begin 196. display_vld <= 0; 197. end 198. else begin 199. display_vld <= (dis_cnt==0 && (dis_sel==dis_sel_tmp)) ? 1'b1 : 1'b0; 200. end 201.end1.9 數碼管顯示模塊設計
1.9.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| Display | 輸入 | 顯示數據輸入信號 |
| Display_vld | 輸入 | 顯示數據有效指示信號 |
| Seg_sel | 輸出 | 數碼管位選信號 |
| Segment | 輸出 | 數碼管段選信號 |
1.9.2 設計思路
本模塊主要實現的功能是對顯示對象選擇模塊的顯示數據輸出信號(display)進行數碼管顯示。
1、復位后,數碼管默認顯示運算數1;
2、當result_err有效時,數碼管顯示8個F;
3、當result_neg有效時,第8個數碼管顯示“—”;
4、數碼管顯示display;
由于數碼管顯示在前面已有案例介紹,所以這個就不做介紹。感興趣的同學可以看一下往期的文章:【每周FPGA案例】至簡設計系列_7段數碼管顯示
1.9.3 參考代碼
1.always @(posedge clk or negedge rst_n) begin 2. if (rst_n==0) begin 3. count_20us <= 0; 4. end 5. else if(add_count_20us) begin 6. if(end_count_20us) 7. count_20us <= 0; 8. else 9. count_20us <= count_20us+1 ; 10. end 11.end 12.assign add_count_20us = 1; 13.assign end_count_20us = add_count_20us && count_20us == TIME_20US-1 ; 14. 15. 16.always @(posedge clk or negedge rst_n) begin 17. if (rst_n==0) begin 18. sel_cnt <= 0; 19. end 20. else if(add_sel_cnt) begin 21. if(end_sel_cnt) 22. sel_cnt <= 0; 23. else 24. sel_cnt <= sel_cnt+1 ; 25. end 26.end 27.assign add_sel_cnt = end_count_20us; 28.assign end_sel_cnt = add_sel_cnt && sel_cnt == SEG_NUM-1 ; 29. 30. 31. 32.always @(posedge clk or negedge rst_n)begin 33. if(rst_n==1'b0)begin 34. seg_sel <= {SEG_NUM{1'b1}}; 35. end 36. else begin 37. seg_sel <= ~(1'b1 << sel_cnt); 38. end 39.end 40. 41.always @(posedge clk or negedge rst_n)begin 42. if(rst_n==1'b0)begin 43. display_ff0 <= 0; 44. end 45. else begin 46. for(ii=0;ii<SEG_NUM;ii=ii+1)begin 47. if(display_vld==1)begin 48. display_ff0[(ii+1)*4-1 -:4] <= display[(ii+1)*4-1 -:4]; 49. end 50. else begin 51. display_ff0[(ii+1)*4-1 -:4] <= display_ff0[(ii+1)*4-1 -:4]; 52. end 53. end 54. end 55.end 56. 57.always @(*)begin 58. seg_tmp = display_ff0[(sel_cnt+1)*4-1 -:4]; 59.end 60. 61. 62.always @(posedge clk or negedge rst_n)begin 63. if(rst_n==1'b0)begin 64. segment <= NUM_0; 65. end 66. else begin 67. case(seg_tmp) 68. 0 :segment <=NUM_0 ; 69. 1 :segment <=NUM_1 ; 70. 2 :segment <=NUM_2 ; 71. 3 :segment <=NUM_3 ; 72. 4 :segment <=NUM_4 ; 73. 5 :segment <=NUM_5 ; 74. 6 :segment <=NUM_6 ; 75. 7 :segment <=NUM_7 ; 76. 8 :segment <=NUM_8 ; 77. 9 :segment <=NUM_9 ; 78. 10:segment <=NUM_10 ; 79. default:segment <= NUM_ERR; 80. endcase 81. end 82.end1.10 蜂鳴器模塊設計
1.10.1 接口信號
| clk | 輸入 | 系統時鐘 |
| rst_n | 輸入 | 低電平復位信號 |
| Op_1_err | 輸入 | 運算數1溢出信號,高電平有效 |
| Op_2_err | 輸入 | 運算數2溢出信號,高電平有效 |
| Result_err | 輸入 | 運算結果錯誤信號,高電平有效 |
| Beep | 輸出 | 蜂鳴輸出信號,高電平有效 |
1.10.2 設計思路
該模塊的主要功能是根據接收到的各個錯誤指示信號,進行報警提示。當接收到錯誤信號有效的時候,蜂鳴器報警,持續1秒的時間,因此提出一個計數器的架構,如下圖所示:
主要由時鐘計數器cnt_1s和蜂鳴器輸出組成,下面時兩個信號的設計思路:
時鐘計數器cnt_1s:該計數器的作用是計時1秒的時間。加一條件為flag_add,表示進入報警狀態的時候便開始計數。結束條件為數5000_0000個,系統時鐘為50M,一個時鐘周期為20ns,5000_0000個時鐘周期就是1秒。
蜂鳴器輸出信號beep:初始狀態為1,表示不報警。從1變0的條件為op_1_err || op_2_err || result_err,表示接收到這些錯誤指示信號之后,開始報警。從0變1的條件為end_cnt_1s,表示報警時間持續1秒,之后結束。
1.10.3 參考代碼
1.always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1'b0)begin 3. flag_add <= 0; 4. end 5. else if(op_1_err || op_2_err || result_err)begin 6. flag_add <= 1; 7. end 8. else if(end_cnt_1s)begin 9. flag_add <= 0; 10. end 11.end 12. 13. 14.always @(posedge clk or negedge rst_n) begin 15. if (rst_n==0) begin 16. cnt_1s <= 0; 17. end 18. else if(add_cnt_1s) begin 19. if(end_cnt_1s) 20. cnt_1s <= 0; 21. else 22. cnt_1s <= cnt_1s+1 ; 23. end 24.end 25.assign add_cnt_1s = flag_add; 26.assign end_cnt_1s = add_cnt_1s && cnt_1s == CNT_1S-1 ; 27. 28. 29.always @(posedge clk or negedge rst_n)begin 30. if(rst_n==1'b0)begin 31. beep <= 1'b1; 32. end 33. else if(flag_add)begin 34. beep <= 1'b0; 35. end 36. else begin 37. beep <= 1'b1; 38. end 39.end1.11 效果和總結
1.11.1 db603開發板
由于計算器的演示是一個動態的過程,所以從下面圖片中看不出具體實現的效果,想要看上板效果的話可以看一下工程上板的視頻。
1.11.2 ms980試驗箱
由于計算器的演示是一個動態的過程,所以從下面圖片中看不出具體實現的效果,想要看上板效果的話可以看一下工程上板的視頻。
感興趣的朋友也可以訪問明德揚論壇(http://www.fpgabbs.cn/)進行FPGA相關工程設計學習,也可以看一下我們往期的文章.
總結
以上是生活随笔為你收集整理的至简设计系列_简易计算器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 武忠祥每日一题知识点总结
- 下一篇: Gmail的小Bug