2024寒假在家一起练-基于小脚丫FPGA套件STEP BaseBoard V4.0实现两位十进制加、减、乘、除计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,实现了两位十进制加、减、乘、除计算器的设计,它的主要功能为:实现两位十进制数的加减乘除运算,并将结果于数码管上显示。。
标签
FPGA
2024寒假在家一起练
一痕辰
更新2024-04-01
北京理工大学
261


一、项目需求


实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,4×4键盘按键分配如下图所示。

 图片.png

基本要求:

运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

扩展要求:

使用TFTLCD来做显示,自行设计显示界面,代替数码管显示输入的参数、运算符以及计算后的结果。

结果展示:

下面是15加减乘除3的结果演示(由于设计存在不足,需要按两下等号才可以展示结果,第一次显示内置初始值0000

bf874c4c963a48604137fe58b9bc05c.jpg16a0b2b8a6f1575eae6b541017430f1.jpg107c8055eeac1f9d9a7943e6b6ac7ee.jpg

f15b48c570c58b68fcd85892a3b1ba9.jpg174582328157da8a52b53164b8634e9.jpg823a83961b469f5c6a43cce8e878e6e.jpg7765abe454968a071f544190d001c8e.jpg

额外功能:对结果进行连续运算(这里展示对结果5进行连续运算5+3=8)原结果5不动-按下加号-输入3-按下两次等号出现结果8。

7765abe454968a071f544190d001c8e.jpg475ad8bdfaae9bd0bd3ea6ca8cbb086.jpg3946c153766fbe994fd162d113be9b1.jpgdf8cb02c6d96f73e7bf87740f8684db.jpg


二、需求分析

对于该项目需要在FPGA上显示,分析该项目对于实现过程的需求有:

1. 了解Verilog与数电原理:

本项目的开发过程全部通过Verilog编程语言与数字电路时序逻辑、组合逻辑实现。首先需要了解基础Verilog语言的编写方式。并且掌握数字电路原理,分析数据的逻辑关系,通过Verilog语言实现目的。

2. 键盘输入:

计算器两个操作数的获取需要通过V4.0扩展板上的矩阵键盘实现,获取矩阵键盘的输入则至关重要。获取输入需要了解矩阵键盘的工作原理并思考通过何种方式将键盘输入结果保存下来并输出给其他模块。

3. 数码管显示:

了解V4.0板数码管的显示逻辑,了解该扩展板硬件上驱动数码管的方式,应用Verilog语言实现对数码管的驱动。

4. 计算:

计算模块是整个项目中最为核心的模块,计算模块通过接收矩阵键盘的按键输入,为数码管显示模块提供相应的输出。由于在本设计中数码管的显示数据完全由计算模块提供,所以计算模块需要十分严谨且复杂的考虑。在完成数值计算的同时需要对输出给数码管的显示数据进行操作以符合项目对计算器显示的要求。

5. BCD模块

Verilog语言中所有数值运算的过程全部是由二进制数完成。而对于本项目,所有的预期数据输入与输出都是建立在操作者十进制的思维之上。而十进制无法通过固定位数的二进制数字完全表达,所以需要设计一个BCD模块对二进制操作数进行处理以符合我们对于十进制输入、输出的预期。



三、实现的方式

本项目我的实现通过五个模块的合作实现:

1. 顶层模块:

顶层模块在Verilog中包含所有与硬件设计相关的逻辑和端口定义。顶层模块在Verilog项目中的作用非常关键,它作为设计的入口点,与外部世界进行交互,并将整个设计的各个部分组合在一起。顶层文件声明了设计的输入输出接口,并以此配置引脚。同时声明wire型中间量以实现不同子模块之间的数据交互,并调用子模块实现功能。

2. 数码管驱动模块

本项目的V4.0扩展板的八位数码管通过两片74HC590级联驱动。两片74HC590寄存器拥有同一时钟与复位信号,第一片接收来自核心板的使能,八位输出控制八位数码管哪位亮起的位选。同时接收下八位使能信号的同时将之前的信号输出给第二片74HC590,输出控制该数码管的段选以实现数字的展示。

数码管的段选信息通过模块内设置的字库进行输出,即将不同的使能情况与对应数字的段选信息进行绑定。

同时,由于两位十进制数之间的四则运算最多存在四位数,所以在结果展示时提供后四位的位选使能。

图片.png

3. 键盘按键读取模块 

图片.png

以上为4×4矩阵按键的硬件电路图,可以看到4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4)。该项目中,为了减少I/O口的占用,使用了扫描的方式对矩阵的按键进行确定,一共分为4个时刻,分别对应4根行线中的一根拉低,4个时刻依次循环,这样就完成了矩阵按键的全部扫描检测,我们在程序中以这4个时刻对应状态机的4个状态。 至于循环的周期,根据我们基础教程里可知,按键抖动的不稳定时间在10ms以内,所以对同一个按键采样的周期大于10ms,这同样取20ms时间。20ms时间对应4个状态,每5ms进行一次状态转换。

图片.png 

具体的实现过程如上图所示,通过不断对行输出检测使能,检测列的高低电平信息,最终将16位键盘按键的行列存于一个16位二进制寄存器中。

同时由于扫描是通过时钟信号驱动的,也就是检测的键盘输出信息是时刻改变的(松开按键键盘按键信息便恢复为无按键状态)。为了方便将按键信息交给其他模块处理,需要对键盘按键信息在按键发生后进行锁存,以保证在按键动作结束后仍然知道之前按了哪个键。

4. 计算模块

我们本次任务是进行两个十进制数之间的加减乘除运算,所以设置了两个寄存器以储存这两个十进制数的信息。当按键按下后,计算模块读取键盘的锁存信息,并存入寄存器数组a中。读到第二个按键按下后,将寄存器数组a向左移位,将新按键数存入a的低四位中。(实际的操作是将第一个数先存入数码管显示数组seg_data中,在按下计算键后存入寄存器a中,同时seg_data置零)

在第一个数输入完毕、按下计算符按键后,计算符标志位记录按下的计算符是什么。在以和上面相同的方式记录完成第二个数后,按下等于键,通过计算符的标识完成相应的计算。

由于数字是通过四位二进制存储一位十进制的方法进行的,这会导致存储出来的二进制数在计算机看来的实际的大小与我们想要表达的不同(如十进制表示“预想值17”存在寄存器里是“实际值0001 0111”,而这个二进制数的实际值大小是23)。这便需要我们将存进来的二进制数进行处理以使其实际值大小等于我们的预想值。

在处理完实际值与预想值的统一之后,便可以进行两个数值之间的运算了。而问题在于计算的结果仍然是以二进制表示其大小,我们在数码管上表示却需要每四位二进制表示一位十进制(如表示10+7的结果17,计算机内的存储方式为“0001 0001”,但是在数码管中逐位展示时我们期望的结果是“0001 0111”)。我们则需要对结果的“实际值”转换为逐位对应的“预想值”。而这个转换操作可以通过一个BCD模块完成。

5. BCD模块

实现将一个二进制数字在十进制表达方式中的逐位数字,以四位存储一个的方式输出(如我想表示“实际值9801”,需要输出为“1001 1000 0000 0001”)。以便于传输给数码管逐位展示输出。

6.LCD展示模块*

目前还未实现。将结果转化为符合LCD屏接收格式的使能信号,实现计算器结果展示。

图片.png

四、功能框图

图片.png图片.png


五、代码及说明

下面我对该项目任务中关键的代码部分进行展示与说明

MAIN:begin
if(cnt_main >= 3'd7) cnt_main <= 1'b0;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
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};
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



以上是数码管驱动模块的主要代码部分,该代码为我通过电子森林提供的六位数码管驱动例程改造为八位数码管得来的,由于两个74HC595D寄存器级联只有一个信息使能输入,第二片所需的段使能是由第一片进位上来的。所以需要明确的是实际的sdio_out信号的组成是低位存位选信息,高位存段选信息从而实现一个数字的显示。

always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
seg_data <= 32'h0;
seg_data_en <= 8'h0;
flag <= 3'b000;
equal <= 1'b0;
end else begin
case(key_pulse) //key_pulse脉宽等于clk_in的周期
16'h1000: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0000}; end //0
16'h0400: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0001}; end //1
16'h0200: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0010}; end //2
16'h0100: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0011}; end //3
16'h0010: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0100}; end //4
16'h0020: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0101}; end //5
16'h0040: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0110}; end //6
16'h0001: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b0111}; end //7
16'h0002: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b1000}; end //8
16'h0004: begin seg_data_en <= {1'b1,seg_data_en[7:1]}; seg_data <= {seg_data[27:0],4'b1001}; end //9
16'h8000: begin flag <= 3'b001; seg_data_en <= 8'h0; a = seg_data; seg_data <= 32'h0; end //加 flag=1
16'h0800: begin flag <= 3'b010; seg_data_en <= 8'h0; a = seg_data; seg_data <= 32'h0; end //减 flag=2
16'h0080: begin flag <= 3'b011; seg_data_en <= 8'h0; a = seg_data; seg_data <= 32'h0; end //乘 flag=3
16'h0008: begin flag <= 3'b100; seg_data_en <= 8'h0; a = seg_data; seg_data <= 32'h0; end //除 flag=4
16'h4000: begin equal<= 1'b1; end //等号
default: seg_data <= seg_data; //无按键按下时保持
endcase

if(equal==1'b1) begin
case(flag)
3'b001: begin
a={28'h0,a[7:4]}*32'd10+{28'h0,a[3:0]};real_data={28'h0,seg_data[7:4]}*32'd10+{28'h0,seg_data[3:0]};result = a+real_data;
result_last_four=result[15:0];result={12'h0,result_wan,result_qian,result_bai,result_shi,result_ge};
a <= 32'h0;
seg_data <= result;
seg_data_en<=8'hf0;
flag <= 3'b011;
equal <= 1'b0;
end
3'b010: begin
a={28'h0,a[7:4]}*32'd10+{28'h0,a[3:0]};real_data={28'h0,seg_data[7:4]}*32'd10+{28'h0,seg_data[3:0]};result = a-real_data;
result_last_four=result[15:0];result={12'h0,result_wan,result_qian,result_bai,result_shi,result_ge};
a <= 32'h0;
seg_data <= result;
seg_data_en<=8'hf0;
flag <= 3'b011;
equal <= 1'b0;
end
3'b011: begin
a={28'h0,a[7:4]}*32'd10+{28'h0,a[3:0]};real_data={28'h0,seg_data[7:4]}*32'd10+{28'h0,seg_data[3:0]};result = a*real_data;
result_last_four=result[15:0];result={12'h0,result_wan,result_qian,result_bai,result_shi,result_ge};
a <= 32'h0;
seg_data <= result;
seg_data_en<=8'hf0;
flag <= 3'b011;
equal <= 1'b0;
end
3'b100: begin
a={28'h0,a[7:4]}*32'd10+{28'h0,a[3:0]};real_data={28'h0,seg_data[7:4]}*32'd10+{28'h0,seg_data[3:0]};result = a/real_data;
result_last_four=result[15:0];result={12'h0,result_wan,result_qian,result_bai,result_shi,result_ge};
a <= 32'h0;
seg_data <= result;
seg_data_en<=8'hf0;
flag <= 3'b011;
equal <= 1'b0;
end
default: begin equal <= 1'b0; end
endcase

end
end
end

以上是我对核心计算部分的代码展示,这里实现了当键盘输入时,位使能增加一位、数字显示左移一位,同时将输入数字存入显示seg_data的最低四位中。对于最后计算结果展示通过bcd模块输出最终的十进制表示结果。



六、仿真与实现效果

由于是初学者且关注实际上板效果,每个模块的仿真我使用直接导入板子的方式进行验证。其中最为关键的部分为数码管以及矩阵键盘的驱动。对此我设计了一个键盘检测功能以验证数码管和键盘效果。同时确定按下不同键时矩阵键盘的输出结果。(由于数码管是依次扫描输出,照片中可能显示不全)按下K2 K11时键盘输出为dfbf。

86b507e3031aef6bdb8172aa5fe9f3c.jpg



七、FPGA的资源利用说明

图片.png

除部分设计冗杂与内存预设生疏,由于在该项目中所有关于数据显示的寄存器全部使用的是32位寄存器,以期实现八位数字的加减乘除运算,这些因素导致FPGA资源占用较多。在未来多加学习后期望能够改善资源占用情况。



八、功能、不足、改善空间与建议

功能:

在能实现基本加减乘除运算功能的基础上,可以实现对结果的继续操作以实现连续计算,同时在计算结果后可以再次按下等号进行复位以重新计算。

不足:

本次项目中,由于对相关内容较为生疏以及开始时间较晚导致时间较少,本次实现中存在一点不足:在两个操作数输入完成后,第一次按下等号时显示的是初始预设值“0000”,第二次按下等号才会在数码管上显示结果。我对于这个问题的出现进行了以下分析:在计算出结果的真实值后,需要将二进制的真实值呈递给BCD模块令其返回符合十进制显示格式的数值。而BCD模块的实现需要经历多个时钟周期,这导致在处理完等号的当前时钟周期并没有及时完成十进制显示数值的更新,从而导致数码管显示初始值0000。在后续的改善中预想对计算模块的时钟信号进行分频变成低频率时钟信号,以实现BCD模块处理结束后再进行数码管显示操作。同时由于个人原因本人项目实施时间较少,没有完成在电子屏上对计算器功能的展示也是本次项目设计的一大遗憾。

改进空间:

由于寄存器预留空间较大,且实现方法统一,后续可在此基础上完成至多八位的加减乘除计算(乘计算可能导致数据溢出)。同时,数码管显示模块对小数点的使能也进行了定义,所以后续有望实现小数计算操作。了解了LCD屏工作原理,后续可以将数码管显示内容通过LCD屏进行展示。

建议:

1.WEBIDE在代码改动较小时可能出现“运行旧代码”的情况。导致修改代码时的困难与迷惑。

2.WEBIDE中FPGA映射在资源利用超出时,无法进行正确的映射,却没有明确的报错提醒,这可能导致初学者无法知晓错误之处。对于不了解FPGA开发的同学,这可能也会对其造成困扰。

附件下载
top.v
led.v
keyboard.v
jisuan.v
bcd.v
implement.jed
项目文件
testkeyboard.jed
键盘数码管测试文件
团队介绍
于翔泊 北京理工大学
团队成员
一痕辰
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号