2024寒假练 - 基于小脚丫FPGA套件STEP BaseBoard V4.0制作的两位十进制加、减、乘、除计算器
硬禾学堂-2024寒假一起练项目-平台2-任务1-两位十进制加、减、乘、除计算器
标签
FPGA
Lee
更新2024-04-01
333

一、项目需求

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

基本要求:

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


二、需求分析

  1. 支撑整个FPGA计算器系统的硬件为小脚丫FPGA套件STEP BaseBoard V4.0,FPGA核心为Lattice LCMXO2,使用到的拓展板载硬件模块为4*4矩阵键盘和8位数码管。
  2. FPGA固件可以分成两个主要部分,即外围驱动模块和计算器功能实现模块。外围驱动模块又可以分为4*4矩阵键盘模块和8位数码管模块。
  3. 4*4矩阵键盘为嵌入式开发中常用的输入方式,主要通过一定频率扫描行(或者列),在选中某一行的同时查询列中按键按下的情况,最后通过行与列的交叉匹配,确定输入的按键值。
  4. 数码管的本质是多阵列LED灯,通过HC595等串转并芯片控制,在计算器的数码管显示中,需要对计算结果进行提取处理才能正确显示在数码管中。
  5. 计算器核心代码的编写中,只要搭建完成一种计算方式的计算,即可推广到其他常见运算中。
  6. 该项目+,-,×,÷四种运算中÷在FPGA中较为特殊,处理不当可能消耗很多资源,一般使用除法器IP核和自己编写的高性能除法器模块处理,但是在一些对实时性要求不高并且对余数无要求的场合也可以直接调用"/"处理。
  7. 该项目FPGA计算机器的核心固件编写需要多次根据按键的输入值作为跳转条件,这里最好使用状态机管理状态间的跳转。


三、实现的方式

  1. 使用Visio划分系统框图,确定工程需要的主要模块。
  2. 使用Visio完成计算器核心处理算法的状态机跳转示意图,指导Verilog代码编写。
  3. 编写计算器核心代码,并使用Modelsim仿真验证代码正确性。
  4. 结合电子森林官方提供的小脚丫拓展板资料完成外围模块的编写、修改、整合。
  5. 使用Diamond工具进行综合、布局布线、jed文件生成(该工程源代码已同步至WebIDE)。
  6. 上板调试并修改优化代码。


四、功能框图

1.系统功能模块框图

上图为固件顶层的功能模块划分。主要包括三个部分:系统输入模块:4*4矩阵键盘驱动模块,系统数据处理模块:Calculator_Core,系统显示输出模块:数码管驱动。

2.Calculator_Core状态机跳转示意图

上图为该项目核心模块的状态机跳转示意图,该项目总共划分为6个状态:S0_IDEL、S1、S2、S3、S4、S5。

  1. 上电为S0_IDEL状态主要用于系统复位,并对各寄存器赋初始值。当用户按下键盘中标识的0~9时,系统跳转至S1状态。
  2. S1状态主要用于记录第一个操作数,对于项目要求中的2位输入,采用num1 = num1*10 + keydata的形式存入,当用户按下标志+ - * /的按键时,系统状态跳入S2状态。
  3. S2状态主要用于记录操作符,为后面的实际计算做铺垫。当用户按下0~9按键时,系统跳转至S3状态。
  4. S3状态主要用于记录第二个操作数,具体操作与S1状态类似。当用户按下=按键时,系统跳转至S4状态。
  5. S4状态主要是根据操作符以分支结构的方式进行相关数学计算。计算完成后,rRESULT_READY信号拉高,状态机跳转至S5状态。
  6. S5状态主要根据用户按键来进行下一步跳转:如果用户按下0~9则状态机跳转至S1,如果用户按下任意操作符则状态机跳转至S2状态,并且将上一次计算的结果赋值给第一个操作数。


五、代码及说明

1.顶层模块

`timescale 1ns/1ns

module Calculator_Top (
input iCLK12M , // System Clock 12M
input iaRSTX , // Asynchronous reset active low

//Array_KeyBoard
input [3:0] col , // 矩阵按键列接口
output [3:0] row , // 矩阵按键行接口

//Segment_scan
output rclk_out , // 74HC595的RCK管脚
output sclk_out , // 74HC595的SCK管脚
output sdio_out // 74HC595的SER管脚
);

wire [31:0] wvSEGDATA;
wire [15:0] wvKEY_OUT;

Array_KeyBoard #
(
.NUM_FOR_200HZ( 60000 ) //定义计数器cnt的计数范围,例化时可更改
)
Array_KeyBoard_u0(
.clk_in ( iCLK12M ), //input 系统时钟
.rst_n_in ( iaRSTX ), //input 系统复位,低有效
.col ( col ), //input [3:0] 矩阵按键列接口
.row ( row ), //output reg [3:0] 矩阵按键行接口
.key_out ( wvKEY_OUT ) //output reg [15:0] 消抖后的信号
);

Calculator_Core Calculator_Core_u0 (
.iCLK12M ( iCLK12M ), // System Clock 12M
.iaRSTX ( iaRSTX ), // Asynchronous reset active low
.ivKEY_IN ( ~wvKEY_OUT ), // 4*4 Keyboard Input active high

.ovSEG_DATA ( wvSEGDATA ) // Segment Display Data
);
segment_scan Segment_scan_u0(

.clk ( iCLK12M ), //系统时钟 12MHz
.rst_n ( iaRSTX ), //系统复位 低有效
.dat_1 ( wvSEGDATA[3:0] ), //SEG1 显示的数据输入
.dat_2 ( wvSEGDATA[7:4] ), //SEG2 显示的数据输入
.dat_3 ( wvSEGDATA[11:8] ), //SEG3 显示的数据输入
.dat_4 ( wvSEGDATA[15:12] ), //SEG4 显示的数据输入
.dat_5 ( wvSEGDATA[19:16] ), //SEG5 显示的数据输入
.dat_6 ( wvSEGDATA[23:20] ), //SEG6 显示的数据输入
.dat_7 ( wvSEGDATA[27:24] ), //SEG7 显示的数据输入
.dat_8 ( wvSEGDATA[31:28] ), //SEG8 显示的数据输入
.dat_en ( 8'B11111111 ), //数码管数据位显示使能,[MSB~LSB]=[SEG1~SEG8]
.dot_en ( 8'B00000000 ), //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]
.seg_rck( rclk_out ), //74HC595的RCK管脚
.seg_sck( sclk_out ), //74HC595的SCK管脚
.seg_din( sdio_out ) //74HC595的SER管脚
);

endmodule

顶层模块的设计与4.1小结中的系统模块划分保持一致,主要包括三个部分:系统输入模块:4*4矩阵键盘驱动模块,系统数据处理模块:Calculator_Core,系统显示输出模块:数码管驱动。Calculator_Core 模块主要接受16bit按键数据与输出数码管显示数据。数码管显示部分的位数使能端口始终使能为1,同时由于该设计不支持小数运算,因此小数显示部分设置为恒0。

2.计算器核心处理模块

2.1状态机设计

always @(posedge iCLK12M or negedge iaRSTX) begin
if(~iaRSTX) begin
rvCUR_STATE <= S0_IDLE;
end else begin
rvCUR_STATE <= rvNEXT_STATE;
end
end

always @(*) begin
rvNEXT_STATE = rvCUR_STATE;
case (rvCUR_STATE)
S0_IDLE:begin //复位
if ((rKEY_VALID == 1'B1)&&(rvKEY_DATA<4'D10)) begin //按键输入0~9进入状态1
rvNEXT_STATE = S1;
end
end
S1:begin //存储和显示第一个操作数rvNUM1
if ((rKEY_VALID == 1'B1)&&(rvKEY_DATA >= 4'D10)&&(rvKEY_DATA <= 4'D13)) begin //按符号键进入状态2
rvNEXT_STATE = S2;
end
end
S2:begin //存储运算符号rvOPCODE
if ((rKEY_VALID == 1'B1)&&(rvKEY_DATA<4'D10)) begin //按键输入0~9进入状态3
rvNEXT_STATE = S3;
end
end
S3:begin //存储和显示第二个操作数rvNUM2
if ((rKEY_VALID == 1'B1)&&(rvKEY_DATA == 4'D14)) begin //=号,进入S4
rvNEXT_STATE = S4;
end
end
S4:begin //根据存储运算符号rvOPCODE计算结果rvRESULT,并显示在ovSEG_DATA
if (rRESULT_READY == 1) begin //rRESULT_READY
rvNEXT_STATE = S5;
end
end
S5:begin //主要用于等待、判断下一步操作为连续运算进入S2还是回归S1
if ((rKEY_VALID_1D == 1'B1)&&(rvKEY_DATA<4'D10)) begin //按键输入0~9进入S1
rvNEXT_STATE = S1;
end else if ((rKEY_VALID == 1'B1)&&(rvKEY_DATA >= 4'D10)&&(rvKEY_DATA <= 4'D13)) begin ////按键输入操作数进入S3
rvNEXT_STATE = S2;
end
end
default : rvNEXT_STATE = S0_IDLE;
endcase
end

该部分为状态机划分与跳转模块,具体功能描述参见4.2小结的Visio框图与介绍。

2.2计算结果数码管显示转换

`timescale 1ns/1ns

module bin2dec(clk12M,rst_n,data_in,d0,d1,d2,d3,d4,d5,d6,d7,d8);
input clk12M,rst_n;
input [31:0] data_in;
output [3:0] d0,d1,d2,d3,d4,d5,d6,d7,d8;
reg [31:0] data_in_r;
reg [3:0] state;
reg [3:0] d0_r,d1_r,d2_r,d3_r,d4_r,d5_r,d6_r,d7_r,d8_r,temp0,temp1,temp2,temp3,temp4,temp5,temp6,temp7,temp8;
assign d0=d0_r;
assign d1=d1_r;
assign d2=d2_r;
assign d3=d3_r;
assign d4=d4_r;
assign d5=d5_r;
assign d6=d6_r;
assign d7=d7_r;
assign d8=d8_r;
always@(posedge clk12M or negedge rst_n)
begin
if(!rst_n)
begin
state<=4'd0;
d0_r<=4'd0;
d1_r<=4'd0;
d2_r<=4'd0;
d3_r<=4'd0;
d4_r<=4'd0;
d5_r<=4'd0;
d6_r<=4'd0;
d7_r<=4'd0;
d8_r<=4'd0;
temp1<=4'd0;
temp2<=4'd0;
temp3<=4'd0;
temp4<=4'd0;
temp5<=4'd0;
temp6<=4'd0;
temp7<=4'd0;
temp0<=4'd0;
temp8<=4'd0;
end
else
begin
case(state)
4'd0:begin
data_in_r<=data_in;
state<=4'd1;
temp1<=4'd0;
temp2<=4'd0;
temp3<=4'd0;
temp4<=4'd0;
temp5<=4'd0;
temp6<=4'd0;
temp7<=4'd0;
temp0<=4'd0;
temp8<=4'd0;
end
4'd1:begin
if(data_in_r>=32'd100000000)
begin
data_in_r<=data_in_r-32'd100000000;
temp8<=temp8+4'd1;
end
else
begin

state<=4'd2;
end
end
4'd2:begin
if(data_in_r>=32'd10000000)
begin
data_in_r<=data_in_r-32'd10000000;
temp7<=temp7+4'd1;
end
else
begin

state<=4'd3;
end
end
4'd3:begin
if(data_in_r>=32'd1000000)
begin
data_in_r<=data_in_r-32'd1000000;
temp6<=temp6+4'd1;
end
else
begin

state<=4'd4;
end
end
4'd4:begin
if(data_in_r>=32'd100000)
begin
data_in_r<=data_in_r-32'd100000;
temp5<=temp5+4'd1;
end
else
begin

state<=4'd5;
end
end
4'd5:begin
if(data_in_r>=32'd10000)
begin
data_in_r<=data_in_r-32'd10000;
temp4<=temp4+4'd1;
end
else
begin

state<=4'd6;
end
end
4'd6:begin
if(data_in_r>=32'd1000)
begin
data_in_r<=data_in_r-32'd1000;
temp3<=temp3+4'd1;
end
else
begin

state<=4'd7;
end
end
4'd7:begin
if(data_in_r>=32'd100)
begin
data_in_r<=data_in_r-32'd100;
temp2<=temp2+4'd1;
end
else
begin

state<=4'd8;
end
end
4'd8:begin
if(data_in_r>=32'd10)
begin
data_in_r<=data_in_r-32'd10;
temp1<=temp1+4'd1;
end
else
begin

state<=4'd9;
end
end
4'd9:begin
if(data_in_r>=32'd1)
begin
data_in_r<=data_in_r-32'd1;
temp0<=temp0+4'd1;
end
else
begin
d0_r<=temp0;
d1_r<=temp1;
d2_r<=temp2;
d3_r<=temp3;
d4_r<=temp4;
d5_r<=temp5;
d6_r<=temp6;
d7_r<=temp7;
d8_r<=temp8;
state<=4'd0;
end
end
default:state<=4'd0;
endcase
end
end
endmodule


该部分主要通过状态机逐位比较大小和减法应用结合完成位数的提取,避免了除法取余操作对资源的浪费。例如2300这个数,状态机跳转至千位时就是将2300与1000比较,,如果2300大于1000则减去1000,同时Temp值+1,当2300-1000-1000成为300进入下一状态时,temp值已经累加至2,即提取出了千位上的值,其他值以此类推。

2.3数码管右侧显示

//rvSEG_DATA
always @(posedge iCLK12M or negedge iaRSTX) begin
if(~iaRSTX) begin
rvSEG_DATA <= 32'D0;
end else if((rvCUR_STATE == S1)&&(rKEY_VALID_1D == 1'B1)&&(rvKEY_DATA<4'D10))begin
rvSEG_DATA <= {rvKEY_DATA,rvSEG_DATA[31:4]};
end else if ((rvCUR_STATE == S2)&&(rvNEXT_STATE == S3)) begin
rvSEG_DATA <= 32'D0;
end else if ((rvCUR_STATE == S3)&&(rKEY_VALID_1D == 1'B1)&&(rvKEY_DATA<4'D10)) begin
rvSEG_DATA <= {rvKEY_DATA,rvSEG_DATA[31:4]};
end else if ((rvCUR_STATE == S3)&&(rvNEXT_STATE == S4)) begin
rvSEG_DATA <= 32'D0;
end else if ((rvCUR_STATE == S5)&&(rvNEXT_STATE == S5)) begin
if ((rvOPCODE == 4'D1)&&(rvNUM1 < rvNUM2)) begin
rvSEG_DATA <= {d0,d1,d2,d3,d4,d5,d6,4'D10};
end else begin
rvSEG_DATA <= {d0,d1,d2,d3,d4,d5,d6,d7};
end
end else if ((rvCUR_STATE == S5)&&((rvNEXT_STATE == S1)||(rvNEXT_STATE == S3))) begin
rvSEG_DATA <= 32'D0;
end else begin
rvSEG_DATA <= rvSEG_DATA;
end
end

该部分主要通过位拼接与移位操作来完成项目要求的第一个数在右侧显示,第二个数按下时,第一个向左移动的要求。

6.仿真波形图

6.1计算器核心功能仿真图

image.png

上图中显示了一个12+17=29计算结果直接*2最终等于58的仿真,可以看到,系统2次结果均正确,且显示结果需要提取的数也已经按照项目需求正确拼接至数码管显示寄存器中。

6.2计算结果数提取功能仿真图

image.png

上图可以看到,在状态机跳转至对应的位时,数据转换模块在两次结果的提取中均正确提取出了对应位的数据。第一次为2和9,第二次为5和8。


7.FPGA资源利用说明

以下为Diamond工具生成的资源报告:

   Number of registers:    357 out of  4635 (8%)
PFU registers: 357 out of 4320 (8%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 2098 out of 2160 (97%)
SLICEs as Logic/ROM: 2098 out of 2160 (97%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 1785 out of 2160 (83%)
Number of LUT4s: 4165 out of 4320 (96%)
Number used as logic LUTs: 595
Number used as distributed RAM: 0
Number used as ripple logic: 3570
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%)

可以看到综合后Lut资源使用了大约8%,SLICEs和LUT4s资源使用到了96%+的水平,对于该项目的进一步优化可以使用高性能除法器IP核代替/的操作,可以在一定程度减少资源的利用。





附件下载
Calculator_impl1.jed
JED文件
Calculator_Top.v
SRC_项目顶层文件
Array_KeyBoard.v
SRC_按键模块4*4
Calculator_Core.v
SRC_计算器核心处理模块
bin2dec.v
SRC_计算结果转化数码管文件
Segment_scan.v
SRC_8位数码管驱动
tb_CalculatorCore.v
TB_计算器核心模块仿真文件
团队介绍
个人开发者,会画点板子,也写点Verilog。
团队成员
Lee
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号