2024年寒假练 - 用小脚丫FPGA套件STEP BaseBoard V4.0实现简易计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,实现了简易计算器的设计,它的主要功能为:实现八位十进制数的四则运算,可连续运算。
标签
FPGA
数字逻辑
Lattice
2024寒假练
y268
更新2024-03-29
北京理工大学
222

一、项目需求

实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制。运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

二、需求分析

功能需求

  • 实现两位十进制数的加、减、乘、除四种基本运算功能。
  • 输入:使用矩阵按键输入运算数和运算符。
  • 输出:使用8个八段数码管显示运算数和计算结果。

非功能需求

  • 性能:保证运算器的运算速度和精确度。
  • 安全:确保输入的数据有效且不会导致系统异常。
  • 可用性:易于操作、稳定可靠。
  • 可维护性:代码易于理解、修改和扩展

技术约束

  • 使用FPGA实现,需要考虑FPGA的资源限制和性能特点。
  • 使用矩阵按键和八段数码管,需要了解它们的驱动方式和接口规范。
  • 实现加、减、乘、除运算需要考虑算法的设计和优化,以及数据表示和处理的方法。
  • 输入的运算数和运算符需要进行有效性验证,防止非法输入导致系统异常或错误结果

三、实现的方式

硬件设计

  • 按键输入接口设计:设计一个矩阵按键接口模块,用于接收运算数和运算符的输入。可以使用FPGA的GPIO接口来读取按键输入。
  • 数码管显示接口设计:设计一个数码管显示接口模块,用于控制八段数码管显示运算数和计算结果。这个模块需要将计算结果转换为数码管可以显示的格式,并通过FPGA的GPIO接口发送给数码管。
  • 运算逻辑设计:设计加、减、乘、除四种运算的逻辑电路。每种运算都需要一个专门的模块来处理,该模块应当能够接收输入的运算数和运算符,并输出计算结果

算法实现

  • 加减乘除算法:实现针对两位十进制数的加、减、乘、除运算的算法。可以使用数字电路中常见的加法器、减法器、乘法器和除法器来实现这些算法。需要注意算法的精度和性能。
  • 数码管显示算法:实现将运算结果转换为数码管可以显示的格式的算法。例如,将十进制数转换为七段数码管的显示格式

FPGA开发工具使用

  • 使用FPGA开发工具(lattice diamond)作为开发平台。
  • 在项目中添加设计好的硬件模块,并进行连接和约束。
  • 对设计进行综合、布局布线、生成jed文件。

测试和调试

  • 对设计进行仿真测试,验证硬件模块的功能和正确性。
  • 将jed文件加载到FPGA板上进行实际测试,检查系统的性能和稳定性。
  • 调试硬件和软件,确保系统能够正确地实现加、减、乘、除四种运算功能,并正确显示结果。

四、硬件介绍

核心板:

基于Lattice MXO2的小脚丫FPGA核心板:

STEP小脚丫FPGA学习平台是苏州思得普信息科技公司专门针对FPGA初学者(尤其是学习数字电路的在校同学)打造的一系列性价比最高、学习门槛最低的学习模块系列。板上选用的芯片兼具了FPGA和CPLD的优点,瞬时上电启动,无需外部重新配置FPGA,是学习数字逻辑绝佳的选择。系列中所有板子的大小兼容标准的DIP40封装,尺寸只有52mm x 18mm,非常便于携带,而且能够直接插在面包板上或以模块的方式放置在其它电路板上以即插即用的方式,大大简化系统的设计。

引脚定义:

image.png

扩展底板:

STEP BaseBoard V4.0是第4代小脚丫FPGA扩展底板,可以用于全系列小脚丫核心板的功能扩展,采用100mm*161.8mm的黄金比例尺寸,板子集成了存储器、温湿度传感器、接近式传感器、矩阵键盘、旋转编码器、HDMI接口、RGBLCD液晶屏、8个7位数码管、蜂鸣器模块、UART通信模块、ADC模块、DAC模块和WIFI通信模块,配合小脚丫FPGA板能够完成多种实验,是数字逻辑、微机原理、可编程逻辑语言以及EDA设计工具等课程完美的实验平台。

电路框图:

image.png

四、功能框图

流程图

image.png

原理图

image.png

五、代码及说明

十进制数转bcd码:

为方便数据在数码管上显示,需要将得到的十进制结果转换成二进制bcd码。结合数据大小需求,将输入十进制数位宽规定为27,将输出的bcd码位宽规定为32。

module bin_to_bcd(
        rst_n     ,  //系统复位,低有效
        bin_code   ,  //需要进行BCD转码的二进制数据
        bcd_code      //转码后的BCD码型数据输出
     );

  parameter   NUM_WID           =   27     ;
  parameter   result_bcd_wid =  32 ;

  input                    rst_n     ;  //系统复位,低有效
  input  [NUM_WID-1:0]        bin_code   ;  //需要进行BCD转码的二进制数据
  output [result_bcd_wid-1:0]   bcd_code   ;  //转码后的BCD码型数据输出
  reg       [result_bcd_wid-1:0]   bcd_code   ;
  reg       [58:0]             shift_reg ;

对数据进行循环满五加三操作和位操作,即可将十进制数转换为对应的bcd码。

always@(bin_code or rst_n)begin
  shift_reg = {32'h0,bin_code};
  if(!rst_n)
     bcd_code = 0;
  else begin
     repeat(27) begin //循环27次  
        //BCD码各位数据作满5加3操作,
        if (shift_reg[30:27] >= 5) shift_reg[30:27] = shift_reg[30:27] + 2'b11;
        if (shift_reg[34:31] >= 5) shift_reg[34:31] = shift_reg[34:31] + 2'b11;
        if (shift_reg[38:35] >= 5) shift_reg[38:35] = shift_reg[38:35] + 2'b11;
        if (shift_reg[42:39] >= 5) shift_reg[42:39] = shift_reg[42:39] + 2'b11;
        if (shift_reg[46:43] >= 5) shift_reg[46:43] = shift_reg[46:43] + 2'b11;
        if (shift_reg[50:47] >= 5) shift_reg[50:47] = shift_reg[50:47] + 2'b11;
        if (shift_reg[54:51] >= 5) shift_reg[54:51] = shift_reg[54:51] + 2'b11;
        if (shift_reg[58:55] >= 5) shift_reg[58:55] = shift_reg[58:55] + 2'b11;
        shift_reg = shift_reg << 1;
     end
     bcd_code = shift_reg[58:27];  
  end  
end

模块综合之后的原理图:

image.png

键盘输入模块:

矩阵键盘的按键排列成矩阵的形式,需要通过按键扫描技术来检测用户的按键输入。在设计按键扫描的算法时,需要考虑扫描速度、电路复杂度以及与其他硬件模块的并发性等因素。矩阵键盘的按键可能会因为机械原因而产生抖动,导致系统误判按键输入。为了避免这种情况,通常需要在软件中添加防抖动机制,例如通过软件延时或者采用硬件滤波电路来消除抖动。在键盘输入模块中,通过时钟分频获得了一个200Hz的分频信号,因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样。周期采样时每隔20ms采样一次,对应这里状态机每20ms循环一次,每个状态对应5ms时间,那么通过连续两次采样的值判断即可解决10ms的按键抖动带来的误判。

键位分配:

image.png

按键扫描状态机:

always@(posedge clk_200hz or negedge rst_n) begin
  if(rst_n==1'b0) begin
     c_state <= STATE0;
     row <= 4'b1110;
  end
  else begin
     case(c_state)
        //状态c_state跳转及对应状态下矩阵按键的row输出
        STATE0: begin c_state <= STATE1; row <= 4'b1101; end
        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

综合后的原理图:

image.png

计算器核心部分:

存储部分用状态机和寄存器来实现,我们输入的数字需要进行数学处理才能得到想要的值,若第一个输入的是数字键,则保存下来,第二次输入还是输入的是数字键时,第一个数值左移变为十位,第二次的为个位,第三次若还是数字键,那么第一个数值将变为百位,第二个为十位,第三个为个位,以此类推,直到有符号键输入。

计算器的运算核心部分采用状态机来实现,实现对数据输入、数学运算、数据输出的整合。具体实现方式上,通过一个4位二进制数来保存键值,同时也用来判断状态机是否跳转。逻辑上引入一个flag变量来控制程序启停,保证只有当输入数据发生变化时才会进行数据处理,确保程序稳定运行。

//模块功能:利用状态机实现计算器核心功能
module calculator_core (
                   clk       ,
                   rst_n     ,
                   flag       ,
                   key_data   ,
                   bin_data
                 );

  input        clk         ;
  input        rst_n     ;
  input        flag     ;
  input [3:0]    key_data   ;
 
  output reg [26:0] bin_data ;
     
  reg [1:0] state             ;
  reg [26:0] num1             ;
  reg [3:0] opcode         ;
 
  always @ (posedge clk or negedge rst_n)
     begin
        if (!rst_n)
           begin
              state <= 0   ;
              num1 <= 0     ;
              bin_data <= 0 ;
              opcode <= 0       ;
           end
        else
           begin
              case (state)
              //状态机实现
                 0 : begin
                       if (flag)
                          begin
                             if (key_data < 10)
                                begin
                                   bin_data <= bin_data * 10 + key_data;
                                end
                             else
                                begin
                                   if (key_data == 14)
                                      begin
                                         state <= 0;
                                      end
                                   else
                                   //按下操作符,状态机跳转
                                   //利用num1作为中间量,保存操作数1
                                      begin
                                         opcode <= key_data;
                                         state <= 1;
                                         num1 <= bin_data;
                                         bin_data <= 0;
                                      end
                                end
                          end
                       else
                          begin
                             state <= 0;
                          end
                    end
                 //运算状态
                 1 : begin
                    if (flag)
                    begin
                       if (key_data < 10)
                          begin
                             bin_data <= bin_data * 10 + key_data;
                          end
                       else
                          begin
                             if (key_data == 14)
                             begin
                                case (opcode)
                                10 :  begin bin_data <= num1 + bin_data; state <= 2; end
                                11 :  begin bin_data <= num1 - bin_data; state <= 2; end
                                12 :  begin bin_data <= num1 * bin_data; state <= 2; end
                                13 :  begin bin_data <= num1 / bin_data; state <= 2; end
                                default : bin_data <= 0;
                                endcase
                             end
                             else
                                begin
                                   state <= 1;
                                end
                          end
                    end
                    else
                       begin
                          state <= 1;
                       end
                 end
             
                 2 : begin
                    if (flag)
                    begin
                       if (key_data < 10)
                          begin
                             bin_data <= {22'd0,key_data};
                             state <= 0;
                          end
                       else
                          begin
                             if (key_data == 14)
                                begin
                                   state <= 2;
                                end
                             else
                                begin
                                   num1 <= bin_data;
                                   opcode <= key_data;
                                   bin_data <= 0;
                                   state <=1;
                                end
                          end
                    end
                    else
                       begin
                          state <= 2;
                       end
                 end
              endcase
           end
     end
endmodule

综合后的原理图:

image.png

矩阵键盘信号译码部分:

矩阵键盘信号译码是将按键扫描结果转换为可识别的按键信息的过程,包括按键扫描、信号解码、按键映射和错误处理等步骤,以实现对按键输入的准确识别和处理。矩阵键盘的信号需要经过译码才能成为我们所需要的信息,首先进行按键扫描,通过逐行扫描和逐列扫描的方式,检测矩阵键盘上每个按键的状态。根据按键扫描的结果,进行信号解码,将检测到的按键状态转换为可识别的按键信息,最后将译码后的按键编码值映射到对应的功能或字符。同时,对信号进行译码之后还能将键值信息和位置信息相对应,更加便于调试管理。本程序中使用了两段译码程序,分别用于数据读取和按键测试。

always@(posedge clk or negedge rst_n) begin
  if(rst_n==1'b0) begin
     seg_data <= 8'h00;
  end else begin
     case(key_pulse)                
        16'h0001: seg_data <= 8'h01;   //编码。这里使用16进制,其前四位和后四位恰好对应二进制的十位和个位
        16'h0002: seg_data <= 8'h02;
        16'h0004: seg_data <= 8'h03;
        16'h0008: seg_data <= 8'h04;
        16'h0010: seg_data <= 8'h05;
        16'h0020: seg_data <= 8'h06;
        16'h0040: seg_data <= 8'h07;
        16'h0080: seg_data <= 8'h08;
        16'h0100: seg_data <= 8'h09;
        16'h0200: seg_data <= 8'h10;
        16'h0400: seg_data <= 8'h11;
        16'h0800: seg_data <= 8'h12;
        16'h1000: seg_data <= 8'h13;
        16'h2000: seg_data <= 8'h14;
        16'h4000: seg_data <= 8'h15;
        16'h8000: seg_data <= 8'h16;
        default:  seg_data <= seg_data;   //无按键按下时保持
     endcase
  end
end

数据显示部分:

8个数码管需要的信号线较多,再单独分配引脚显得臃肿而浪费。这里采用两个74HC595芯片级联来控制数码管显示,在最简单的情况下我们只需要控制3根引脚输入得到8根引脚并行输出信号,而且可以级联使用,我们使用3个I/O口控制两个级联的74HC595芯片,产生16路并行输出,从而控制数码管显示。

74HC595是一款常用的串行输入、并行输出的移位寄存器芯片,广泛应用于数字电路设计中。74HC595芯片具有一个8位的串行输入寄存器。可以通过串行数据输入引脚(SER)将数据串行输入到寄存器中。多个74HC595芯片可以级联连接,以扩展输出端口的数量。级联连接时,将第一个芯片的串行输出连接到第二个芯片的串行输入,依此类推。级联连接时,除了第一个芯片外,其他芯片的串行输入都可以接收上一个芯片的并行输出,以实现数据级联传输。

综合后的原理图:

image.png

六、FPGA资源利用说明

资源占用:

image.png

整体来看,资源占用较多也不均衡。经过模块单独综合仿真后发现,除法部分占用了超过三分之二的资源。此前功能更加完善的实现方案也因为综合后占用资源超过了板载资源,从而无法上板测试。针对此现象,应当采用更加精简的算法来优化除法器设计,从而减少资源占用。重新审视设计中的算法和逻辑,寻找优化的空间。可以考虑优化加、减、乘、除等运算模块的设计,减少逻辑深度和面积占用。使用更高效的算法和数据结构可以减少资源使用量。考虑是否可以通过资源共享和复用来减少资源占用。例如,多个模块中可能存在相似的功能或数据,可以考虑将其合并或共享,减少资源重复使用。检查设计中是否存在资源冗余或不必要的重复,对其进行优化或删除。合理使用FPGA提供的资源优化工具,例如逻辑合并、时序约束等。


附件下载
calculator_v1_2_impl1.jed
jed文件
calculator_v1_2.zip
工程文件
团队介绍
北京理工大学
团队成员
y268
北京理工大学信息与电子学院
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号