2024年寒假练 - 基于小脚丫FPGA套件STEP BaseBoard V4.0实现两位十进制加、减、乘、除计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,verilog语言,实现了实现两位十进制加、减、乘、除计算器的设计,它的主要功能为:实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制。
标签
FPGA
ljz123
更新2024-04-01
194

基于小脚丫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的资源利用说明:

image.png

成果图片展示:

遇到的主要问题:

  按键输入后怎样把按键的输入信息转化为数据信号,因为每一次按键会改变下一次按键的可选项,完全归纳法种类很多,想起来有些麻烦。

  小数点处理方法,怎样让小数点参与到运算当中是难题,后面想到将数字运算和小数点运算分开,对小数点运算采取分类讨论完全归纳的方法。

  除法运算处理方法,最终借鉴网络资料决定采用恢复余数法。

未来的计划和建议:

  该项目已成功实现了简易计算器的功能,达到了预期指标。未来可以通过控制lcd屏显示数据,可以更好的实现数据显示。

附件下载
xiugai.v
团队介绍
个人创造
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号