2024年寒假练 - 基于小脚丫FPGA套件STEP Baseboard V4.0实现两位十进制加、减、乘、除计算器
该项目使用了Lattice Diamond软件,Verilog语言,实现了一个十进制计算器的设计,它的主要功能为:1.两位十进制数加、减、乘、除 2.运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。3.输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。。
标签
FPGA
verilog
计算器
参加活动/培训
2024寒假一起练
wapoci
更新2024-04-01
西安交通大学
405

一、题目要求介绍

实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制。

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

二、项目介绍

1.项目功能

1)运算数显示

待运算数通过矩阵键盘输入,如下图,先输入数字2,后输入数字8,2跳变到左侧数码管,原先2的位置被8取代。

在按下运算符号(加减乘除)之前,数字可被不断替换。直至按下加减乘除任一运算符号后,第一个数不可输入替换,可对第二个数进行输入。小数点的输入同理,在按下运算符号前,按一次显示,再按一次小数点消失。如下图,同时显示了待运算的两个数,每个数左侧为十位,右侧为个位。



a12a47c3dfe11e314221fd34c5dcfcc.jpg

5637b8030043140a6941deee6b911bf.jpg

2)四则运算

(1)加法

当执行加法运算时,按下等于键后,显示运算结果,可实现进位操作。

6e55471a9ebe0532d9bdb368a93be0f.jpg

f9eb4a45e696a31134508b8d8de11b6.jpg


(2)减法

当执行减法运算时,按下等于键后,显示运算结果,可实现退位操作。

d82f2afd188331f0bc4cbeed3b13d6d.jpg

9cf16ba1c687118ce5a2f3f64146f8f.jpg

(3)乘法

当执行减法运算时,按下等于键后,显示运算结果,可实现退位操作。

dd919bde6731c5bb68b5119881636dc.jpg

fa0177367d11182939f4fda4429fc50.jpg

(4)除法

当执行除法运算时,按下等于键后,显示运算结果,左侧数为商,右侧数为余数。

d8213de04c2b82cb8bbf2d37587980e.jpg

815e92fae13b3d02c96696cd5d53f59.jpg

2.项目设计

1)设计思路

(1)本项目实现的计算器通过按键输入数字和运算符号,可结合矩阵键盘例程实现。

(2)在输入端,需要判断输入键值的类型(数字或运算符号)。

(3)同时,等号较为特殊,输入后要显示运算结果,因此需要单独判断。

(4)因显示结果需要在拓展板上的数码管显示,需要用到74HC595进行控制。



2)系统流程图

按照设计思路的画出的系统流程图如下图所示:

中断流程图.jpg

3)硬件介绍

FPGA核心板:基于Lattice MXO2的小脚丫FPGA核心板 - Type C接口

小脚丫FPGA团队最新推出的FPGA核心模块,无需下载安装软件,可以直接在浏览器里编程,一个USB Type C端口支持供电、FPGA的配置以及UART通信,管脚完全兼容传统的小脚丫FPGA模块。


拓展板:小脚丫FPGA套件STEP BaseBoard V4.0

搭配任何一款小脚丫FPGA核心模块,针对高校数字电路、系统教学实验以及EDA实验而开发的综合性实验平台,拥有丰富的外设、接口。

4)RTL图



rtl.png


5)FPGA资源占用报告

  • 寄存器数量:共251个,占总数4635个的5%
    • PFU寄存器:共251个,占总数4320个的6%
    • PIO寄存器:共0个,占总数315个的0%
  • SLICE数量:共1689个,占总数2160个的78%
    • 作为逻辑/ROM的SLICE:共1689个,占总数2160个的78%
    • 作为RAM的SLICE:共0个,占总数1620个的0%
    • 作为进位链的SLICE:共765个,占总数2160个的35%
  • LUT4数量:共3358个,占总数4320个的78%
    • 用作逻辑LUT的数量:1828个
    • 用作分布式RAM的数量:0个
    • 用作波纹逻辑的数量:1530个
    • 用作移位寄存器的数量:0个
  • 使用的PIO站点数量:13 + 4(JTAG)个,占总数105个的16%
  • 块RAM数量:共0个,占总数10个的0%
  • GSR数量:共1个,占总数1个的100%
  • EFB使用情况:未使用
  • JTAG使用情况:未使用
  • 回读使用情况:未使用
  • 振荡器使用情况:未使用
  • 启动电路使用情况:未使用
  • POR(上电复位)情况:已启用
  • Bandgap(带隙基准)情况:已启用
  • 电源控制器数量:共0个,占总数1个的0%
  • 动态Bank控制器(BCINRD)数量:共0个,占总数6个的0%
  • 动态Bank控制器(BCLVDSO)数量:共0个,占总数1个的0%
  • DCCA数量:共0个,占总数8个的0%
  • DCMA数量:共0个,占总数2个的0%
  • PLL(锁相环)数量:共0个,占总数2个的0%
  • DQSDLL数量:共0个,占总数2个的0%
  • CLKDIVC数量:共0个,占总数4个的0%
  • ECLKSYNCA数量:共0个,占总数4个的0%
  • ECLKBRIDGECS数量:共0个,占总数2个的0%


Design Summary
Number of registers: 251 out of 4635 (5%)
PFU registers: 251 out of 4320 (6%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 1689 out of 2160 (78%)
SLICEs as Logic/ROM: 1689 out of 2160 (78%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 765 out of 2160 (35%)
Number of LUT4s: 3358 out of 4320 (78%)
Number used as logic LUTs: 1828
Number used as distributed RAM: 0
Number used as ripple logic: 1530
Number used as shift registers: 0
Number of PIO sites used: 13 + 4(JTAG) out of 105 (16%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 0 out of 2 (0%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)


三、主要代码片段介绍

1)键值输入存储

矩阵键盘的按键防抖主要参照了例程type_system里的array_keyboard.v,其输出为key_pulse,在此不再赘述。

等于用case语句对输入的防抖处理信号key_pulse进行判断,对tem_seg_data寄存器进行赋值,不同的键值对应数字0~9、加、减、乘、除、小数点、等于。考虑到有重复输入按键的情况(例如输入数字22),当无按键输入时,也需给tem_seg_data寄存器进行赋值。最后的输出seg_data为10位数,从高到低分别为等于、加、减、乘、除、小数点、四位数字。

always @(posedge clk or negedge rst_n) begin  
if (!rst_n) begin
tem_seg_data <= 10'h00;

end
else begin

case(key_pulse)
16'h0001: tem_seg_data <= 10'h07;
16'h0002: tem_seg_data <= 10'h08;
16'h0004: tem_seg_data <= 10'h09;
16'h0010: tem_seg_data <= 10'h04;
16'h0020: tem_seg_data <= 10'h05;
16'h0040: tem_seg_data <= 10'h06;
16'h0100: tem_seg_data <= 10'h01;
16'h0200: tem_seg_data <= 10'h02;
16'h0400: tem_seg_data <= 10'h03;
16'h1000: tem_seg_data <= 10'h00;

16'h2000: tem_seg_data <= 10'b0000010000;//小数点

16'h0008: tem_seg_data <= 10'b0100000000; //加
16'h0080: tem_seg_data <= 10'b0010000000; //减
16'h0800: tem_seg_data <= 10'b0001000000; //乘
16'h8000: tem_seg_data <= 10'b0000100000; //除

16'h4000: tem_seg_data <= 10'b1000000000; //等于

default: tem_seg_data <= 10'b1111111111; // 无按键按下
endcase
end
end

always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
seg_data <= 10'b1111111111;
end
else begin
seg_data <= tem_seg_data ;

end
end


2)输入键值分类、处理

该部分代码在oper_char模块中。该模块中seg_data_unproduced即为上面模块的输出seg_data,本模块的输出包含处理后的数据seg_data_produced,为一个十位数。

下面的代码实现了移位操作,即新输入的数字将显示在个位,原来输入的数字显示在十位。


		else if(seg_data_unproduced !=10'b1111111111)begin//有新按键输入
if(seg_data_unproduced[9:4]==0) begin //输入的是数字

if(seg_data_produced[7:0]==8'b0 )begin//个位和十位都没输入
seg_data_produced[3:0] <= seg_data_unproduced[3:0];
end

else begin
seg_data_produced[7:0] <= {seg_data_produced[3:0],seg_data_unproduced[3:0]};//移位
end
end


下面的代码实现了小数点的显示,先可输入左侧的小数点,待运算符号按下后(oper_flag标志信号被置1),才可输入右侧的小数点。

		else if(seg_data_unproduced[9:4]!=0)begin//输入的是符号
if(seg_data_unproduced[9:4]==6'b000001 &&oper_flag==0)begin//第一次按小数点
dot_en[7] = ~dot_en[7];
end

else if(seg_data_unproduced[9:4]==6'b000001 &&oper_flag!=0)begin//第二次小数点
dot_en[4] = ~dot_en[4];
end

下面的代码是对输入符号的判断及处理,因等于号较为特殊,因此单独输出一个标志信号equal_flag。加减乘除用输出信号seg_data_oper来区分,逻辑相同,因此只示出加号的代码部分。

			else if(seg_data_unproduced[9:5]==5'b10000)begin//等于
equal_flag<=1'b1;

end

else if(seg_data_unproduced[9:5]==5'b01000)begin//加
seg_data_oper<= 4'b1000;

oper_flag <=1'b1;
seg_data_produced[7:0]<=8'b0;
end


3)数字四则运算

在num_out模块中,实现了对于将进行运算的两位数字的存储和四则运算。

下面的代码实现了对两个十位数的存储,当oper_flag标志信号被置1,即已输入运算符号时,可输入并存储第二个十位数。

always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
seg_data_cal_store<= 15'h0;
end

else if(oper_flag==0)begin//未输入运算符
seg_data_cal_store[15:8]<=seg_data; //先显示左边的数字
end

else if(oper_flag!=0)begin//已输入运算符
seg_data_cal_store[7:0]<=seg_data;

end

end


下面的代码是加法运算,由于运算的数字不是作为整体存储在寄存器中,而是十位存储在[7:4],个位存储在[3:0],分开存储的。因此涉及到进位时,不能简单相加,需做进一步处理。

		if(seg_data_oper== 4'b1000)begin//加法

if(seg_data_cal_store[3:0]+seg_data_cal_store[11:8]<4'd10)begin
seg_data_cal[7:0] <= seg_data_cal_store[7:0] + seg_data_cal_store[15:8];
end

else begin
seg_data_cal[3:0] <= seg_data_cal_store[3:0] + seg_data_cal_store[11:8] -4'b1010;
seg_data_cal[7:4] <= seg_data_cal_store[7:4] + seg_data_cal_store[15:12]+4'b0001;
end

seg_data_cal[16]<= 1'b0;//加法结果为正
end


下面的代码是减法运算,同加法的思路,在涉及到退位时,需做进一步处理。

		else if(seg_data_oper==4'b0100)begin//减法
if(seg_data_cal_store[15:8]<seg_data_cal_store[7:0]) begin//结果为负

if(seg_data_cal_store[3:0]<seg_data_cal_store[11:8])begin//要借位
seg_data_cal[3:0] <= seg_data_cal_store[3:0] - seg_data_cal_store[11:8] +4'b1010;
seg_data_cal[7:4] <= seg_data_cal_store[7:4] - seg_data_cal_store[15:12]-4'b0001;
end

else begin
seg_data_cal[7:0] <= seg_data_cal_store[7:0]-seg_data_cal_store[15:8];
end

seg_data_cal[16]<= 1'b1; //减法结果为负
end
else if(seg_data_cal_store[15:8]>=seg_data_cal_store[7:0]) begin//结果为正或0

if(seg_data_cal_store[11:8]<seg_data_cal_store[3:0])begin//要借位
seg_data_cal[3:0] <= seg_data_cal_store[11:8] - seg_data_cal_store[3:0] +4'b1010;
seg_data_cal[7:4] <= seg_data_cal_store[15:12] - seg_data_cal_store[7:4]-4'b0001;
end

else begin

seg_data_cal[7:0] <= seg_data_cal_store[15:8]-seg_data_cal_store[7:0];
end

seg_data_cal[16]<= 1'b0; //减法结果为正
end


下面的代码是乘法运算,由于输入分别存储了十位和个位,而不是存储值,因此需要用乘法器,而非*乘法运算符。

module multi(

input clk,rst_n,
input [7:0]a,
input [7:0]b,
output reg [15:0]result
);

reg [15:0] tem_sum;

integer i;
reg [3:0] cnt;

always@(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tem_sum=0;
cnt=0;
end


tem_sum=a*b;
cnt=0;

for(i=0;i<10;i=i+1)begin
if(tem_sum>=1000)begin //1
tem_sum=tem_sum-1000;
cnt=cnt+1;
end

else begin
result[15:12]=cnt;
end
end
cnt=0;
for(i=0;i<10;i=i+1)begin
if(tem_sum>=100)begin //1
tem_sum=tem_sum-100;
cnt=cnt+1;
end

else begin
result[11:8]=cnt;
end
end
cnt=0;
for(i=0;i<10;i=i+1)begin
if(tem_sum>=10)begin //1
tem_sum=tem_sum-10;
cnt=cnt+1;
end

else begin
result[7:4]=cnt;
end
end

result[3:0]=tem_sum[3:0];
end
endmodule



下面的代码是除法运算,需要用除法器来实现,因此先将div_flag除法标志位置为1,再到除法器division里进行计算。除法器模块division主要用长除法来得到商和余数的结果。

		else if(seg_data_oper== 4'b0001)begin//除法
div_flag<=1'b1;
seg_data_cal[16]<= 1'b0;
end
end

always@(posedge clk or negedge rst_n) begin
reg_divisor ={16'd0,divisor};
reg_dividend ={dividend,16'd0};
temp_r =0;
for(i=0;i<16;i=i+1)
begin
reg_divisor =reg_divisor<<1;
temp_r=temp_r<<1;

if(reg_divisor>=reg_dividend)
begin
reg_divisor=reg_divisor-reg_dividend;
temp_r[0] =1;
end
else
begin
reg_divisor= reg_divisor;
temp_r[0] =0;
end
end

end

assign remainder =reg_divisor[31:16];
assign quotient =temp_r;


4)数字及运算结果显示

由于本系统中有三个输出,分别为未按等号前的两个十位数、加减乘的结果、除的结果,因此仿照多路选择器的思路,设计了MUX模块如下,用来在不同的情况下显示计算结果。


always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
seg_data_final<= 32'h0;
end

else if(!cal_flag)begin//未按等号
seg_data_final[31:28]<=seg_data_cal_store[15:12];
seg_data_final[27:24]<=seg_data_cal_store[11:8];//第一个数

seg_data_final[19:16]<=seg_data_cal_store[7:4];
seg_data_final[15:12]<=seg_data_cal_store[3:0];//第二个数

end

else if(cal_flag&&!div_flag)begin//非除法

seg_data_final[31:16]<=seg_data_cal[15:0] ;//符号
seg_data_final[15:12]<=0;

end

else if(cal_flag&&div_flag)begin//除法

seg_data_final[31:24]<=result[7:0];
seg_data_final[19:12]<=rmd[7:0];
end
end


为了显示结果整洁,在高位为0时,取消该数码管的显示,同时考虑到小数运算,在乘法时小数点的位置取决于两个运算数的小数点个数,也需进行处理。


module out_produced(  
input clk,
input rst_n,

input cal_flag,
input div_flag,
input multi_flag,

input [7:0] dot_en,


input [31:0] seg_data_final,

output reg [7:0] dat_en,
output reg [7:0] dot_en_final
);

always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dat_en<= 8'b11000000;
end

if(!cal_flag)begin//在输入数据时
dat_en<= 8'b11011000;
end

else if(cal_flag&&!div_flag)begin//按下等号后时
if(seg_data_final[31:28])begin
dat_en<= 8'b11110000;
end

else if(seg_data_final[27:24])begin//千位为0
dat_en<= 8'b01110000;
end

else if(seg_data_final[23:20])begin//千、百位为0
dat_en<= 8'b00110000;
end

else begin//千、百、个位为0
dat_en<= 8'b00010000;
end
end

else if(cal_flag&&div_flag)begin



if(seg_data_final[31:28]&&seg_data_final[19:16])begin
dat_en<= 8'b11011000;
end

else if(!seg_data_final[31:28]&&seg_data_final[19:16])begin
dat_en<= 8'b01011000;
end

else if(seg_data_final[31:28]&&!seg_data_final[19:16])begin
dat_en<= 8'b11001000;
end

else begin
dat_en<= 8'b01001000;
end
end
end

always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dot_en_final<= 8'b00000000;
end

if(!cal_flag)begin //在输入中
dot_en_final<= dot_en;
end

else if(cal_flag)begin //按下等号后

if(div_flag)begin //是除法
dot_en_final<= 8'b00000000;
end

else if(multi_flag)begin//乘法,按照小数点个数来决定结果小数点位置
if(dot_en[7]&&dot_en[4])begin
dot_en_final<= 8'b01000000;
end

else if(dot_en[7]||dot_en[4])begin
dot_en_final<= 8'b00100000;
end

else begin
dot_en_final<=8'b00000000;
end
end

else begin//加法或减法
if(dot_en)begin
dot_en_final<=8'b00100000;
end

else begin
dot_en_final<=8'b00000000;
end

end
end
end
endmodule



5)各主要模块仿真结果

下面是实现系统主要功能的各模块仿真结果:

(1)division_part模块

image.png

(2)num_out模块
image.png
(3)oper_char模块

image.png



四、问题与解决

1)管脚接触不良

本次实验硬件为MXO2核心板及Baseboard V4.0拓展板。接触不良,是所有插入式外接拓展板的通病,在学习该FPGA的初期,接触不良的问题让我误以为板子自身除了问题或者例程有误。不过所幸在交流群上,我看到有群友反映了同样的问题,并给出了解决办法——用力按,问题得以解决。


2)时序问题

每次生成jed文件时,无论是例程和我自己的项目,总会有如下警报:

image.png


查看place and route中的报告,发现时序存在一定问题,但设计或制造过程还是能够解决


Cost Table Summary
Level/ Number Worst Timing Worst Timing Run NCD
Cost [ncd] Unrouted Slack Score Slack(hold) Score(hold) Time Status
---------- -------- ----- ------ ----------- ----------- ---- ------
5_1 * 0 -8629.612 2147483647 -1.526 154319 10 Completed


打开Timing Analysis View进行进一步研究,如下图。发现信号普遍超出了要求,但超出的部分不多。结合程序可以正常运行的情况,这个问题可以忽视不理:

image.png



3)代码具体问题举例

具体代码如下图所示,这是用以实现加法的部分,实际运行发现结果不正确,因此尝试仿真研究:

image.png

仿真发现加法进位结果反复在41和3B变化:

image.png

结合仿真,做出以下判断:if条件是通过计算结果cal来判断。导致计算结果为十六进制下3B时,符合进位条件,变为41,在41时,又不符合进位条件,重新计算变为3B,如此反复。将if条件改为存储数组store,问题解决。



五、未来计划

该项目已经成功实现了十位数计算器的基本功能。然而还有许多可以提升与扩展的地方:

1.使用拓展板上的液晶显示屏显示计算结果

2.优化时序问题

3.优化算法,如先对输入数据进行转换合并,如把2和4合并为24,再进行计算,可大大减少代码量和资源占用。

附件下载
lab1_1.rar
工程
type_system_impl1.jed
jed文件
团队介绍
团队仅有一个人,西安交通大学大三学生
团队成员
wapoci
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号