基于小脚丫FPGA套件STEP BaseBoard V4.0实现两位十进制加、减、乘、除计算器
项目需求:
通过思得普的WebIDE进行verilog编程,用小脚丫FPGA套件STEP BaseBoard V4.0完成一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制。十六个矩阵键盘按图一分布。
图一
同时应满足运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
需求分析:
该项目需要使用小脚丫FPGA套件STEP BaseBoard V4.0上的矩阵键盘和数码管作为按键输入和显示,同时还要有数据输入后的计算与处理。所以该项目应当包括三个主要模块:①矩阵键盘输入模块②数码管显示模块③数据计算与处理模块。
各模块应当完成的功能:
矩阵键盘输入模块:按键后将按键信息准确的传入数据计算与处理模块。
数码管显示模块:将数据计算与处理模块的输出信息准确的显示在数码管上。
数据计算与处理模块:将从矩阵键盘输入的数据按照运算法则进行计算输出可以在数码管上显示的数据。
实现的方式:
开发工具:思得普的WebIDE在线开发工具
在写程序时为了方便模块间的交流,将矩阵键盘模块与数码管显示模块,数据计算与处理模块放在一个模块中。同时因为除法模块在数据显示时需要其多次使用求被显示数的个位,十位,百位,千位,万位,所以将数据计算与处理模块的下辖的除法模块单独放在一个模块中,方便其多次使用。
矩阵键盘模块实现方式:
参考电子森林官网矩阵按键驱动模块代码,对四行键盘依次扫描。行输入在0111,1011,1101,1110中循环,检测列输出,哪一个按键被按下,则在该按键按下时,这一行输入为0时,输出信号为0。一直重复扫描,将按键情况记录在16位keyboard中。哪一位变为0就说明哪个按键被按下。
数码管显示模块实现方式:
参考电子森林官网数码管显示模块,对每个数码管高频率轮流输出,来实现各个数码管同时显示的情况。驱动芯片使用两块级联74hc595d,实现串行输入,同时并行输出16位信号,控制数码管的闪烁。
数据计算与处理模块(主要有四个模块):
按键输入与记录部分采用完全归纳的方法,将按键按下的情况分为两大类情况。第一类是输入一个数字的第一个数位,和输入第一个数位后的情况。之所以这样分类,是考虑到在输入一个数时第一个数位一定输入的是数字,而在按下一个数字后就可能出现很多情况,包括数字,小数点,运算负号,等号这几种其他情况。所以分两大类来写,第一类情况(第一个数字按下)完成后,calstyle变为1实现下次按键时进入第二种情况。第二种情况(第一个数字被按下后)又分为两种情况,一种是直接再次按下数字或运算符,也就是最简单的情况,如果第二次按下的是数字,则calstyle不变依旧执行第二个模块,如果是按下运算符则说明一个数据输入完成,回到第一个模块进行第二个运算数的输入,另一种情况是第二次按下了点,这时我另外写了一个输入按键的if语块,因为这时输入的一定是数字,不可能按下其他按键。
计算部分也采用完全归纳的方法,将四种运算写入四个if条件中,每个运算符下又分为四个情况,分别是两个计算数都有小数点,第一个计算数有小数点第二个计算数没点,第一个计算数没有小数点第二个计算数有小数点,两个计算数都没点。我相当于把数字运算和小数点运算分开了,因为这样我认为比较方便,只有加法和减法中各两种小情况一共四种情况需要考虑小数点,而且这四种情况处理情况也很简单,只需要根据小数点的情况给某一个计算数乘十再加减即可。小数点运算独立后也极为简单,乘法叠加,加法减法不变,除法相减或有需要时给结果乘十。
除法部分采用恢复余数法。如果余数大于除数,商上1余数减去除数,除数右移一位。如果余数小于除数,商上0,余数不变,除数右移一位。反复进行,最后得出结果。
显示前数据处理部分:
采用反复除十的方法提取结果数的个位十位百位千位万位,将其传给数码管显示的模块。
k4按钮是清零按钮。
功能框图:
主要代码及说明:
---------------------------------------------------
矩阵键盘与数码管模块均使用电子森林网站上的教程代码,无须介绍,本项目关键部分是运算模块
数据计算与处理模块:
按键输入与记录核心部分:
代码讲解:
整体代码介绍:有效数据输入后,进行甄别,看该按钮对应什么数,记录下该位数并进行显示,接着进入下一位数的输入或者运算符或小数点的输入过程,输入后记录输入结果。清除运算展示后进入第二个数的输入,方式与上面输入第一个数的方式相同,使用同一个代码。
详细部分通过注释的形式介绍
always@(negedge flag or negedge rst_n_in)begin //flag为有效输入信号
if(!rst_n_in)begin //复位对各种初识数据初始化
seg_data_en<=8'b11111111;
seg_dot_en<=8'b00000000;
seg_data_1<=4'b0000;
seg_data_2<=4'b0000;
seg_data_3<=4'b0000;
seg_data_4<=4'b0000;
seg_data_5<=4'b0000;
seg_data_6<=4'b0000;
seg_data_7<=4'b0000;
seg_data_8<=4'b0000;
calstyle<=1'b0;
jia<=1'b0;
num1<=20'b0000_0000_0000_0000_0000;
jian<=1'b0;
chen<=1'b0;
chu<=1'b0;
num1xiao<=8'b0000_0000;
dot_en<=1'b0;
inputdone<=2'b00;
dot_remember<=1'b0;
end else
if(!calstyle)begin //calstyle记录数据的进程,为0时输入第一个数据,为1时输入第二个数据
if(!key_out_0)begin //输入7
seg_data_8<=4'b0111; //数码管显示7
num1<=20'b0000_0000_0000_0000_0111; //记录第一个输入的数字用于后续运算
seg_data_en<=8'b11111111; //数码管使能
dot_remember<=1'b0; //记录小数点输入状况
calstyle<=1'b1;end //calstyle变为1下一次进入该模块时进入第二次输入的模式
else if(!key_out_1)begin
seg_data_8<=4'b1000;
num1<=20'b0000_0000_0000_0000_1000; //输入8
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_2)begin
seg_data_8<=4'b1001; //输入9
num1<=20'b0000_0000_0000_0000_1001;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_4)begin
seg_data_8<=4'b0100; //输入4
num1<=20'b0000_0000_0000_0000_0100;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_5)begin
seg_data_8<=4'b0101; //输入5
num1<=20'b0000_0000_0000_0000_0101;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_6)begin
seg_data_8<=4'b0110; //输入6
num1<=20'b0000_0000_0000_0000_0110;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_10)begin
seg_data_8<=4'b0001; //输入1
num1<=20'b0000_0000_0000_0000_0001;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_9)begin
seg_data_8<=4'b0010; //输入2
num1<=20'b0000_0000_0000_0000_0010;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_8)begin
seg_data_8<=4'b0011; //输入3
num1<=20'b0000_0000_0000_0000_0011;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else if(!key_out_12)begin
seg_data_8<=4'b0000; //输入0
num1<=20'b0000_0000_0000_0000_0000;
seg_data_en<=8'b11111111;
dot_remember<=1'b0;
calstyle<=1'b1;end
else seg_data_en<=8'b11111111;
end else if(calstyle)begin
if(!dot_en)begin//第二下和不按点的第三下
if(!key_out_0)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0111; //输入7
num1<=num1*20'd10+20'd7; //计算运算数
seg_data_en<=8'b11111111;end
else if(!key_out_1)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b1000; //输入8
num1<=num1*20'd10+20'd8;
seg_data_en<=8'b11111111;end
else if(!key_out_2)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b1001; //输入9
num1<=num1*20'd10+20'd9;
seg_data_en<=8'b11111111;end
else if(!key_out_4)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0100; //输入4
num1<=num1*20'd10+20'd4;
seg_data_en<=8'b11111111;end
else if(!key_out_5)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0101; //输入5
num1<=num1*20'd10+20'd5;
seg_data_en<=8'b11111111;end
else if(!key_out_6)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0110; //输入6
num1<=num1*20'd10+20'd6;
seg_data_en<=8'b11111111;end
else if(!key_out_10)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0001; //输入1
num1<=num1*20'd10+20'd1;
seg_data_en<=8'b11111111;end
else if(!key_out_9)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0010; //输入2
num1<=num1*20'd10+20'd2;
seg_data_en<=8'b11111111;end
else if(!key_out_8)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0011; //输入3
num1<=num1*20'd10+20'd3;
seg_data_en<=8'b11111111;end
else if(!key_out_12)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0000; //输入0
num1<=num1*20'd10+20'd0;
seg_data_en<=8'b11111111;end
else if(!key_out_13)begin
dot_en<=1'b1; //输入.
dot_remember<=1'b1;
seg_dot_en<=8'b1000_0000;
end
else if(!key_out_15)begin
jia<=1'b1; //输入+
calstyle<=1'b0; //初始化模块
seg_data_8<=4'b0000; //给数码管清零
seg_data_7<=4'b0000; //给数码管清零
seg_data_en<=8'b11111111; //数码管使能
seg_dot_en<=8'b00000000; //数码管小数点使能
inputdone<=2'b10; end //标志输入第一个数结束
else if(!key_out_11)begin
jian<=1'b1; //输入-
calstyle<=1'b0;
seg_data_8<=4'b0000;
seg_data_7<=4'b0000;
seg_data_en<=8'b11111111;
seg_dot_en<=8'b00000000;
inputdone<=2'b10; end
else if(!key_out_7)begin //输入*
chen<=1'b1;
calstyle<=1'b0;
seg_data_8<=4'b0000;
seg_data_7<=4'b0000;
seg_data_en<=8'b11111111;
seg_dot_en<=8'b00000000;
inputdone<=2'b10; end
else if(!key_out_3)begin //输入 /
chu<=1'b1;
calstyle<=1'b0;
seg_data_8<=4'b0000;
seg_data_7<=4'b0000;
seg_data_en<=8'b11111111;
seg_dot_en<=8'b00000000;
inputdone<=2'b10; end
else if(inputdone_0&inputdone_1)begin //对运算结果进行展示
seg_data_8<=numresult_ge[3:0]; //个位
seg_data_7<=numresult_shi[3:0]; //十位
seg_data_6<=numresult_bai[3:0]; //百位
seg_data_5<=numresult_qian[3:0]; //千位
seg_data_4<=numresult_wan[3:0]; //万位
seg_dot_en<=dot_result;
seg_data_en<=8'b11111111;
end
else if(!key_out_14)begin //输入=
inputdone<=2'b11;
seg_data_en<=8'b11111111;
end
else seg_data_en<=8'b11111111;
end else begin //按完点后的情况
if(!key_out_0)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0111;
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd7;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_1)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b1000; //输入8
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd8;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_2)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b1001; //输入9
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd9;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_4)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0100; //输入4
seg_dot_en<=8'b0100_0000;
seg_data_en<=8'b11111111;
num1<=num1*20'd10+20'd4;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_5)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0101; //输入5
seg_dot_en<=8'b0100_0000;
seg_data_en<=8'b11111111;
num1<=num1*20'd10+20'd5;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_6)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0110; //输入6
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd6;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_10)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0001; //输入1
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd1;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_9)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0010; //输入2
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd2;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_8)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0011; //输入3
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd3;
dot_en=1'b0;
calstyle<=1'b1;end
else if(!key_out_12)begin
seg_data_7<=seg_data_8;
seg_data_8<=4'b0000; //输入0
seg_data_en<=8'b11111111;
seg_dot_en<=8'b0100_0000;
num1<=num1*20'd10+20'd0;
dot_en=1'b0;
calstyle<=1'b1;end
else seg_data_en<=8'b11111111;
end
end else
seg_data_en<=8'b11111111;
end
计算部分
代码介绍:
整体代码介绍:我将小数点运算和数字运算分开,数字运算就使用正常加减乘除运算的方式,而小数点运算采用分类讨论穷举的方式运算,即先判断第一个数带不带小数点再判断第二个数带不带小数点。
详细介绍通过代码注释方式展示
always@(posedge clk_in or negedge rst_n_in)begin
if(!rst_n_in)begin //复位给数据初始化
numresult<=17'd0;
chenfa<=34'd0;
dot_result<=8'b0000_0000;
end else if(jia&inputdone_0)begin //加法被激活
if(dog_en1)begin
if(dog_en2)begin //加法中两个小数点都被激活
numresult<=num1_cal+num2_cal;
dot_result<=8'b0100_0000;
end else begin //加法中第一个点被激活第二个点未被激活
numresult<=num2_cal*17'd10+num1_cal;
dot_result<=8'b0100_0000;
end
end else begin
if(dog_en2)begin //加法中第一个点未激活第二个点激活
numresult<=num1_cal*17'd10+num2_cal;
dot_result<=8'b0100_0000;
end else begin //加法中两个点均未被激活
numresult<=num1_cal+num2_cal;
dot_result<=8'b0000_0000;
end
end
end else if(jian&inputdone_0)begin //减法被激活
if(dog_en1)begin
if(dog_en2)begin //减法中两个小数点都被激活
numresult<=num2_cal-num1_cal;
dot_result<=8'b0100_0000;
end else begin //减法中第一个点被激活第二个点未被激活
numresult<=num2_cal*17'd10-num1_cal;
dot_result<=8'b0100_0000;
end
end else begin
if(dog_en2)begin //减法中第一个点未激活第二个点激活
numresult<=num1_cal*17'd10-num2_cal;
dot_result<=8'b0100_0000;
end else begin //减法中两个点均未被激活
numresult<=num1_cal-num2_cal;
dot_result<=8'b0000_0000;
end
end
end else if(chen&inputdone_0)begin //乘法被激活
if(dog_en1)begin
if(dog_en2)begin //乘法中两个小数点都被激活
chenfa=num2_cal*num1_cal;
dot_result=8'b0010_0000;
numresult=chenfa[16:0];
end else begin //乘法中第一个点被激活第二个点未被激活
chenfa=num2_cal*num1_cal;
dot_result=8'b0100_0000;
numresult=chenfa[16:0];
end
end else begin
if(dog_en2)begin //乘法中第一个点未激活第二个点激活
chenfa=num2_cal*num1_cal;
dot_result=8'b0100_0000;
numresult=chenfa[16:0];
end else begin //乘法中两个点均未被激活
chenfa=num2_cal*num1_cal;
dot_result=8'b0000_0000;
numresult=chenfa[16:0];
//numresult=17'b00000_0000_0000_0001;
end
end
end else if(chu&inputdone_0)begin //除法被激活
if(dog_en1)begin
if(dog_en2)begin //除法中两个小数点都被激活
numresult<=chushang;
dot_result=8'b0000_0000;
end else begin //除法中第一个点被激活第二个点未被激活
numresult<=chushang;
dot_result=8'b0100_0000;
end
end else begin
if(dog_en2)begin //除法中第一个点未激活第二个点激活
numresult<=chushang*17'd10;
dot_result=8'b0000_0000;
end else begin //除法中两个点均未被激活
numresult<=chushang;
dot_result=8'b0000_0000;
end
end
end
end
---------------------------------------------------------------------
显示前数据处理部分
代码介绍:通过反复调用除法模块,让被显示数反复除十求得被现实数的每一位数
division numresult_division1(.sys_clk(clk_in),.rst_n(rst_n_in),.A(numresult),.B(17'd10),.ready(ready_1),.shang(qvchugewei),.yushu(gewei),.valid(done_top1));
division numresult_division2(.sys_clk(clk_in),.rst_n(rst_n_in),.A(qvchugewei),.B(17'd10),.ready(ready_1),.shang(qvchushiwei),.yushu(shiwei),.valid(done_top2));
division numresult_division3(.sys_clk(clk_in),.rst_n(rst_n_in),.A(qvchushiwei),.B(17'd10),.ready(ready_1),.shang(qvchubaiwei),.yushu(baiwei),.valid(done_top3));
division numresult_division4(.sys_clk(clk_in),.rst_n(rst_n_in),.A(qvchubaiwei),.B(17'd10),.ready(ready_1),.shang(qvchuqianwei),.yushu(qianwei),.valid(done_top4));
division numresult_division5(.sys_clk(clk_in),.rst_n(rst_n_in),.A(qvchuqianwei),.B(17'd10),.ready(ready_1),.shang(qvchuwanwei),.yushu(wanwei),.valid(done_top5));
wire done_top6;
wire [16:0]chushang;
wire [16:0]chuyvshu;
division numresult_divisionchu(.sys_clk(clk_in),.rst_n(rst_n_in),.A(num1_cal),.B(num2_cal),.ready(ready_1),.shang(chushang),.yushu(chuyvshu),.valid(done_top6));
endmodule
---------------------------------------------------------------------
除法部分
代码介绍:
整体代码介绍:除法部分采用恢复余数法。如果余数大于除数,商上1余数减去除数,除数右移一位。如果余数小于除数,商上0,余数不变,除数右移一位。反复进行,最后得出结果。
详细代码介绍通过注释方式展示
module division(
input wire sys_clk, // 系统时钟
input wire rst_n, // 异步复位信号,低电平有效
input wire signed [16:0] A, // 被除数,17位有符号整数
input wire signed [16:0] B, // 除数,17位有符号整数
input wire ready, // 准备信号,当为1时,表示可以进行除法操作
output reg signed [16:0] shang, // 商,17位有符号整数
output reg signed [16:0] yushu, // 余数,17位有符号整数
output reg valid // 标志位,表示除法操作是否完成
);
reg work_flag; // 工作标志位,用于控制除法操作的各个阶段
reg [15:0] yushu_qian; // 临时余数寄存器,用于存储每一步的余数
reg [31:0] chushu; // 临时除数寄存器,用于存储每一步的除数
reg [4:0] cnt; // 计数器,用于控制除法操作的迭代次数
reg [15:0] shang_qian; // 临时商寄存器,用于存储每一步的商
// 第一个always块,用于控制工作标志位和计数器
always@(posedge sys_clk, negedge rst_n)
if(!rst_n) // 如果复位信号为低电平
work_flag <= 1'd0; // 将工作标志位清零
else if(cnt == 5'd16) // 如果计数器等于16
work_flag <= 1'd0; // 将工作标志位清零
else if(ready == 1'd1) // 如果准备信号为高电平
work_flag <= 1'd1; // 将工作标志位置1
// 第二个always块,用于计算每一步的余数
always@(posedge sys_clk, negedge rst_n)
if(!rst_n)
yushu_qian <= 16'd0; // 如果复位信号为低电平,将临时余数寄存器清零
else if(work_flag == 1'd0) // 如果工作标志位为0
yushu_qian <= (A[16] == 1'd1)?~A[15:0]+1'd1:A[15:0]; // 根据被除数的符号位进行取反加1或保持不变
else if(work_flag == 1'd1) // 如果工作标志位为1
begin
if(yushu_qian >= chushu) // 如果临时余数大于等于临时除数
yushu_qian <= yushu_qian - chushu; // 减去临时除数
else
yushu_qian <= yushu_qian; // 否则余数保持不变
end
// 第三个always块,用于计算每一步的除数
always@(posedge sys_clk, negedge rst_n)
if(!rst_n)
chushu <= 32'd0; // 如果复位信号为低电平,将临时除数寄存器清零
else if(work_flag == 1'd0) // 如果工作标志位为0
chushu <= {(B[16] == 1'd1)?~B[15:0]+1'd1:B[15:0],16'd0}; // 根据除数的符号位进行取反加1或保持不变,并扩展到32位
else if(work_flag == 1'd1) // 如果工作标志位为1
chushu <= chushu>>1; // 将临时除数右移一位
// 第四个always块,用于控制计数器
always@(posedge sys_clk, negedge rst_n)
if(!rst_n)
cnt <= 5'd0; // 如果复位信号为低电平,将计数器清零
else if(work_flag == 1'd0) // 如果工作标志位为0
cnt <= 5'd0; // 将计数器清零
else
cnt <= cnt + 5'd1; // 否则计数器加1
// 第五个always块,用于计算每一步的商
always@(posedge sys_clk, negedge rst_n)
if(!rst_n)
shang_qian <= 16'd0; // 如果复位信号为低电平,将临时商寄存器清零
else if(work_flag == 1'd0) // 如果工作标志位为0
shang_qian <= 16'd0; // 临时商寄存器保持为零
else if(work_flag == 1'd1) // 如果工作标志位为1
begin
if(yushu_qian >= chushu) // 如果临时余数大于等于临时除数
shang_qian[16-cnt] <= 1'd1; // 在相应的位置设置商为1
else
shang_qian[16-cnt] <= 1'd0; // 否则设置商为0
end
// 第六个always块,用于输出最终的商
always@(posedge sys_clk, negedge rst_n)
if(!rst_n)
shang <= 17'd0; // 如果复位信号为低电平,将商清零
else if(cnt == 5'd17) // 如果计数器等于17
shang <= shang_qian; // 将临时商寄存器的内容输出为最终的商
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
yushu <= 17'd0;
else if(cnt == 5'd17)
yushu <={1'd0,yushu_qian[15:0]};
//{1'd0,yushu_qian[15:0]}
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
valid <= 5'd0;
else if(cnt == 'd17)
valid <= 5'd1;
else
valid <= 5'd0;
endmodule
仿真波形图:
数码管显示模块:仿真代码:
`timescale 1ns/1ns
module Segment_scan
(
/*input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
input [3:0] seg_data_1, //SEG1 数码管要显示的数据
input [3:0] seg_data_2, //SEG2 数码管要显示的数据
input [3:0] seg_data_3, //SEG3 数码管要显示的数据
input [3:0] seg_data_4, //SEG4 数码管要显示的数据
input [3:0] seg_data_5, //SEG5 数码管要显示的数据
input [3:0] seg_data_6, //SEG6 数码管要显示的数据
input [3:0] seg_data_7,
input [3:0] seg_data_8,
input [7:0] seg_data_en, //各位数码管数据显示使能,[MSB~LSB]=[SEG6~SEG1]
input [7:0] seg_dot_en, //各位数码管小数点显示使能,[MSB~LSB]=[SEG6~SEG1]
output reg rclk_out, //74HC595的RCK管脚
output reg sclk_out, //74HC595的SCK管脚
output reg sdio_out //74HC595的SER管脚
*/
);
reg clk_in; //系统时钟
reg rst_n_in; //系统复位,低有效
reg [3:0] seg_data_1; //SEG1 数码管要显示的数据
reg [3:0] seg_data_2; //SEG2 数码管要显示的数据
reg [3:0] seg_data_3; //SEG3 数码管要显示的数据
reg [3:0] seg_data_4; //SEG4 数码管要显示的数据
reg [3:0] seg_data_5; //SEG5 数码管要显示的数据
reg [3:0] seg_data_6; //SEG6 数码管要显示的数据
reg [3:0] seg_data_7;
reg [3:0] seg_data_8;
reg [7:0] seg_data_en; //各位数码管数据显示使能,[MSB~LSB]=[SEG6~SEG1]
reg [7:0] seg_dot_en; //各位数码管小数点显示使能,[MSB~LSB]=[SEG6~SEG1]
reg rclk_out; //74HC595的RCK管脚
reg sclk_out; //74HC595的SCK管脚
reg sdio_out; //74HC595的SER管脚
reg cnt3=1'b0;
initial
begin
clk_in=1'b0;
seg_data_1=4'b0001;
seg_data_2=4'b0001;
seg_data_3=4'b0001;
seg_data_4=4'b0001;
seg_data_5=4'b0001;
seg_data_6=4'b0001;
seg_data_7=4'b0001;
seg_data_8=4'b0001;
seg_data_en=8'b00000001;
seg_dot_en=8'b00000001;
rst_n_in=1'b1;
#5 rst_n_in=1'b0;
#10 rst_n_in=1'b1;
end
always #10 clk_in=~clk_in;
always @(rst_n_in)
begin
if(cnt3==1'b0)
begin
cnt3=cnt3+1;
rst_n_in=1'b0;
end
end
parameter CLK_DIV_PERIOD = 600; //分频系数
localparam IDLE = 3'd0;
localparam MAIN = 3'd1;
localparam WRITE = 3'd2;
localparam LOW = 1'b0;
localparam HIGH = 1'b1;
//创建数码管的字库,字库数据依段码顺序有关
//这里字库数据[MSB~LSB]={DP,G,F,E,D,C,B,A}
reg[7:0] seg [15:0];
initial begin
seg[0] = 8'h3f; // 0
seg[1] = 8'h06; // 1
seg[2] = 8'h5b; // 2
seg[3] = 8'h4f; // 3
seg[4] = 8'h66; // 4
seg[5] = 8'h6d; // 5
seg[6] = 8'h7d; // 6
seg[7] = 8'h07; // 7
seg[8] = 8'h7f; // 8
seg[9] = 8'h6f; // 9
seg[10] = 8'h77; // A
seg[11] = 8'h7c; // b
seg[12] = 8'h39; // C
seg[13] = 8'h5e; // d
seg[14] = 8'h79; // E
seg[15] = 8'h71; // F
end
//计数器对系统时钟信号进行计数
reg[9:0] cnt=0;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
cnt <= 1'b0;
end else begin
if(cnt>=(CLK_DIV_PERIOD-1)) cnt <= 1'b0;
else cnt <= cnt + 1'b1;
end
end
//根据计数器计数的周期产生分频的脉冲信号
reg clk_div; //分频结果:clk_div
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
clk_div <= 1'b0;
end else begin
if(cnt==(CLK_DIV_PERIOD-1)) clk_div <= 1'b1;
else clk_div <= 1'b0;
end
end
//使用状态机完成数码管的扫描和74HC595时序的实现
reg [15:0] data_reg;
reg [2:0] cnt_main;
reg [5:0] cnt_write;
reg [2:0] state = IDLE;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin //复位状态下,各寄存器置初值
state <= IDLE;
cnt_main <= 3'd0;
cnt_write <= 6'd0;
sdio_out <= 1'b0;
sclk_out <= LOW;
rclk_out <= LOW;
end else begin
case(state)
IDLE:begin //IDLE作为第一个状态,相当于软复位
state <= MAIN;
cnt_main <= 3'd0;
cnt_write <= 6'd0;
sdio_out <= 1'b0;
sclk_out <= LOW;
rclk_out <= LOW;
end
MAIN:begin
if(cnt_main >= 3'd7) cnt_main <= 1'b0;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
//对6位数码管逐位扫描
3'd0: begin
state <= WRITE; //在配置完发给74HC595的数据同时跳转至WRITE状态,完成串行时序
data_reg <= {seg[seg_data_1]|(seg_dot_en[0]?8'h80:8'h00),seg_data_en[0]?8'hfe:8'hff};
//data_reg[15:8]为段选,data_reg[7:0]为位选
//seg[seg_data_1] 是根据端口的输入获取相应字库数据
//seg_dot_en[0]?8'h80:8'h00 是根据小数点显示使能信号 控制SEG1数码管的小数点DP段的电平
//seg_data_en[0]?8'hfe:8'hff 是根据数据显示使能信号 控制SEG1数码管的位选引脚的电平
end
3'd1: begin
state <= WRITE;
data_reg <= {seg[seg_data_2]|(seg_dot_en[1]?8'h80:8'h00),seg_data_en[1]?8'hfd:8'hff}; //253
end
3'd2: begin
state <= WRITE;
data_reg <= {seg[seg_data_3]|(seg_dot_en[2]?8'h80:8'h00),seg_data_en[2]?8'hfb:8'hff}; //
end
3'd3: begin
state <= WRITE;
data_reg <= {seg[seg_data_4]|(seg_dot_en[3]?8'h80:8'h00),seg_data_en[3]?8'hf7:8'hff};
end
3'd4: begin
state <= WRITE;
data_reg <= {seg[seg_data_5]|(seg_dot_en[4]?8'h80:8'h00),seg_data_en[4]?8'hef:8'hff};
end
3'd5: begin
state <= WRITE;
data_reg <= {seg[seg_data_6]|(seg_dot_en[5]?8'h80:8'h00),seg_data_en[5]?8'hdf:8'hff};
end
3'd6: begin
state <= WRITE;
data_reg <= {seg[seg_data_7]|(seg_dot_en[6]?8'h80:8'h00),seg_data_en[6]?8'hbf:8'hff};
end
3'd7: begin
state <= WRITE;
data_reg <= {seg[seg_data_8]|(seg_dot_en[7]?8'h80:8'h00),seg_data_en[7]?8'h7f:8'hff};
end
default: state <= IDLE;
endcase
end
WRITE:begin
if(clk_div) begin //74HC595的串行时钟有速度要求,需要按照分频后的节拍
if(cnt_write >= 6'd33) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
//74HC595是串行转并行的芯片,3路输入可产生8路输出,而且可以级联使用
//74HC595的时序实现,参考74HC595的芯片手册
6'd0: begin sclk_out <= LOW; sdio_out <= data_reg[15]; end //SCK下降沿时SER更新数据
6'd1: begin sclk_out <= HIGH; end //SCK上升沿时SER数据稳定
6'd2: begin sclk_out <= LOW; sdio_out <= data_reg[14]; end
6'd3: begin sclk_out <= HIGH; end
6'd4: begin sclk_out <= LOW; sdio_out <= data_reg[13]; end
6'd5: begin sclk_out <= HIGH; end
6'd6: begin sclk_out <= LOW; sdio_out <= data_reg[12]; end
6'd7: begin sclk_out <= HIGH; end
6'd8: begin sclk_out <= LOW; sdio_out <= data_reg[11]; end
6'd9: begin sclk_out <= HIGH; end
6'd10: begin sclk_out <= LOW; sdio_out <= data_reg[10]; end
6'd11: begin sclk_out <= HIGH; end
6'd12: begin sclk_out <= LOW; sdio_out <= data_reg[9]; end
6'd13: begin sclk_out <= HIGH; end
6'd14: begin sclk_out <= LOW; sdio_out <= data_reg[8]; end
6'd15: begin sclk_out <= HIGH; end
6'd16: begin sclk_out <= LOW; sdio_out <= data_reg[7]; end
6'd17: begin sclk_out <= HIGH; end
6'd18: begin sclk_out <= LOW; sdio_out <= data_reg[6]; end
6'd19: begin sclk_out <= HIGH; end
6'd20: begin sclk_out <= LOW; sdio_out <= data_reg[5]; end
6'd21: begin sclk_out <= HIGH; end
6'd22: begin sclk_out <= LOW; sdio_out <= data_reg[4]; end
6'd23: begin sclk_out <= HIGH; end
6'd24: begin sclk_out <= LOW; sdio_out <= data_reg[3]; end
6'd25: begin sclk_out <= HIGH; end
6'd26: begin sclk_out <= LOW; sdio_out <= data_reg[2]; end
6'd27: begin sclk_out <= HIGH; end
6'd28: begin sclk_out <= LOW; sdio_out <= data_reg[1]; end
6'd29: begin sclk_out <= HIGH; end
6'd30: begin sclk_out <= LOW; sdio_out <= data_reg[0]; end
6'd31: begin sclk_out <= HIGH; end
6'd32: begin rclk_out <= HIGH; end //当16位数据传送完成后RCK拉高,输出生效
6'd33: begin rclk_out <= LOW; state <= MAIN; end
default: state <= IDLE;
endcase
end else begin
sclk_out <= sclk_out;
sdio_out <= sdio_out;
rclk_out <= rclk_out;
cnt_write <= cnt_write;
state <= state;
end
end
default: state <= IDLE;
endcase
end
end
endmodule
矩阵键盘输入模块:
仿真代码:
`timescale 1ns/1ns
module Array_KeyBoard #
(
parameter NUM_FOR_200HZ = 60000 //定义计数器cnt的计数范围,例化时可更改
)
(
/*
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
input [3:0] col, //矩阵按键列接口
output reg [3:0] row, //矩阵按键行接口
output reg [15:0] key_out //消抖后的信号
*/
);
reg clk_in; //系统时钟
reg rst_n_in; //系统复位,低有效
reg [3:0] col; //矩阵按键列接口
reg [3:0] row; //矩阵按键行接口
reg [15:0] key_out; //消抖后的信号
reg cnt3;
initial
begin
clk_in=1'b0;
rst_n_in=1'b1;
cnt3=1'b0;
col=4'b1111;
#5 rst_n_in=1'b0;
#5 rst_n_in=1'b1;
end
always #10 clk_in=~clk_in;
always #2000 col=4'b1110;
always #2500 col=4'b1110;
always @(rst_n_in)
begin
if(cnt3==1'b0)
begin
cnt3=cnt3+1;
rst_n_in=1'b0;
end
end
/*
因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态
在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样
周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间
对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理
*/
localparam STATE0 = 2'b00;
localparam STATE1 = 2'b01;
localparam STATE2 = 2'b10;
localparam STATE3 = 2'b11;
//计数器计数分频实现5ms周期信号clk_200hz
reg [15:0] cnt2;
reg clk_200hz;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin //复位时计数器cnt2清零,clk_200hz信号起始电平为低电平
cnt2 <= 16'd0;
clk_200hz <= 1'b0;
end else begin
if(cnt2 >= ((NUM_FOR_200HZ>>1) - 1)) begin //数字逻辑中右移1位相当于除2
cnt2 <= 16'd0;
clk_200hz <= ~clk_200hz; //clk_200hz信号取反
end else begin
cnt2 <= cnt2 + 1'b1;
clk_200hz <= clk_200hz;
end
end
end
reg [1:0] c_state;
//状态机根据clk_200hz信号在4个状态间循环,每个状态对矩阵按键的行接口单行有效
always@(posedge clk_200hz or negedge rst_n_in) begin
if(!rst_n_in) begin
c_state <= STATE0;
row <= 4'b1110;
end else begin
case(c_state)
STATE0: begin c_state <= STATE1; row <= 4'b1101; end //状态c_state跳转及对应状态下矩阵按键的row输出
STATE1: begin c_state <= STATE2; row <= 4'b1011; end
STATE2: begin c_state <= STATE3; row <= 4'b0111; end
STATE3: begin c_state <= STATE0; row <= 4'b1110; end
default:begin c_state <= STATE0; row <= 4'b1110; end
endcase
end
end
//因为每个状态中单行有效,通过对列接口的电平状态采样得到对应4个按键的状态,依次循环
always@(negedge clk_200hz or negedge rst_n_in) begin
if(!rst_n_in) begin
key_out <= 16'hffff;
end else begin
case(c_state)
STATE0:key_out[3:0] <= col; //采集当前状态的列数据赋值给对应的寄存器位
STATE1:key_out[7:4] <= col;
STATE2:key_out[11:8] <= col;
STATE3:key_out[15:12] <= col;
default:key_out <= 16'hffff;
endcase
end
end
endmodule
除法部分:
仿真代码:
`timescale 1ns/1ns
module tb_2();
reg signed [16:0] A;
reg signed [16:0] B;
reg sys_clk;
reg rst_n;
reg ready;
division division_inst(
.sys_clk (sys_clk),
.rst_n (rst_n),
.A (A ),
.B (B ),
.ready (ready)
);
always #10 sys_clk = ~sys_clk;
initial begin
sys_clk = 1'd0;
rst_n <= 1'd0;
A <= 16'd0;
B <= 16'd0;
ready <= 1'd0;
#10
rst_n <= 1'd1;
#100;
A <= 17'd27;
B <= 17'd10;
ready <= 1'd1;
#20
ready <= 1'd0;
#400;
A <= 17'd33;
B <= 17'd7;
ready <= 1'd1;
#20
ready <= 1'd0;
#400;
A <= 17'd39;
B <= 17'd2;
ready <= 1'd1;
#20
ready <= 1'd0;
#400;
A <= 17'd17;
B <= 17'd3;
ready <= 1'd1;
#20
ready <= 1'd0;
end
endmodule
module division(
input wire sys_clk,
input wire rst_n ,
input wire signed [16:0] A,
input wire signed [16:0] B,
input wire ready,
output reg signed [16:0] shang,
output reg signed [16:0] yushu,
output reg valid
);
reg work_flag;
reg [15:0] yushu_qian;
reg [31:0] chushu;
reg [4:0] cnt;
reg [15:0] shang_qian;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
work_flag <= 1'd0;
else if(cnt == 5'd16)
work_flag <= 1'd0;
else if(ready == 1'd1)
work_flag <= 1'd1;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
yushu_qian <= 16'd0;
else if(work_flag == 1'd0)
yushu_qian <= (A[16] == 1'd1)?~A[15:0]+1'd1:A[15:0];
else if(work_flag == 1'd1)
begin
if(yushu_qian >= chushu)
yushu_qian <= yushu_qian - chushu;
else
yushu_qian <= yushu_qian;
end
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
chushu <= 32'd0;
else if(work_flag == 1'd0)
chushu <= {(B[16] == 1'd1)?~B[15:0]+1'd1:B[15:0],16'd0};
else if(work_flag == 1'd1)
chushu <= chushu>>1;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
cnt <= 5'd0;
else if(work_flag == 1'd0)
cnt <= 5'd0;
else
cnt <= cnt + 5'd1;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
shang_qian <= 16'd0;
else if(work_flag == 1'd0)
shang_qian <= 16'd0;
else if(work_flag == 1'd1)
begin
if(yushu_qian >= chushu)
shang_qian[16-cnt] <= 1'd1;
else
shang_qian[16-cnt] <= 1'd0;
end
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
shang <= 17'd0;
else if(cnt == 5'd17)
shang <=shang_qian; //(A[16]^B[16] == 1'd1)?{1'd1,~shang_qian+1'd1}:
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
yushu <= 17'd0;
else if(cnt == 5'd17)
yushu <={1'd0,yushu_qian[15:0]};
//{1'd0,yushu_qian[15:0]}
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
valid <= 5'd0;
else if(cnt == 'd17)
valid <= 5'd1;
else
valid <= 5'd0;
endmodule
FPGA的资源利用说明:
成果图片展示:
遇到的主要问题:
按键输入后怎样把按键的输入信息转化为数据信号,因为每一次按键会改变下一次按键的可选项,完全归纳法种类很多,想起来有些麻烦。
小数点处理方法,怎样让小数点参与到运算当中是难题,后面想到将数字运算和小数点运算分开,对小数点运算采取分类讨论完全归纳的方法。
除法运算处理方法,最终借鉴网络资料决定采用恢复余数法。
未来的计划和建议:
该项目已成功实现了简易计算器的功能,达到了预期指标。未来可以通过控制lcd屏显示数据,可以更好的实现数据显示。