项目需求
该项目的目的是为了实现一个两位十进制数的计算器,该计算器能够实现加、减、乘、除,并且这些功能是通过开发板上的按键来进行实现的。其每个数字和运算符号刚好构成了4*4的键盘,其键盘按键的分布图如下图所示:
图1
在进行该项目的编写时,我实现了题目所给的基本要求,即:运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
需求分析
为了实现上述要求的计算器我们需要考虑得是运算结果及位数。我们的数码管只有八位,输入数据最多包括两个两位的十进制数,占用了四位数码管,因为只需要实现加减乘除,其中乘法会使运算结果的位数最多,但是我们知道,两个两位数的乘法结果最多是四位数,因此八位数码管刚好能实现我们的运算要求。然后就是按键的分布,我们只需要按照题目所给的要求对板子的按键匹配分布即可。接下来就是运算时的需求,对于加法、减法以及乘法来说,都是较好实现的运算,我们需要着重考虑的是除法的运算,如何采取一个合适的设计方法来使的除法运算得到正确的结果是我们需要考虑的,我们在接下来的实现方法中会着重描述怎么解决除法的问题。
实现方法
首先对于我们对于计算器的输入按键值需要定义出不同的状态,然后根据输入的状态来判断出输入的数据是数字,运算符号还是等号。在不同的状态下我们所进行的操作也不同,比如我们将输入的第一个数字定为个位数,再输入数字时他的值就会扩大十倍变成十位数,新输入的数字就变成了个位数;对于运算符号,我们检测到输入运算符后,先记录下此时的状态,再输入第二个数据的时候再进行运算,对于加法,和乘法我们就进行正常的操作,对于减法和除法我们考虑了一些情况。在对减法进行操作时,我们需要首先判断减数以及被减数的大小,我们都使大的那个数减去小的那个数,但是不同的是我们用一个变量来确定这个结果的正负,以此来方便我们数码管的显示。对于除法来说我们知道结果可以显示四位数,因此我们首先将被除数扩大1000倍,然后再来除以我们的除数,如果我们的结果万位上有值就说明此时的小数点在四位数结果的第二位上,否则我们的小数点就在四位数结果的第一位上,通过此方法我们解决了除法上小数点位数的问题。除了上述外我们通过上拉的方式,来判断此时按键的状态,并且对按键进行了消抖处理。我们设置系统频率为50MHz,以此来使我们数码管的显示更加的稳定。
功能框图
刚通电时的开发板如下图所示,此时数码管默认为不亮的状态。
图2
通过该开发板实现简易计算器的各个按键功能如下图所示,其按键分布如题目所给的4×4键盘一致,并且每个按键所代表的符号也已经标注出来,并且我们将复位键也标识出来了,通过这个按键我们可以对数码管进行复位,即对于我们所设计的计算器来说就是实现清零的功能。
图3
加法操作:
图4
如上图所示,我们实现了一个75+55的加法操作,并且得到了正确的结果130。
减法操作:
图5
如上图所示我们实现了8-52的减法操作,并且计算得到了正确的结果-44,说明了该计算器也考虑到了结果为负数的情况。
乘法操作:
图6
如上图所示我们实现了75×25的乘法操作,并且计算得到了正确的结果1875。
除法操作:
图7
如上图所示,我们计算了85÷7的除法操作,由于我们的结果显示只有四位数码管,因此我们保留了两位小数得到了正确的结果12.14。
软件流程图
我们实现该简易计算器的流程图如下所示:
如上述流程图所示,实现该计算器的主要操作流程就是首先按下复位键实现计算器的清零,然后输入第一个运算数之后按下需要进行的加减乘除运算按键的其中一个,然后再输入第二个数,最后按下等号键得到最终的结果,其中每次输入的两个运算数都会在数码管的前四位显示,得到的结果会在数码管的后四位显示。
主要代码及说明
整个代码主要分为四个大的模块部分,分别是顶层模块部分,数字键盘模块部分,控制模块部分,以及数码管扫描模块部分。其中最为重要的是控制模块部分,在这个模块展示了实现该计算器代码得核心部分。下面分别是各个模块部分的响应代码及其说明。
顶层模块:
在该模块中主要介绍了输入的时钟信号,复位信号,输出的键盘模块。该模块的主要功能有:
1、key_board模块用于将键盘输入转换为键值和使能信号。
2、control模块用于根据键值和使能信号生成控制信号,控制数码管显示特定内容。
3、segment_scan模块用于将控制信号转换为数码管的段选和位选信号。
module top(
input clk,
input rst_n,
output [3:0]key_col,
input [3:0]key_row,
output seg_rck,
output seg_sck,
output seg_din
);
wire key_en;
wire [3:0]key_value;
wire [31:0]hex_dis;
wire [7:0] sel;//数码管位选
wire [7:0] seg;//数码管段选
wire [7:0] point;//小数点
key_board key_board(
.Clk (clk),
.Rst_n (rst_n),
.Key_Board_Row_i(key_row),
.Key_Board_Col_o(key_col),
.Key_Flag (key_en),
.Key_Value (key_value)
);
control control(
.clk(clk),
.rst_n(rst_n),
.key_en(key_en),
.key_value(key_value),
.point(point),
.hex_dis(hex_dis)
);
segment_scan u3(
.clk (clk ),
.rst_n (rst_n ), //系统复位 低有效
.dat_1 (hex_dis[31:28] ),
.dat_2 (hex_dis[27:24] ),
.dat_3 (hex_dis[23:20] ),
.dat_4 (hex_dis[19:16] ),
.dat_5 (hex_dis[15:12] ),
.dat_6 (hex_dis[11: 8] ),
.dat_7 (hex_dis[ 7: 4] ),
.dat_8 (hex_dis[ 3: 0] ),
.dat_en (8'hff ),
.dot_en (point ),
.seg_rck (seg_rck ),
.seg_sck (seg_sck ),
.seg_din (seg_din )
);
endmodule
数字键盘模块:
该模块的代码主要实现了对键盘输入的扫描和识别,以及根据扫描结果来输出响应的按键值,并且标识出是否有按键被按下。该模块主要功能有:
1、使用时钟信号和复位信号对计数器进行控制,以及一些状态的初始化。
2、通过状态机控制键盘扫描的过程,包括按键检测、行扫描、列扫描、按键结果判断等步骤。
3、根据扫描结果,确定按键的值,并输出到 Key_Value 中。
4、最终根据按键扫描的状态变化,更新按键标志位 Key_Flag。
该模块的主要代码如下
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Cnt_Done <= 1'b0;
else if(counter1 == 20'd999999)
Cnt_Done <= 1'b1;
else
Cnt_Done <= 1'b0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
En_Cnt <= 1'b0;
state <= IDEL;
Key_Board_Col_o <= 4'b0000;
Col_Tmp <= 4'd0;
Key_Flag_r <= 1'b0;
Key_Value_tmp <= 8'd0;
Key_Board_Row_r <= 4'b1111;
end
else begin
case(state)
IDEL:
if(~&Key_Board_Row_i)begin
En_Cnt <= 1'b1;
state <= P_FILTER;
end
else begin
En_Cnt <= 1'b0;
state <= IDEL;
end
P_FILTER:
if(Cnt_Done) begin
En_Cnt <= 1'b0;
state <= READ_ROW_P;
end
else begin
En_Cnt <= 1'b1;
state <= P_FILTER;
end
READ_ROW_P:
if(~Key_Board_Row_i)begin
Key_Board_Row_r <= Key_Board_Row_i;
state <= SCAN_C0;
Key_Board_Col_o <= 4'b1110;
end
else begin
state <= IDEL;
Key_Board_Col_o <= 4'b0000;
end
SCAN_C0:
begin
state <= SCAN_C1;
Key_Board_Col_o <= 4'b1101;
if(~&Key_Board_Row_i)
Col_Tmp <= 4'b0001;
else
Col_Tmp <= 4'b0000;
end
SCAN_C1:
begin
state <= SCAN_C2;
Key_Board_Col_o <= 4'b1011;
if(~&Key_Board_Row_i)
Col_Tmp <= Col_Tmp | 4'b0010;
else
Col_Tmp <= Col_Tmp;
end
SCAN_C2:
begin
state <= SCAN_C3;
Key_Board_Col_o <= 4'b0111;
if(~&Key_Board_Row_i)
Col_Tmp <= Col_Tmp | 4'b0100;
else
Col_Tmp <= Col_Tmp;
end
SCAN_C3:
begin
state <= PRESS_RESULT;
if(~&Key_Board_Row_i)
Col_Tmp <= Col_Tmp | 4'b1000;
else
Col_Tmp <= Col_Tmp;
end
PRESS_RESULT:
begin
state <= WAIT_R;
Key_Board_Col_o <= 4'b0000;
if(((Key_Board_Row_r[0] + Key_Board_Row_r[1] + Key_Board_Row_r[2] + Key_Board_Row_r[3]) == 4'd3) &&
((Col_Tmp[0] + Col_Tmp[1] + Col_Tmp[2] + Col_Tmp[3]) == 4'd1))begin
Key_Flag_r <= 1'b1;
Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp};
end
else begin
Key_Flag_r <= 1'b0;
Key_Value_tmp <= Key_Value_tmp;
end
end
WAIT_R:
begin
Key_Flag_r <= 1'b0;
if(&Key_Board_Row_i)begin
En_Cnt <= 1'b1;
state <= R_FILTER;
end
else begin
state <= WAIT_R;
En_Cnt <= 1'b0;
end
end
R_FILTER:
if(Cnt_Done) begin
En_Cnt <= 1'b0;
state <= READ_ROW_R;
end
else begin
En_Cnt <= 1'b1;
state <= R_FILTER;
end
READ_ROW_R:
if(&Key_Board_Row_i)
state <= IDEL;
else begin
En_Cnt <= 1'b1;
state <= R_FILTER;
end
default:state <= IDEL;
endcase
end
控制模块:
在这个模块中实现了该计算器的核心逻辑模块,我们通过按键盘来实现我们数据的输入以及加减乘除运算,然后我们将得到的计算结果和输入数据以BCD码显示在8位数码管上。该模块的主要功能有:
1、将按键输入的值转换为对应的ASCII字符。
2、定义了状态机,根据不同的状态进行不同的操作,包括输入第一个数字的个位和十位、选择操作符、输入第二个数字的个位和十位、计算结果等。
3、使用BCD模块将计算结果转换为BCD码,分别输出个位、十位、百位、千位和万位的BCD码,其中当有万位存在时说明进行的是除法的操作,此时需要取结果的前四位并且将小数点确定在第二位数据身上此时才能得到正确结果,具体见演示视频。
4、在BCD模块中使用到了大四加三的BCD编码算法,该算法帮助我们更好的将二进制转化成为了BCD码。
5、根据当前状态和计算结果,控制数码管显示,包括显示输入数字、操作符、计算结果等,并且处理小数点的位置。
该模块主要代码如下:
always@(posedge clk or negedge rst_n)
if(~rst_n)
begin
state <= WAIT_NUM1_GE;
option_type <= 2'd0;
end
else
case(state)
WAIT_NUM1_GE:
if(key_en &&key_asc>=8'h30 && key_asc<=8'h39)
begin
state <= WAIT_NUM1_SHI;
option_num1 <= key_asc-8'h30;
dis_num1 <= key_asc-8'h30;
end
else
state <= WAIT_NUM1_GE;
WAIT_NUM1_SHI:
if(key_en &&key_asc>=8'h30 && key_asc<=8'h39)
begin
state <= WAIT_OPTION;
option_num1 <= option_num1*10+key_asc-8'h30;
dis_num1 <= {dis_num1[3:0],4'h0}+key_asc-8'h30;
end
else if((key_asc=="+"||key_asc=="-"||key_asc=="X"||key_asc=="/") && key_en)
begin
state <= WAIT_NUM2_GE;
case(key_asc)
"+" : option_type <= 0;
"-" : option_type <= 1;
"X" : option_type <= 2;
"/" : option_type <= 3;
default:option_type <= 0;
endcase
if(key_asc=="/")
option_num1 <= option_num1 *1000;
end
else
state <= WAIT_NUM1_SHI;
WAIT_OPTION:
if((key_asc=="+"||key_asc=="-"||key_asc=="X"||key_asc=="/") && key_en)
begin
state <= WAIT_NUM2_GE;
case(key_asc)
"+" : option_type <= 0;
"-" : option_type <= 1;
"X" : option_type <= 2;
"/" : option_type <= 3;
default:option_type <= 0;
endcase
if(key_asc=="/")
option_num1 <= option_num1 *1000;
end
else
state <= WAIT_OPTION;
WAIT_NUM2_GE:
if(key_en &&key_asc>=8'h30 && key_asc<=8'h39)
begin
state <= WAIT_NUM2_SHI;
option_num2 <= key_asc-8'h30;
dis_num2 <= key_asc-8'h30;
end
else
state <= WAIT_NUM2_GE;
WAIT_NUM2_SHI:
if(key_en &&key_asc>=8'h30 && key_asc<=8'h39)
begin
state <= WAIT_OK;
option_num2 <= option_num2*10+key_asc-8'h30;
dis_num2 <= {dis_num2[3:0],4'h0}+key_asc-8'h30;
end
else if(key_asc=="=" && key_en)
begin
state <= OPTION_OK;
case(option_type)
0:result <= option_num1+option_num2;
1:result <= (option_num1>option_num2)?(option_num1-option_num2):(option_num2-option_num1);
2:result <= option_num1*option_num2;
3:result <= option_num1/option_num2;
endcase
if(option_type==1&&(option_num1<option_num2))
zero_flag <= 1'b1;
else
zero_flag <= 1'b0;
end
else
state <= WAIT_NUM2_SHI;
WAIT_OK:
if(key_asc=="=" && key_en)
begin
state <= OPTION_OK;
case(option_type)
0:result <= option_num1+option_num2;
1:result <= (option_num1>option_num2)?(option_num1-option_num2):(option_num2-option_num1);
2:result <= option_num1*option_num2;
3:result <= option_num1/option_num2;
endcase
if(option_type==1&&(option_num1<option_num2))
zero_flag <= 1'b1;
else
zero_flag <= 1'b0;
end
else
state <= WAIT_OK;
OPTION_OK:state <= DIS_RESULT;
DIS_RESULT:
state <= DIS_RESULT;
default:state <= WAIT_NUM1_GE;
endcase
数码管扫描模块:
在该模块中实现了数字显示的模块,将输入的信号以及输出的结果都通过状态机控制数码管的扫描和时序操作,将数据在八位的数码管显示出来。该模块的主要功能有:
1、输入数据控制:通过 dat_1 到 dat_8 输入端口输入需要显示的数据,在 dat_en 信号使能下选择要显示的数码管,同时可以通过 dot_en 控制是否显示小数点。
2、数码管扫描:使用状态机进行数码管的扫描,通过控制每个数码管的使能信号实现数据的显示。在 MAIN 状态下根据计数值选择要显示的数据,从而实现多个数码管显示不同的数字。
3、时序控制:利用计数器生成 40 kHz 的时钟信号,控制数码管的刷新频率,同时通过 74HC595 芯片实现数据的串行输出和并行加载,实现数码管显示数据的更新。
仿真部分
我们在完成了代码后,除了在开发板上验证以外我们还进行了仿真操作,由于篇幅原因我们只放出74-30的仿真图形,其图形如下图所示。
图8
我们通过上图可以清晰的看到在进行74-30这个操作后我们得到了正确的结果44。因此可以看出我们的程序的正确性。
FPGA资源利用报告
项目总结
在完成这个项目的过程中遇到了许多的困难,比如怎么解决除法的问题,以及怎么解决数码管片选的问题。但好在小脚丫平台给了我许多参考资料,让我能较好的理解以及运用这些资料来使自己的代码完善,总之此次活动让我收获很多,感谢平台给的这次机会。