前言:
此时姐妹们对“php实现简易计算器”可能比较注重,看官们都需要分析一些“php实现简易计算器”的相关知识。那么小编同时在网摘上收集了一些对于“php实现简易计算器””的相关内容,希望姐妹们能喜欢,各位老铁们快快来了解一下吧!本文为明德扬原创及录用文章,转载请注明出处!
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顶层信号
1.1.6参考代码
module calc_project( clk , rst_n , key_col , key_row , seg_sel , segment , beep ); parameter KEY_WID = 4 ; parameter STATE_WID = 5 ; parameter NUM_WID = 27 ; parameter SEG_NUM = 8 ; parameter SEG_WID = 8 ; input clk ; input rst_n ; input [KEY_WID-1:0] key_col ; output [KEY_WID-1:0] key_row ; output [SEG_NUM-1:0] seg_sel ; output [SEG_WID-1:0] segment ; output beep ; wire [KEY_WID-1:0] key_num ; wire key_vld ; wire [KEY_WID-1:0] key_num_out ; wire [KEY_WID-1:0] key_vld_out ; wire [STATE_WID-1:0] state_c ; wire [NUM_WID-1:0] op_1 ; wire op_1_err ; wire [KEY_WID-1:0] oper ; wire [NUM_WID-1:0] op_2 ; wire op_2_err ; wire [NUM_WID-1:0] result ; wire result_err ; wire result_neg ; wire [SEG_NUM*4-1:0] display ; wire display_vld ; key_scan key_scan_prj( .clk (clk ) , .rst_n (rst_n ) , .key_col (key_col) , .key_row (key_row) , .key_out (key_num) , .key_vld (key_vld) ); work_state work_state_prj( .clk (clk ) , .rst_n (rst_n ) , .key_num (key_num ) , .key_vld (key_vld ) , .result_err (result_err ) , .key_num_out(key_num_out) , .key_vld_out(key_vld_out) , .state_c (state_c ) ); op_1 op_1_prj( .clk (clk ) , .rst_n (rst_n ) , .key_num (key_num_out) , .key_vld (key_vld_out) , .state_c (state_c ) , .result (result ) , .op_1 (op_1 ) , .op_1_err (op_1_err ) ); oper oper_prj( .clk (clk ) , .rst_n (rst_n ) , .key_num (key_num_out) , .key_vld (key_vld_out) , .state_c (state_c ) , .oper (oper ) ); op_2 op_2_prj( .clk (clk ) , .rst_n (rst_n ) , .key_num (key_num_out) , .key_vld (key_vld_out) , .state_c (state_c ) , .op_2 (op_2 ) , .op_2_err (op_2_err ) ); result result_prj( .clk (clk ) , .rst_n (rst_n ) , .key_num (key_num_out) , .key_vld (key_vld_out) , .state_c (state_c ) , .op_1 (op_1 ) , .oper (oper ) , .op_2 (op_2 ) , .result (result ) , .result_err (result_err ) , .result_neg (result_neg ) ); display_sel display_sel_prj( .clk (clk ) , .rst_n (rst_n ) , .state_c (state_c ) , .op_1 (op_1 ) , .op_2 (op_2 ) , .result_neg (result_neg ) , .display (display ) , .display_vld(display_vld) ); segment segment_prj( .rst_n (rst_n ) , .clk (clk ) , .display (display ) , .display_vld(display_vld) , .seg_sel (seg_sel ) , .segment (segment ) ); beep beep_prj( .clk (clk ) , .rst_n (rst_n ) , .op_1_err (op_1_err ) , .op_2_err (op_2_err ) , .result_err (result_err ) , .beep (beep ) );endmodule
1.2 键盘扫描模块设计
1.2.1接口信号
1.2.2 设计思路
在前面的案例中已经有矩阵键盘的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
1.2.3参考代码
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_col_ff0 <= 4'b1111; key_col_ff1 <= 4'b1111; end else begin key_col_ff0 <= key_col ; key_col_ff1 <= key_col_ff0; endendalways @(posedge clk or negedge rst_n) begin if (rst_n==0) begin shake_cnt <= 0; end else if(add_shake_cnt) begin if(end_shake_cnt) shake_cnt <= 0; else shake_cnt <= shake_cnt+1 ; endendassign add_shake_cnt = key_col_ff1!=4'hf;assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS-1 ;always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin state_c <= CHK_COL; end else begin state_c <= state_n; endendalways @(*)begin case(state_c) CHK_COL: begin if(col2row_start )begin state_n = CHK_ROW; end else begin state_n = CHK_COL; end end CHK_ROW: begin if(row2del_start)begin state_n = DELAY; end else begin state_n = CHK_ROW; end end DELAY : begin if(del2wait_start)begin state_n = WAIT_END; end else begin state_n = DELAY; end end WAIT_END: begin if(wait2col_start)begin state_n = CHK_COL; end else begin state_n = WAIT_END; end end default: state_n = CHK_COL; endcaseendassign col2row_start = state_c==CHK_COL && end_shake_cnt;assign row2del_start = state_c==CHK_ROW && row_index==3 && end_row_cnt;assign del2wait_start= state_c==DELAY && end_row_cnt;assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_row <= 4'b0; end else if(state_c==CHK_ROW)begin key_row <= ~(1'b1 << row_index); end else begin key_row <= 4'b0; endendalways @(posedge clk or negedge rst_n) begin if (rst_n==0) begin row_index <= 0; end else if(add_row_index) begin if(end_row_index) row_index <= 0; else row_index <= row_index+1 ; end else if(state_c!=CHK_ROW)begin row_index <= 0; endendassign add_row_index = state_c==CHK_ROW && end_row_cnt;assign end_row_index = add_row_index && row_index == 4-1 ;always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin row_cnt <= 0; end else if(add_row_cnt) begin if(end_row_cnt) row_cnt <= 0; else row_cnt <= row_cnt+1 ; endendassign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;assign end_row_cnt = add_row_cnt && row_cnt == 16-1 ;always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_col_get <= 0; end else if(state_c==CHK_COL && end_shake_cnt ) begin if(key_col_ff1==4'b1110) key_col_get <= 0; else if(key_col_ff1==4'b1101) key_col_get <= 1; else if(key_col_ff1==4'b1011) key_col_get <= 2; else key_col_get <= 3; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_out <= 0; end else if(state_c==CHK_ROW && end_row_cnt)begin key_out <= {row_index,key_col_get}; end else begin key_out <= 0; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_vld <= 1'b0; end else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin key_vld <= 1'b1; end else begin key_vld <= 1'b0; endend
1.3 工作状态选择模块设计
1.3.1接口信号
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,在命令模式下输入如下内容,即可生成本模块所需要的状态机代码。
使用明德扬的状态机模板,可以很快速的写出此模块代码。
always @(*)begin case(key_num) 4'd0 :key_num_chg = 4'd7 ; 4'd1 :key_num_chg = 4'd8 ; 4'd2 :key_num_chg = 4'd9 ; 4'd3 :key_num_chg = 4'd10 ; 4'd7 :key_num_chg = 4'd11 ; 4'd8 :key_num_chg = 4'd1 ; 4'd9 :key_num_chg = 4'd2 ; 4'd10 :key_num_chg = 4'd3 ; 4'd11 :key_num_chg = 4'd14 ; 4'd12 :key_num_chg = 4'd0 ; 4'd13 :key_num_chg = 4'd12 ; 4'd14 :key_num_chg = 4'd13 ; default:key_num_chg = key_num; endcaseendassign 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;assign key_op_en = (key_num_chg==10 || key_num_chg==11 || key_num_chg==12 || key_num_chg==13) && key_vld==1;assign key_cal_en = key_num_chg==15 && key_vld==1; assign key_back_en = key_num_chg==14 && key_vld==1;always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin state_c <= OP_1 ; end else begin state_c <= state_n; endendalways @(*) begin if(result_err)begin state_n = ERROR; end else begin case(state_c) OP_1 :begin if(op_12oper_start) state_n = OPER ; else if(op_12result_start) state_n = RESULT ; else state_n = state_c ; end OPER :begin if(oper2op_2_start) state_n = OP_2 ; else if(oper2result_start) state_n = RESULT ; else state_n = state_c ; end OP_2 :begin if(op_22oper_start) state_n = OPER ; else if(op_22result_start) state_n = RESULT ; else state_n = state_c ; end RESULT :begin if(result2op_1_start) state_n = OP_1 ; else if(result2oper_start) state_n = OPER ; else state_n = state_c ; end ERROR :begin if(error2op_1_start) state_n = OP_1 ; else state_n = state_c ; end default : state_n = OP_1 ; endcaseendendassign op_12oper_start = state_c==OP_1 && key_op_en ;assign op_12result_start = state_c==OP_1 && key_cal_en;assign oper2op_2_start = state_c==OPER && key_num_en;assign oper2result_start = state_c==OPER && key_cal_en;assign op_22oper_start = state_c==OP_2 && key_op_en ;assign op_22result_start = state_c==OP_2 && key_cal_en;assign result2op_1_start = state_c==RESULT && key_num_en;assign result2oper_start = state_c==RESULT && key_op_en ;assign error2op_1_start = state_c==ERROR && key_num_en;always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_num_out <= 0; end else begin key_num_out <= key_num_chg; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_vld_out <= 0; end else begin key_vld_out <= key_vld; endend
1.4 运算数1模块设计
1.4.1接口信号
1.4.2设计思路
该模块主要的作用是根据当前状态和输入的按键,来决定运算数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、 其他时刻保持为低电平。
assign key_num_en = (key_num==0 || key_num==1 || key_num==2 || key_num==3 || key_num==4 || key_num==5 || key_num==6 || key_num==7 || key_num==8 || key_num==9) && key_vld==1;assign key_op_en = (key_num==10 || key_num==11 || key_num==12 || key_num==13) && key_vld==1;assign key_cal_en = key_num==15 && key_vld==1; assign key_back_en = key_num==14 && key_vld==1;always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin state_c_ff <= 0; end else begin state_c_ff <= state_c; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin op_2 <= 0; end else if(state_c==OPER)begin op_2 <= 0; end else if(state_c_ff==OPER && state_c==OP_2)begin op_2 <= key_num; end else if(state_c==OP_2 && key_back_en==1)begin op_2 <= op_2 / 10; end else if(state_c==OP_2 && key_num_en==1)begin op_2 <= (op_2>9999999) ? op_2 : (op_2*10+key_num); end else begin op_2 <= op_2; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin op_2_err <= 0; end else if(state_c==OP_2 && key_num_en==1 && op_2>9999999)begin op_2_err <= 1'b1; end else begin op_2_err <= 1'b0; endendendmodule
1.4.3参考代码
1.5 运算符模块设计
1.5.1接口信号
1.5.2设计思路
本模块的设计思路比较简单,只需要判断哪些按键是运算符,然后在这些运算符被按下的时候,将他们对应的值输出就可以了。
下面是运算符指示信号设计思路:
1、 当“加”“减”“乘”“除”四个按键的任意一个被按下之后,该信号置为高电平;
2、 当“加”“减”“乘”“除”四个按键没有一个被按下的时候,该信号置为低电平。
下面是运算符输出信号oper设计思路:
初始状态,该信号输出0;
1、 当处于操作数1状态时,输出0;
2、 当“加”“减”“乘”“除”任意按键被按下之后,输出该按键对应的值;
3、 其他时候保持不变;
1.5.3参考代码
assign key_op_en = (key_num==10 || key_num==11 || key_num==12 || key_num==13) && key_vld==1;always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin oper <= 0; end else if(state_c==OP_1)begin oper <= 0; end else if(key_op_en==1)begin oper <= key_num; end else begin oper <= oper; endend
1.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参考代码
assign key_num_en = (key_num==0 || key_num==1 || key_num==2 || key_num==3 || key_num==4 || key_num==5 || key_num==6 || key_num==7 || key_num==8 || key_num==9) && key_vld==1;assign key_op_en = (key_num==10 || key_num==11 || key_num==12 || key_num==13) && key_vld==1;assign key_cal_en = key_num==15 && key_vld==1; assign key_back_en = key_num==14 && key_vld==1;always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin state_c_ff <= 0; end else begin state_c_ff <= state_c; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin flag_calc <= 0; end else if(state_c==OP_1)begin flag_calc <= 1'b0; end else if(state_c_ff==OP_2 && state_c==OPER || state_c==RESULT)begin flag_calc <= 1'b1; end else begin flag_calc <= flag_calc; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin op_1 <= 0; end else if(state_c==ERROR)begin op_1 <= 10; end else if((state_c_ff==RESULT || state_c_ff==ERROR) && state_c==OP_1)begin op_1 <= key_num; end else if(state_c==OP_1 && key_back_en==1)begin op_1 <= op_1 / 10; end else if(state_c==OP_1 && key_num_en==1)begin op_1 <= (op_1>9999999) ? op_1 : (op_1*10+key_num); end else if(flag_calc==1)begin op_1 <= result; end else begin op_1 <= op_1; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin op_1_err <= 0; end else if(state_c==OP_1 && key_num_en==1 && op_1>9999999)begin op_1_err <= 1'b1; end else begin op_1_err <= 1'b0; endend
由于篇幅限制,本文分为(一)(二)两篇;请接着学习(二)。
本案例提供教学视频和工程源代码,需要的同学请到明德扬论坛进行学习。
感兴趣的朋友也可以访问明德扬论坛()进行FPGA相关工程设计学习,也可以看一下我们往期的文章:
《基于FPGA的密码锁设计》
《波形相位频率可调DDS信号发生器》
《基于FPGA的曼彻斯特编码解码设计》
《基于FPGA的出租车计费系统》
《数电基础与Verilog设计》
《基于FPGA的频率、电压测量》
《基于FPGA的汉明码编码解码设计》
《关于锁存器问题的讨论》
《阻塞赋值与非阻塞赋值》
《参数例化时自动计算位宽的解决办法》
明德扬是一家专注于FPGA领域的专业性公司,公司主要业务包括开发板、教育培训、项目承接、人才服务等多个方向。点拨开发板——学习FPGA的入门之选。
MP801开发板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习FPGA。周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才服务——提供人才推荐、人才代培、人才派遣等服务。