1 项目要求
(1)实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,4×4键盘按键分配如下图所示。
(2)运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
2 完成的功能介绍
2.1 功能框图
2.2 数码管显示
项目实现了运算数和结果的显示,具体如下:
(1)刚开机或复位后,显示0
(2)输入第一个运算数后,显示该运算数
(3)输入运算符后,不显示任何,等待输入第二个运算数
(4)输入第二个运算数后,显示该运算数
(5)点击等于后,显示计算结果,如这个例子中是计算32+68,则结果为100
(6)在有错误操作时,如连续输入两次运算符、不输入第二个运算数直接按等于号等等,则会显示ERROR
2.3 运算
项目实现了加减乘除四种运算,运算数限于两位数以内,其中减法可以得到负数结果,除法为整数除法,即结果去掉了小数点后的部分。
3 整体思路
整个项目可以分成以下几部分:
- 数码管显示
- 状态机编写
- 运算过程编写
- 按键输入部分
4 实现过程
4.1 数码管显示
这次活动的板子配置了74HC595,这是一个8位串行输入、并行输出的位移缓存器,以此来控制数码管,资料中提供了驱动的例程,本项目在例程的基础上修改每个数码管显示的值和使能信号,从而显示需要的内容。
值得注意的是,在将数值显示在数码管上时,需要将二进制编码改变为BCD编码方式,BCD码也称为二进码十进数,因为数码管需要显示十进制数据,若不转换编码方式,会对数据显示的正确性产生影响。这个程序在例程中也有相对应的,只需稍作修改。
4.2 状态机编写
为了实现计算过程的正常实现,本项目编写了状态机算法。设定初始(IDLE)、一个运算数(S_1N)、一运算数一运算符(S_1N1S)、两运算数(S_2N)、一结果(S_1R)、错误(ERROR)六种状态,将数入的按键分为数字、运算符、等于号三类,当有按键按下时,根据按键类型和目前状态确定下一状态。具体状态转变过程如下。
always@(posedge clk or negedge rst ) begin
if (!rst) begin
nstate <= 4'd0;end
else if (key_pulse>16'd0)begin
if (key_type==2'd0)//数字
case (cstate)
CAL_IDLE:nstate=S_1N;
S_1N:nstate=S_1N;
S_1N1S:nstate=S_2N;
S_2N:nstate=S_2N;
S_1R:nstate=S_1N;
ERROR:nstate=S_1N;
default:nstate=cstate;
endcase
else if (key_type==2'd1)//运算符
case(cstate)
CAL_IDLE:nstate=ERROR;
S_1N:nstate=S_1N1S;
S_1N1S:
if (key<7'd16)
nstate=ERROR;
else
nstate=S_1N1S;
S_2N:nstate=ERROR;
S_1R:nstate=ERROR;
ERROR:nstate=ERROR;
default:nstate=cstate;
endcase
else if (key_type==2'd2)//等于
case(cstate)
CAL_IDLE:nstate=ERROR;
S_1N:nstate=S_1R;
S_1N1S:nstate=ERROR;
S_2N:nstate=S_1R;
S_1R:nstate=S_1R;
ERROR:nstate=ERROR;
default:nstate=cstate;
endcase
else
nstate=cstate;
end
end
与每个状态对应的,有不同的运算数、运算符、计算信号的变化方式,以及数码管显示的数值和位数等等信息,均在状态机部分进行了设置。
4.3 运算过程编写
运算过程没有什么难点,用正常的+、-、*、/就可以,值得注意的是当减法结果小于0时,需要进行判断,并更新负号显示状态:仅在结果显示时显示负号,一旦开始下一次计算,即一个数字类型按键被按下时,停止显示负号。
always@(posedge clk or negedge rst) begin
if (!rst)
s<=1'b0;//s为负号显示信号
if (cal_pulse==1'b1 & num2>num1 & operator==2'd2)
s<=1'b1;
else if (s_pulse==1'b1)
s<=1'b0;
else
s<=s;
end
4.4 按键输入部分
按键输入也移植了硬禾学堂提供的例程,但由于要编写状态机,选择使用key_pulse作为有输入信号的标志。另外,编写代码以获得按键的值和类型。
//我们把按键信息转换成计算器按键的值,数字按键的值就为0-9,运算符号按键的值为10-14,等于号按键的值为15,无输入时值为16
always@(key_in) begin
case(key_in)
16'h0001: key = 5'd7; //7
16'h0002: key = 5'd8; //8,
16'h0004: key = 5'd9; //9,
16'h0008: key = 5'd10; //除,
16'h0010: key = 5'd4; //4,
16'h0020: key = 5'd5; //5,
16'h0040: key = 5'd6; //6,
16'h0080: key = 5'd11; //x,
16'h0100: key = 5'd3; //3,
16'h0200: key = 5'd2; //2,
16'h0400: key = 5'd1; //1,
16'h0800: key = 5'd12; //-,
16'h1000: key = 5'd0; //0,
16'h2000: key = 5'd14; //.,
16'h4000: key = 5'd15; //=,
16'h8000: key = 5'd13; //+,
default: key = 5'd16; //0
endcase
end
//转换成按键类型的过程与上类似,类型共三种:1:数字(0-9),2:运算符(+、-、x、/),3:等于号(=)
5 FPGA资源占用报告
6 困难和还可以优化的问题
- 这次选择的题目相对来说比较简单,最主要的困难就是状态机的编写,各种信号的变化比较复杂,容易造成时序上的错误。
- 除法结果可以再添加小数部分。
- 在LCD上显示运算数和结果
7 未来计划和建议
- 这块板子的外设非常丰富,让人很心动,之后会进行更多的开发和练习。这次由于报名了教资(完全没想到要背那么多...),选择了较为简单的题目,之后会尝试一下其他的题目。