2024年寒假练 - 基于STEP BaseBord V4.0制作计算器
该项目使用了WebIDE在线平台、Verilog语言,实现了小脚丫FPGA套件STEP BaseBord V4.0的设计,它的主要功能为:能够进行两位十进制的加、减、乘、除运算。
标签
FPGA
计算器
参加活动/培训
香蕉尊
更新2024-04-01
北京理工大学
34

1.项目介绍

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

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

2.硬件介绍

硬件平台包括STEP-MXO2-LPC核心板及其拓展版,核心板使用Lattice公司的 LCMXO2-4000HC-4MG132作为主控芯片,芯片包含:

  • 4320个LUT资源, 96Kbit 用户闪存,92Kbit RAM;
  • 2+2路PLL+DLL;
  • 一路SPI、一路定时器、2路I2C
  • 支持DDR/DDR2/LPDDR存储器;

扩展底板集成了存储器、温湿度传感器、接近式传感器、矩阵键盘、旋转编码器、HDMI接口、RGBLCD液晶屏、8个7位数码管、蜂鸣器模块、UART通信模块、ADC模块、DAC模块和WIFI通信模块。

3.设计思路

本次实验使用拓展底板上的矩阵键盘和数码管设计完成计算器功能。本设计总共包含6个模块,其中key_scan模块用于接收矩阵键盘输入,cal模块用于存储键盘输入数据并计算,bcd_8421模块将二进制的计算结果转换为8421码表示,然后经过二选一多路选择器根据计算是否完成的标志信号选择待显示的数字是操作数还是计算结果,然后data_gen模块将要显示的数字转化为数码管的片选和段选信号,最后hc595_ctrl模块实现数据的并串转换控制数码管的显示。

3.1 key_scan模块设计思路
矩阵按键的硬件电路图如图所示,可以看到4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4),同时列线通过上拉电阻连接到VCC电压(3.3V),4根行线是输入的,是由FPGA控制拉高或拉低;4根列线数输出的,是由4根行线的输入及按键的状态决定,输出给FPGA。

举个例子,FPGA控制输出到4根行线电平分别为ROW1=0、ROW2=1、ROW3=1、ROW4=1时,如果此时K1按键按下,对应4根列线输出COL1=0、COL2=1、COL3=1、COL4=1,根据这个原理按照扫描的方式,一共分为4个时刻,分别对应4根行线中的一根拉低,4个时刻依次循环,这样就完成了矩阵按键的全部扫描检测。在实际测试过程中,会出现按键抖动带来的输出不稳定。

这个时候我们可以适当增加每次扫描的时间间隔。这里我采用的是15ms,按键扫描的状态转移图如图所示:

初始状态S0下FPGA始终给四根列线输出低电平,当没有按键被按下或者按键时间过短,此时标志信号始终输出0;当有按键被按下并且超过15ms的时候,状态跳转到S1,同时将按键标志信号拉高,此时四根列线在每个时钟周期依次输出低电平再结合读取的行线对应的电平,两者组合确定被按下按键的位置信息,再按照实现约定对每个按键进行编号就完成了一次矩阵键盘扫描的全过程,之后重新跳转到S0状态等待下一次按键被按下。下面是状态转移图的代码实现:

	always @ (posedge clk_1khz or negedge rst_n)
begin
if(!rst_n)
begin
cnt_time <= 5'd0;
row_col <= 8'd0;
state <= s0;
row <= 4'b0000;
flag <= 0;
end
else
case(state)
s0 : begin
if(col != 4'b1111)
begin
if(cnt_time < TEST)
begin
cnt_time <= cnt_time + 1;
flag <= 0;
end
else
begin
cnt_time <= 0;
row <= 4'b0111;
state <= s1;
end
end
else
begin
cnt_time <= 0;
flag <= 0;
end
end

s1 : begin
if(col != 4'b1111)
begin
row_col <= {row, col}; //改?
flag <= 1;
row <= 4'b0000;
state <= s2;
end
else
begin
row <= {row[0], row[3:1]};
state <= s1;
end
end

s2 : begin
if(col == 4'b1111)
begin
if(cnt_time < TEST)
cnt_time <= cnt_time + 1;
else
begin
cnt_time <= 0;
row <= 4'b0000;
flag <= 0;
state <= s0;
end
end
else
begin
cnt_time <= 0;
flag <= 0;
state <= s2;
end
end
default : state <= s0;
endcase
end

下面是编号与按键的一一映射关系:

	always @ (*)
begin
if(!rst_n)
data <= 4'd0;
else
case(row_col)
8'b0111_1110 : data <= 4'd7; //4 3 2 1 //7
8'b1011_1011 : data <= 4'd6; //6
8'b1011_1101 : data <= 4'd5; //5
8'b1011_1110 : data <= 4'd4; //4

8'b1101_1110 : data <= 4'd3; //3
8'b1101_1101 : data <= 4'd2; //2
8'b1101_1011 : data <= 4'd1; //1
8'b1110_1110 : data <= 4'd0; //0

8'b1110_1101 : data <= 4'd15; //.
8'b1110_1011 : data <= 4'd14; // =
8'b0111_0111 : data <= 4'd13; // /
8'b1011_0111 : data <= 4'd12; // *

8'b1101_0111 : data <= 4'd11; // -
8'b1110_0111 : data <= 4'd10; // +
8'b0111_1101 : data <= 4'd8; //8
8'b0111_1011 : data <= 4'd9; //9
endcase
end

模块仿真

这里为了缩短仿真时间,我仅采用了三分频,模拟输入列信号col="1110",可以看到当按键col输入大于指定时间时,程序检测到有按键输入,此时row信号开始轮询只将一位拉低row="0111",此时仍然模拟输入col="0111",此时{row,col}="0111_0111",检测到是第4行第4列按键被按下,对应的编号为13,对应16进制的d。

3.2 计算(cal)模块设计思路

当按键信号有效时,如果按键数据data小于10代表按下的是运算数,num1记录第一个运算数,状态保持不变,等待num1的下一位数字;如果按键数据data大于10小于14代表按下的是运算符,opcode记录下运算符,状态跳转到S2;如果没有按键按下状态S0保持不变;在S2状态下与S0同理,num2用于存储第二个运算数的值;状态S4是使用CASE语句根据不同的运算符对两个运算数进行不同的运算操作,之后跳转到S5状态;状态S5如果按下的数字则NUM1记录下第一位数字,然后跳转到S0状态;如果按下的是符号键则代表要进行连续运算的操作,则把上一次的计算结果赋值给NUM1作为下一次的第一个运算数,状态跳转到S2等待NUM2的输入。


下面是代码实现:

always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
state <= 0;
num1 <= 0;
num2 <= 0;
result <= 0;
opcode <= 0;
seg_data <= 0;
flag_BCD <= 1'b0;
end
else
begin
case(state)
0 : begin
if(flag)
begin
if(data < 10)
begin
num1 <= num1 * 10 + data;
seg_data <= {seg_data[27:24],data,seg_data[23:0]};
state <= 1;
end
else
begin
if(data < 14)
begin
opcode <= data;
// seg_data <= 0;
// seg_data <= data; //改啦
state <= 2;
end
else
begin
state <= 0;
// seg_data <= data; //改啦
end
end
end
else
begin
state <= 0;
end
end

1 : begin
if(flag)
begin
state <= 1;
end
else
begin
state <= 0;
end
end

2 : begin
if(flag)
begin
if(data < 10)
begin
num2 <= num2 * 10 + data;
seg_data <= {seg_data[31:24],seg_data[19:16],data,seg_data[15:0]};
state <= 3;
end
else
begin
if(data < 14)
begin
state <= 2;
// seg_data <= data;
end
else
begin
// state <= 4;
// seg_data <= data;
state <= 4;
// seg_data <= 32'h12345678;
end
end
end
else
begin
state <= 2;
end
end

3 : begin
if(flag)
begin
state <= 3;
end
else
begin
state <= 2;
end
end


4 : begin
if(!flag)
begin
// seg_data <= opcode;
case(opcode)
4'd10 : result <= num1 + num2;
4'd11 : result <= num1 - num2;
4'd12 : result <= num1 * num2;
4'd13 : result <= num1 / num2;
default : result <= num1 + num2;
endcase
// flag_BCD <= 1'b1;
state <= 5;
flag_BCD <= 1'b1;
end
else
begin
state <= 4;
end
end

// 5 : begin
// if(flag_BCD_en)
// begin
// flag_BCD <= 1'b0;
// state <= 6;
// end
// else
// begin
// state <= 5;
// end
// end

5 : begin
// seg_data <= state;
if(flag)
begin

// seg_data <= 32'h12345678;
if(data < 10)
begin
flag_BCD <= 1'b0;
num1 <= data;
seg_data <= {4'b0,data,24'b0};
state <= 1;
num2 <= 0;
end
else
begin

state <= 5;
end
end
else
begin
state <= 5;
end
end

default : state <= 0;
endcase
end
end

模块仿真

对cal计算模块进行仿真,模拟按键输入“12+12=”我们可以看到仿真波形图按照输入顺序依次显示数字,同时在最后自动输出结果24。

3.3 hc595芯片数据产生模块

当某一时刻,FPGA控制8根公共的段选接口输出数字1对应的数码管字库数据8'h06(DP=0、G=0、F=0、E=0、D=0、C=1、B=1、A=0)时,同时控制6位数码管只有第1位使能(DIG1=0、DIG2=1、DIG3=1、DIG4=1、DIG5=1、DIG6=1)这样我们会看到第1位数码管显示数字1,按照扫描的方式,一共分为8个时刻,段选端口分别对应输出8位数码管需要显示的字库数据,位选端口保持每个时刻只有1位数码管处于使能状态,8个时刻依次循环,当扫描频率足够高时,则在人眼看到的数码管显示就是连续的,我们看到的就是8个不同的数字一起显示。

模块仿真

测试模拟输入待显示数字data=“00123456”,对应输出可以看到片选信号从sel[0]~sel[7]依次拉低实现数码管的逐个片选,然后观察段选信号以第三位为例,对应seg[0]~seg[7]为”01100000“,查找字库对应显示字符为1,说明仿真结果正确。

3.4 hc595芯片数据控制模块

74HC595是较为常用的串行转并行的芯片,内部集成了一个8位移位寄存器、一个存储器和8个三态缓冲输出。在最简单的情况下我们只需要控制3根引脚输入得到8根引脚并行输出信号,而且可以级联使用,我们使用3个I/O口控制两个级联的74HC595芯片,产生16路并行输出,连接到扫描显示的8位数码管上。

SHCP为移位寄存器时钟输入,上升沿时将输入的串行数据(DS端输入)移入移位寄存器中。STCP(存储寄存器时钟)控制,STCP上升沿时移位寄存器的数据会进入数据存储寄存器中,通过输出使能为低即可让存储寄存器中的数据进行输出。所以对于74HC595的使用步骤是:1.首先把要传输的数据通过引脚DS输入到74HC595中。2.产生SHCP时钟,将DS上的数据串行移入移位寄存器。3.产生STCP时钟,将移位寄存器里的数据送入存储寄存器。4.将引脚置为低电平,存储寄存器的数据会在Q0—Q7并行输出,同时并行输出的数据会被锁存起来。

模块仿真

这里以输入数据为4ffe为例,首先四分频计数器cnt_4每四个周期清零一次,得到4分频后的移位时钟shcp,同时cnt_bit记录时钟shcp周期数,每到16个清零一次(因为两个hc595芯片级联控制8个8段数码管,共计16位),观察ds信号按照shcp时钟依次输出“0100_1111_1111_1110”,每当移位了16位数据时存储时钟信号stcp拉高一次。

4.完成的功能

4.1计算器初始状态及按键描述

4.2计算器功能测试:

“10+12=22”

“12-10=2”

“99*99=9801”

“9/3=3”

5.资源使用情况

来自运行日志文件

对FPGA资源占用情况进行简单分析,这里主要关注的资源包括寄存器位数、查找表(LUTs)、输入/输出块(IB/OB)以及特定的逻辑和算术组件。这份报告提供了一个关于FPGA设计资源利用率的快照,关键部分如下:

1.寄存器位数 (Register bits): FPGA中共有226个寄存器位被使用,占总寄存器位数4635的4%。这表明您的设计中对寄存器的需求不高,资源利用率较低。

2.逻辑单元:

LUT4 (查找表): 使用了298个,是实现复杂组合逻辑的关键组件。

CCU2D (复杂逻辑单元): 使用了142个,用于实现算术运算和复杂逻辑功能。

PFUMX: 使用了35个,用于实现多路选择器功能等。

AND2: 使用了4个,表示有简单的逻辑与操作。

3.算术组件:

FADD2B: 使用了28个,用于二进制加法运算。

MULT2: 使用了16个,在设计中有乘法运算的需求。

4.寄存器 (Flip-Flops): 有多种类型的触发器(Flip-Flops)被使用,包括FD1P3AX、FD1P3AY、FD1P3IX、FD1S1A等,这些都是用于存储位元信息,保持设备状态。

5.输入/输出块 (IB/OB): 有6个输入块和7个输出块被使用,因为有外部接口的需求。

6.其他特殊功能组件:

GSR (全局设置/复位): 使用了1个,用于重置整个FPGA。

L6MUX21: 使用了5个,这是一个六输入的多路选择器,表明了较复杂的数据选择逻辑。

6.未来的计划

  • 完成拓展要求,使用TFTLCD来做显示
  • 完成更多的计算功能,更接近一个真实的计算器
  • 可以添加定时器作为闹钟,蜂鸣器用作按键音
附件下载
archive.zip
团队介绍
香蕉尊 北京理工大学
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号