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参考代码
modulecalc_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_NUM4-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_statework_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 ) ); resultresult_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_seldisplay_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) ); segmentsegment_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 设计思路
在前面的案例中已经有矩阵键盘的先容,以是这里不在过多先容,详细先容请看下方链接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310
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; endcaseendassignkey_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;assignkey_op_en = (key_num_chg==10 || key_num_chg==11 || key_num_chg==12 || key_num_chg==13) && key_vld==1;assignkey_cal_en = key_num_chg==15 && key_vld==1; assignkey_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、 其他时候保持为低电平。
assignkey_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;assignkey_op_en = (key_num==10 || key_num==11 || key_num==12 || key_num==13) && key_vld==1;assignkey_cal_en = key_num==15 && key_vld==1; assignkey_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_210+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参考代码
assignkey_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参考代码
assignkey_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;assignkey_op_en = (key_num==10 || key_num==11 || key_num==12 || key_num==13) && key_vld==1;assignkey_cal_en = key_num==15 && key_vld==1; assignkey_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_110+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
由于篇幅限定,本文分为(一)(二)两篇;请接着学习(二)。
本案例供应传授教化视频和工程源代码,须要的同学请到明德扬论坛进行学习。
感兴趣的朋友也可以访问明德扬论坛(http://www.FPGAbbs.cn/)进行FPGA干系工程设计学习,也可以看一下我们往期的文章:
《基于FPGA的密码锁设计》
《波形相位频率可调DDS旗子暗记发生器》
《基于FPGA的曼彻斯特编码解码设计》
《基于FPGA的出租车计费系统》
《数电根本与Verilog设计》
《基于FPGA的频率、电压丈量》
《基于FPGA的汉明码编码解码设计》
《关于锁存器问题的谈论》
《壅塞赋值与非壅塞赋值》
《参数例化时自动打算位宽的办理办法》
明德扬是一家专注于FPGA领域的专业性公司,公司紧张业务包括开拓板、教诲培训、项目承接、人才做事等多个方向。点拨开拓板——学习FPGA的入门之选。MP801开拓板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管韶光和空间,明德扬随时在你身边,助你快速学习FPGA。周末培训班——来日诰日的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,得到丰富的项目履历,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字旗子暗记处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才做事——供应人才推举、人才代培、人才叮嘱消磨等做事。