当前位置:首页 > 生肖 > 正文内容

共醉菊花杯是指代表什么生肖(至简设计系列_简易计算器)

2024-09-20 10:33:36生肖158

--作者:小黑同学

本文为明德扬原创及录用文章,转载请注明出处!

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.1 系统结构框图

系统结构框图如下所示:

1.1.1 模块功能

Ø 键盘扫描模块实现功能

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.2 顶层信号

1.1.1 参考代码

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. endmodule

1.1 键盘扫描模块设计

1.1.1 接口信号

1.1.1 设计思路

在前面的案例中已经有矩阵键盘的介绍,所以这里不在过多介绍

1.1.2 参考代码

1. always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1b0)begin 3. key_col_ff0 <= 4b1111; 4. key_col_ff1 <= 4b1111; 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!=4hf; 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==1b0)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==4hf; 78. 79. always @(posedge clk or negedge rst_n)begin 80. if(rst_n==1b0)begin 81. key_row <= 4b0; 82. end 83. else if(state_c==CHK_ROW)begin 84. key_row <= ~(1b1 << row_index); 85. end 86. else begin 87. key_row <= 4b0; 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==1b0)begin 127. key_col_get <= 0; 128. end 129. else if(state_c==CHK_COL && end_shake_cnt ) begin 130. if(key_col_ff1==4b1110) 131. key_col_get <= 0; 132. else if(key_col_ff1==4b1101) 133. key_col_get <= 1; 134. else if(key_col_ff1==4b1011) 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==1b0)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==1b0)begin 156. key_vld <= 1b0; 157. end 158. else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1b0)begin 159. key_vld <= 1b1; 160. end 161. else begin 162. key_vld <= 1b0; 163. end 164. end

1.2 工作状态选择模块设计

1.2.1 接口信号

1.1.1 设计思路

该模块的主要功能是根据按下的按键进行不同来判断和决定计算器的工作状态。一条等式可以写成:运算数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.1.2 参考代码

使用GVIM,在命令模式下输入如下内容,即可生成本模块所需要的状态机代码。

使用明德扬的状态机模板,可以很快速的写出此模块代码。

1. always @(*)begin 2. case(key_num) 3. 4d0 :key_num_chg = 4d7 ; 4. 4d1 :key_num_chg = 4d8 ; 5. 4d2 :key_num_chg = 4d9 ; 6. 4d3 :key_num_chg = 4d10 ; 7. 4d7 :key_num_chg = 4d11 ; 8. 4d8 :key_num_chg = 4d1 ; 9. 4d9 :key_num_chg = 4d2 ; 10. 4d10 :key_num_chg = 4d3 ; 11. 4d11 :key_num_chg = 4d14 ; 12. 4d12 :key_num_chg = 4d0 ; 13. 4d13 :key_num_chg = 4d12 ; 14. 4d14 :key_num_chg = 4d13 ; 15. default:key_num_chg = key_num; 16. endcase 17. end 18. 19. 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; 20. assign key_op_en = (key_num_chg==10 || key_num_chg==11 || key_num_chg==12 || key_num_chg==13) && key_vld==1; 21. assign key_cal_en = key_num_chg==15 && key_vld==1; 22. assign key_back_en = key_num_chg==14 && key_vld==1; 23. 24. 25. 26. always @(posedge clk or negedge rst_n) begin 27. if (rst_n==0) begin 28. state_c <= OP_1 ; 29. end 30. else begin 31. state_c <= state_n; 32. end 33. end 34. 35. always @(*) begin 36. if(result_err)begin 37. state_n = ERROR; 38. end 39. else begin 40. case(state_c) 41. OP_1 :begin 42. if(op_12oper_start) 43. state_n = OPER ; 44. else if(op_12result_start) 45. state_n = RESULT ; 46. else 47. state_n = state_c ; 48. end 49. OPER :begin 50. if(oper2op_2_start) 51. state_n = OP_2 ; 52. else if(oper2result_start) 53. state_n = RESULT ; 54. else 55. state_n = state_c ; 56. end 57. OP_2 :begin 58. if(op_22oper_start) 59. state_n = OPER ; 60. else if(op_22result_start) 61. state_n = RESULT ; 62. else 63. state_n = state_c ; 64. end 65. RESULT :begin 66. if(result2op_1_start) 67. state_n = OP_1 ; 68. else if(result2oper_start) 69. state_n = OPER ; 70. else 71. state_n = state_c ; 72. end 73. ERROR :begin 74. if(error2op_1_start) 75. state_n = OP_1 ; 76. else 77. state_n = state_c ; 78. end 79. default : state_n = OP_1 ; 80. endcase 81. end 82. end 83. 84. assign op_12oper_start = state_c==OP_1 && key_op_en ; 85. assign op_12result_start = state_c==OP_1 && key_cal_en; 86. assign oper2op_2_start = state_c==OPER && key_num_en; 87. assign oper2result_start = state_c==OPER && key_cal_en; 88. assign op_22oper_start = state_c==OP_2 && key_op_en ; 89. assign op_22result_start = state_c==OP_2 && key_cal_en; 90. assign result2op_1_start = state_c==RESULT && key_num_en; 91. assign result2oper_start = state_c==RESULT && key_op_en ; 92. assign error2op_1_start = state_c==ERROR && key_num_en; 93. 94. always @(posedge clk or negedge rst_n)begin 95. if(rst_n==1b0)begin 96. key_num_out <= 0; 97. end 98. else begin 99. key_num_out <= key_num_chg; 100. end 101. end 102. 103. always @(posedge clk or negedge rst_n)begin 104. if(rst_n==1b0)begin 105. key_vld_out <= 0; 106. end 107. else begin 108. key_vld_out <= key_vld; 109. end 110. end

1.2 运算数1模块设计

1.2.1 接口信号

1.1.1 设计思路

该模块主要的作用是根据当前状态和输入的按键,来决定运算数1要输出的结果。由于本工程需要实现连续运算的功能,所以在这个模块中要区分是否已经得出了运算结果。

下面是计算完成指示信号flag_calc的设计思路:

1、 该信号为高时,表示完成一次计算过程得到了结果。初始状态为低电平;

2、 当输入操作数2后又按下了等号或者其他操作符的时候,变为高电平,所以变高的条件为(state_c_ff==OP_2 && state_c==OPER) || state_c==RESULT;

3、 当处在操作数1状态时,为低电平,所以变低的条件为state_c==OP_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.1.2 参考代码

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==1b0)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==1b0)begin 17. flag_calc <= 0; 18. end 19. else if(state_c==OP_1)begin 20. flag_calc <= 1b0; 21. end 22. else if(state_c_ff==OP_2 && state_c==OPER || state_c==RESULT)begin 23. flag_calc <= 1b1; 24. end 25. else begin 26. flag_calc <= flag_calc; 27. end 28. end 29. 30. always @(posedge clk or negedge rst_n)begin 31. if(rst_n==1b0)begin 32. op_1 <= 0; 33. end 34. else if(state_c==ERROR)begin 35. op_1 <= 10; 36. end 37. else if((state_c_ff==RESULT || state_c_ff==ERROR) && state_c==OP_1)begin 38. op_1 <= key_num_out; 39. end 40. else if(state_c==OP_1 && key_back_en==1)begin 41. op_1 <= op_1 / 10; 42. end 43. else if(state_c==OP_1 && key_num_en==1)begin 44. op_1 <= (op_1>9999999) ? op_1 : (op_1*10+key_num_out); 45. end 46. else if(flag_calc==1)begin 47. op_1 <= result; 48. end 49. else begin 50. op_1 <= op_1; 51. end 52. end 53. 54. always @(posedge clk or negedge rst_n)begin 55. if(rst_n==1b0)begin 56. op_1_err <= 0; 57. end 58. else if(state_c==OP_1 && state_c_ff==OP_1 && key_num_en==1 && op_1>9999999)begin 59. op_1_err <= 1b1; 60. end 61. else begin 62. op_1_err <= 1b0; 63. end 64. end

1.2 运算符模块设计

1.2.1 接口信号

1.1.1 设计思路

本模块的设计思路比较简单,只需要判断哪些按键是运算符,然后再这些运算符被按下的时候,将他们对应的值输出就可以了。

下面是运算符指示信号设计思路:

1、 当“加”“减”“乘”“除”四个按键的任意一个被按下之后,该信号置为高电平;

2、 当“加”“减”“乘”“除”四个按键没有一个被按下的时候,该信号置为低电平。

下面是运算符输出信号oper设计思路:

初始状态,该信号输出0;

1、 当处于操作数1状态时,输出0;

2、 当“加”“减”“乘”“除”任意按键被按下之后,输出该按键对应的值;

3、 其他时候保持不变;

1.1.2 参考代码

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. 3. always @(posedge clk or negedge rst_n)begin 4. if(rst_n==1b0)begin 5. oper <= 0; 6. end 7. else if(state_c==OP_1)begin 8. oper <= 0; 9. end 10. else if(key_op_en==1)begin 11. oper <= key_num_out; 12. end 13. else begin 14. oper <= oper; 15. end 16. End

1.2 运算数2模块设计

1.2.1 接口信号

1.1.1 设计思路

该模块主要的作用是根据当前状态和输入的按键,来决定运算数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.1.2 参考代码

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==1b0)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==1b0)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==1b0)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 <= 1b1; 42. end 43. else begin 44. op_2_err <= 1b0; 45. end 46. end

1.2 运算单元模块设计

1.2.1 接口信号

1.1.1 设计思路

本模块的作用是根据运算符,对运算数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.1.2 参考代码

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==1b0)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==1b0)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==1b0)begin 48. result_neg <= 0; 49. end 50. else if(state_c==OP_1)begin 51. result_neg <= 1b0; 52. end 53. else if(state_c_ff==ERROR)begin 54. result_neg <= 1b0; 55. end 56. else if(calculate==1 && oper==DEV && op_1<op_2)begin 57. result_neg <= 1b1; 58. end 59. else if(calculate==1 && result_neg==1 && oper==ADD && op_1<=op_2)begin 60. result_neg <= 1b0; 61. end 62. else if(result==0)begin 63. result_neg <= 1b0; 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==1b0)begin 72. result_err <= 0; 73. end 74. else if(state_c==OP_1)begin 75. result_err <= 1b0; 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 ? 1b1 : 1b0; 82. else 83. result_err <= 1b0; 84. end 85. DEV:begin 86. if(result_neg==1) 87. result_err <= (op_1+op_2)>999_9999 ? 1b1 : 1b0; 88. else if(op_2>op_1) 89. result_err <= (op_2-op_1)>999_9999 ? 1b1 : 1b0; 90. else 91. result_err <= 1b0; 92. end 93. MUL:begin 94. if(result_neg==1) 95. result_err <= (op_1*op_2)>999_9999 ? 1b1 : 1b0; 96. else 97. result_err <= (op_1*op_2)>9999_9999 ? 1b1 : 1b0; 98. end 99. DIV:begin 100. if(op_2==0) 101. result_err <= 1b1; 102. else 103. result_err <= 1b0; 104. end 105. default:result_err <= 1b0; 106. endcase 107. end 108. else begin 109. result_err <= 1b0; 110. end 111. end

1.2 显示对象选择模块设计

1.2.1 接口信号

1.1.1 设计思路

该模块的作用是根据当前计算器的工作状态来选择数码管的显示内容。

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_sel==dis_sel_tmp && result_neg==result_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.1.1 参考代码

1. always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1b0)begin 3. result_neg_tmp <= 0; 4. end 5. else begin 6. result_neg_tmp <= result_neg; 7. end 8. end 9. 10. always @(*)begin 11. if(state_c==OP_2)begin 12. dis_sel = op_2; 13. end 14. else begin 15. dis_sel = op_1; 16. end 17. end 18. 19. always @(posedge clk or negedge rst_n)begin 20. if(rst_n==1b0)begin 21. dis_sel_tmp <= 0; 22. end 23. else begin 24. dis_sel_tmp <= dis_sel; 25. end 26. end 27. 28. 29. div div_prj( 30. .clock (clk ) , 31. .numer (dis_tmp ) , 32. .denom (10 ) , 33. .quotient (div_quo ) , 34. .remain (div_rem ) 35. ); 36. 37. always @(posedge clk or negedge rst_n)begin 38. if(rst_n==1b0)begin 39. flag_add <= 0; 40. end 41. else if(dis_sel!=dis_sel_tmp || result_neg!=rssult_neg_tmp)begin 42. flag_add <= 1; 43. end 44. else if(end_dis_cnt)begin 45. flag_add <= 0; 46. end 47. end 48. 49. 50. always @(posedge clk or negedge rst_n) begin 51. if (rst_n==0) begin 52. dis_cnt <= 0; 53. end 54. else if(add_dis_cnt) begin 55. if(end_dis_cnt) 56. dis_cnt <= 0; 57. else 58. dis_cnt <= dis_cnt+1 ; 59. end 60. end 61. assign add_dis_cnt = flag_add && (dis_sel==dis_sel_tmp && result_neg==result_neg_tmp); 62. assign end_dis_cnt = add_dis_cnt && dis_cnt == 10-1 ; 63. 64. 65. assign dis_tmp = add_dis_cnt && dis_cnt==1 ? dis_sel : div_quo; 66. 67. always @(posedge clk or negedge rst_n)begin 68. if(rst_n==1b0)begin 69. display <= 4b0; 70. end 71. else if(state_c==ERROR)begin 72. display[4*(dis_cnt)-1 -:4] <= 4b1111; 73. end 74. else if(end_dis_cnt && result_neg==1 && state_c!=OP_2)begin 75. display[31:28] <= 4b1010; 76. end 77. else begin 78. display[4*(dis_cnt-1)-1 -:4] <= div_rem; 79. end 80. end 81. 82. 83. always @(posedge clk or negedge rst_n)begin 84. if(rst_n==1b0)begin 85. display_vld <= 0; 86. end 87. else begin 88. display_vld <= (dis_cnt==0 && (dis_sel==dis_sel_tmp)) ? 1b1 : 1b0; 89. end 90. end

1.2 数码管显示模块设计

1.2.1 接口信号

1.1.1 设计思路

本模块主要实现的功能是对显示对象选择模块的显示数据输出信号(display)进行数码管显示。

1、 复位后,数码管默认显示运算数1;

2、 当result_err有效时,数码管显示8个F;

3、 当result_neg有效时,第8个数码管显示“—”;

4、 数码管显示display;

由于数码管显示在前面已有案例介绍,所以这个就不做介绍。

1.1.2 参考代码

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==1b0)begin 34. seg_sel <= {SEG_NUM{1b1}}; 35. end 36. else begin 37. seg_sel <= ~(1b1 << sel_cnt); 38. end 39. end 40. 41. always @(posedge clk or negedge rst_n)begin 42. if(rst_n==1b0)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==1b0)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. end

1.2 蜂鸣器模块设计

1.2.1 接口信号

1.1.1 设计思路

该模块的主要功能是根据接收到的各个错误指示信号,进行报警提示。当接收到错误信号有效的时候,蜂鸣器报警,持续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.1.2 参考代码

1. always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1b0)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==1b0)begin 31. beep <= 1b1; 32. end 33. else if(flag_add)begin 34. beep <= 1b0; 35. end 36. else begin 37. beep <= 1b1; 38. end 39. end

1.2 效果和总结

1.2.1 db603开发板

由于计算器的演示是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。

1.1.1 ms980试验箱

由于计算器的演示是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。