2024寒假在家一起练——基于小脚丫FPGA套件STEP BaseBoard V4.0实现两位十进制加、减、乘、除计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,实现了两位十进制加、减、乘、除计算器的设计,它的主要功能为:进行两位十进制加、减、乘、除并显示在数码管上。
标签
FPGA
Funpack活动
显示
shining_zhao
更新2024-04-02
60

两位十进制加、减、乘、除计算器的任务报告

@本项目完全在WebIDE平台上进行设计与运行

1.项目需求

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

img

基本需求:

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

2.需求分析

项目需求可以分为两部分,一是完成加减乘除的运算并得到正确的结果,二是将输入的数字与得到的结果正确显示在数码管上。完成加减乘除的运算中加减乘是相对容易的,而除法相对比较困难。完成数码管的显示原理较为容易理解,需要按照题目要求合理设置显示顺序以及确保显示的正确性。

3.实现的方式

实现本项目功能主要可以分为三部分:准确获取按键按下的位置、将按键信息处理为数字信息并计算、将数字显示出来。

准确获取按键按下的位置:

在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式,使用行线和列线分别连接到按键开关的两端,这样我们就可以通过4根行线和4根列线(共8个I/O口)连接16个按键。

img

上图为4×4矩阵按键的硬件电路图,可以看到4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4),同时列线通过上拉电阻连接到VCC电压(3.3V),对于矩阵按键来讲:

  1. 4根行线是输入的,是由FPGA控制拉高或拉低,
  2. 4根列线数输出的,是由4根行线的输入及按键的状态决定,输出给FPGA

按照扫描的方式,一共分为4个时刻,分别对应4根行线中的一根拉低,4个时刻依次循环,这样就完成了矩阵按键的全部扫描检测,通过对按键扫描得到的信号进行定义便可以得到按键按下所代表的输入。

将按键信息处理为数字信息并计算:

通过对按键读取模块传入的数据进行信号与数的一一对应将前后两次键入的数字存入num1与num2中,对num1与num2进行加减乘除的运算,将num1、num2与计算结果的各个位进行拆分并依次传入到显示模块

将数字显示出来:

这里显示数字使用基于74HC595的数码管模块,共8位数码显示块,通过对传入的数据与使能标志位的控制将num1、num2与计算结果的各个位按要求依次显示在数码管上。

4.功能框图

img

5.代码(内嵌到报告中)及说明

本项目使用Verilog语言编写,共有3个功能模块文件与一个top文件。

接下来我将分模块进行代码展示与说明。

Array_KeyBoard模块:


`module Array_KeyBoard #`
(
  parameter           NUM_FOR_200HZ = 60000   //定义计数器cnt的计数范围,例化时可更改`
)
(
  input                   clk_in,         //系统时钟`
  input                   rst_n_in,       //系统复位,低有效`
  input           [3:0]   col,           //矩阵按键列接口`
  output reg     [3:0]   row,           //矩阵按键行接口`
  output reg     [15:0] key_out         //消抖后的信号`
);
/*`
因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态`
在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样`
周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间`
对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理`
*/
  localparam         STATE0 = 2'b00;`
  localparam         STATE1 = 2'b01;`
  localparam         STATE2 = 2'b10;`
  localparam         STATE3 = 2'b11;``

//计数器计数分频实现5ms周期信号clk_200hz
reg     [15:0]     cnt;
reg                 clk_200hz;
always@(posedge clk_in or negedge rst_n_in) begin
  if(!rst_n_in) begin     //复位时计数器cnt清零,clk_200hz信号起始电平为低电平
      cnt <= 16'd0;
      clk_200hz <= 1'b0;
  end else begin
      if(cnt >= ((NUM_FOR_200HZ>>1) - 1)) begin   //数字逻辑中右移1位相当于除2
          cnt <= 16'd0;
          clk_200hz <= ~clk_200hz;   //clk_200hz信号取反
      end else begin
          cnt <= cnt + 1'b1;
          clk_200hz <= clk_200hz;
      end
  end
end

reg     [1:0]       c_state;
//状态机根据clk_200hz信号在4个状态间循环,每个状态对矩阵按键的行接口单行有效
always@(posedge clk_200hz or negedge rst_n_in) begin
  if(!rst_n_in) begin
      c_state <= STATE0;
      row <= 4'b1110;
  end else begin
      case(c_state)
          STATE0: begin c_state <= STATE1; row <= 4'b1101; end   //状态c_state跳转及对应状态下矩阵按键的row输出
          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

//因为每个状态中单行有效,通过对列接口的电平状态采样得到对应4个按键的状态,依次循环
always@(negedge clk_200hz or negedge rst_n_in) begin
  if(!rst_n_in) begin
      key_out <= 16'hffff;
  end else begin
      case(c_state)
          STATE0:key_out[3:0] <= col;     //采集当前状态的列数据赋值给对应的寄存器位
          STATE1:key_out[7:4] <= col;
          STATE2:key_out[11:8] <= col;
          STATE3:key_out[15:12] <= col;
          default:key_out <= 16'hffff;
      endcase
  end
end

endmodule

本模块借鉴平台“基于STEP FPGA的矩阵按键驱动”的例程,通过输出行状态(row)读取列状态(rol)来得到按键按下的准确位置,扫描频率为200Hz。当某一时刻,FPGA控制4根行线分别为ROW1=0、ROW2=1、ROW3=1、ROW4=1时,

  • 对于K1、K2、K3、K4按键:按下时对应4根列线输出COL1=0、COL2=0、COL3=0、COL4=0,不按时对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1,
  • 对于K5~K16之间的按键:无论按下与否,对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1,

通过上面的描述:在这一时刻只有K1、K2、K3、K4按键被按下,才会导致4根列线输出COL1=0、COL2=0、COL3=0、COL4=0,否则COL1=1、COL2=1、COL3=1、COL4=1,反之当FPGA检测到列线(COL1、COL2、COL3、COL4)中有低电平信号时,对应的K1、K2、K3、K4按键应该是被按下了。主要通过定义c_state完成col与row的相互链接。

display模块:

module display(
      input               clk,        
      input               rst_n,  
      input       [3:0]   dat_1,  
      input       [3:0]   dat_2,  
      input       [3:0]   dat_3,  
      input       [3:0]   dat_4,  
      input       [3:0]   dat_5,  
      input       [3:0]   dat_6,
      input       [3:0]   dat_7,  
      input       [3:0]   dat_8,  
      input       [7:0]   dat_en,
      input       [7:0]   dot_en,
      output reg seg_rck,    
      output reg seg_sck,    
      output reg seg_din    
  );

localparam CNT_40KHz = 300;    

localparam IDLE   =   3'd0;
localparam MAIN   =   3'd1;
localparam WRITE   =   3'd2;
localparam LOW     =   1'b0;
localparam HIGH   =   1'b1;

reg[6:0] seg [15:0];
always @(negedge rst_n) begin
  seg[0] =   7'h3f;   // 0
  seg[1] =   7'h06;   // 1
  seg[2] =   7'h5b;   // 2
  seg[3] =   7'h4f;   // 3
  seg[4] =   7'h66;   // 4
  seg[5] =   7'h6d;   // 5
  seg[6] =   7'h7d;   // 6
  seg[7] =   7'h07;   // 7
  seg[8] =   7'h7f;   // 8
  seg[9] =   7'h6f;   // 9
  seg[10] =   7'h40;   // A
  seg[11] =   7'h7c;   // b
  seg[12] =   7'h39;   // C
  seg[13] =   7'h5e;   // d
  seg[14] =   7'h79;   // E
  seg[15] =   7'h71;   // F
end
   
reg [9:0] cnt = 1'b0;
always@(posedge clk or negedge rst_n) begin
  if(!rst_n) cnt <= 1'b0;
  else if(cnt>=(CNT_40KHz-1)) cnt <= 1'b0;
  else cnt <= cnt + 1'b1;
end

reg clk_40khz = 1'b0;
always@(posedge clk or negedge rst_n) begin
  if(!rst_n) clk_40khz <= 1'b0;
  else if(cnt<(CNT_40KHz>>1)) clk_40khz <= 1'b0;
  else clk_40khz <= 1'b1;
end

reg     [15:0]     data;
reg     [2:0]       cnt_main;
reg     [5:0]       cnt_write;
reg     [2:0]       state = IDLE;
always@(posedge clk_40khz or negedge rst_n) begin
  if(!rst_n) begin
      state <= IDLE;
      cnt_main <= 3'd0; cnt_write <= 6'd0;
      seg_din <= 1'b0; seg_sck <= LOW; seg_rck <= LOW;
  end else begin
      case(state)
          IDLE:begin  
                  state <= MAIN;
                  cnt_main <= 3'd0; cnt_write <= 6'd0;
                  seg_din <= 1'b0; seg_sck <= LOW; seg_rck <= LOW;
              end
          MAIN:begin
                  cnt_main <= cnt_main + 1'b1;
                  state <= WRITE;    
                  case(cnt_main)
                      3'd0: data <= {{dot_en[7],seg[dat_1]},dat_en[7]?8'hfe:8'hff};
                      3'd1: data <= {{dot_en[6],seg[dat_2]},dat_en[6]?8'hfd:8'hff};
                      3'd2: data <= {{dot_en[5],seg[dat_3]},dat_en[5]?8'hfb:8'hff};
                      3'd3: data <= {{dot_en[4],seg[dat_4]},dat_en[4]?8'hf7:8'hff};
                      3'd4: data <= {{dot_en[3],seg[dat_5]},dat_en[3]?8'hef:8'hff};
                      3'd5: data <= {{dot_en[2],seg[dat_6]},dat_en[2]?8'hdf:8'hff};
                      3'd6: data <= {{dot_en[1],seg[dat_7]},dat_en[1]?8'hbf:8'hff};
                      3'd7: data <= {{dot_en[0],seg[dat_8]},dat_en[0]?8'h7f:8'hff};
                      default: data <= {8'h00,8'hff};
                  endcase
              end
          WRITE:begin
                  if(cnt_write >= 6'd33) cnt_write <= 1'b0;
                  else cnt_write <= cnt_write + 1'b1;
                  case(cnt_write)
                      6'd0: begin seg_sck <= LOW; seg_din <= data[15]; end      
                      6'd1: begin seg_sck <= HIGH; end                          
                      6'd2: begin seg_sck <= LOW; seg_din <= data[14]; end
                      6'd3: begin seg_sck <= HIGH; end
                      6'd4: begin seg_sck <= LOW; seg_din <= data[13]; end
                      6'd5: begin seg_sck <= HIGH; end
                      6'd6: begin seg_sck <= LOW; seg_din <= data[12]; end
                      6'd7: begin seg_sck <= HIGH; end
                      6'd8: begin seg_sck <= LOW; seg_din <= data[11]; end
                      6'd9: begin seg_sck <= HIGH; end
                      6'd10: begin seg_sck <= LOW; seg_din <= data[10]; end
                      6'd11: begin seg_sck <= HIGH; end
                      6'd12: begin seg_sck <= LOW; seg_din <= data[9]; end
                      6'd13: begin seg_sck <= HIGH; end
                      6'd14: begin seg_sck <= LOW; seg_din <= data[8]; end
                      6'd15: begin seg_sck <= HIGH; end
                      6'd16: begin seg_sck <= LOW; seg_din <= data[7]; end
                      6'd17: begin seg_sck <= HIGH; end
                      6'd18: begin seg_sck <= LOW; seg_din <= data[6]; end
                      6'd19: begin seg_sck <= HIGH; end
                      6'd20: begin seg_sck <= LOW; seg_din <= data[5]; end
                      6'd21: begin seg_sck <= HIGH; end
                      6'd22: begin seg_sck <= LOW; seg_din <= data[4]; end
                      6'd23: begin seg_sck <= HIGH; end
                      6'd24: begin seg_sck <= LOW; seg_din <= data[3]; end
                      6'd25: begin seg_sck <= HIGH; end
                      6'd26: begin seg_sck <= LOW; seg_din <= data[2]; end
                      6'd27: begin seg_sck <= HIGH; end
                      6'd28: begin seg_sck <= LOW; seg_din <= data[1]; end
                      6'd29: begin seg_sck <= HIGH; end
                      6'd30: begin seg_sck <= LOW; seg_din <= data[0]; end
                      6'd31: begin seg_sck <= HIGH; end
                      6'd32: begin seg_rck <= HIGH; end                              
                      6'd33: begin seg_rck <= LOW; state <= MAIN; end
                      default: ;
                  endcase
              end
          default: state <= IDLE;
      endcase
  end
end

endmodule

本模块借鉴平台“STEP FPGA驱动基于74HC595的数码管模块”的例程,当某一时刻,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,其余5位数码管不显示。按照扫描的方式,一共分为6个时刻,段选端口分别对应输出6位数码管需要显示的字库数据,位选端口保持每个时刻只有1位数码管处于使能状态,6个时刻依次循环,当扫描频率足够高(例如当扫描频率等于100Hz)时,则在人眼看到的数码管显示就是连续的,我们看到的就是6个不同的数字。扫描显示的方式使用了14个I/O口控制,相对于简单的处理器来讲14个I/O口也是非常多了,这里又使用了一款常见的驱动芯片74HC595,74HC595是较为常用的串行转并行的芯片,内部集成了一个8位移位寄存器、一个存储器和8个三态缓冲输出。在最简单的情况下我们只需要控制3根引脚输入得到8根引脚并行输出信号,而且可以级联使用,我们使用3个I/O口控制两个级联的74HC595芯片,产生16路并行输出,连接到扫描显示的6位数码管上,可以轻松完成数码管驱动任务。
img

例程中只有6位数码管,我们只需要再增加2位传入数据(dat_7、dat_8)即可。

calculater模块:

本模块为此项目核心模块,为原创模块。下面进行分步讲解。

module calculate
(
  input                   clk_in,         //系统时钟
  input                   rst_n_in,       //系统复位,低有效
  input       [15:0]     key_in,         //矩阵按键列接口
   
  output reg [3:0]       seg_data_1,         //SEG1 数码管要显示的数据
  output reg [3:0]       seg_data_2,         //SEG2 数码管要显示的数据
  output reg [3:0]       seg_data_3,         //SEG3 数码管要显示的数据
  output reg [3:0]       seg_data_4,         //SEG4 数码管要显示的数据
  output reg [3:0]       seg_data_5,         //SEG5 数码管要显示的数据
  output reg [3:0]       seg_data_6,         //SEG6 数码管要显示的数据
  output reg [3:0]       seg_data_7,         //SEG6 数码管要显示的数据
  output reg [3:0]       seg_data_8,         //SEG6 数码管要显示的数据
  output reg [7:0]       seg_data_en,       //各位数码管数据显示使能,[MSB~LSB]=[SEG8~SEG1]
  output reg [7:0]       seg_dot_en         //各位数码管小数点显示使能,[MSB~LSB]=[SEG8~SEG1]
   
);

此为模块端口定义,key_in为输入的按键信号,通过模块计算得到seg_data_1-seg_data_8这8个要输出的数字信息,以及数码管小数点使能seg_dot_en和显示使能seg_data_en。

    //按键信息转化
  localparam ZERO = 16'b1110111111111111; //0
  localparam ONE   = 16'b1111101111111111; //1
  localparam TWO   = 16'b1111110111111111; //2
  localparam THREE = 16'b1111111011111111; //3
  localparam FOUR = 16'b1111111111101111; //4
  localparam FIVE = 16'b1111111111011111; //5
  localparam SIX   = 16'b1111111110111111; //6
  localparam SEVEN = 16'b1111111111111110; //7
  localparam EIGHT = 16'b1111111111111101; //8
  localparam NINE = 16'b1111111111111011; //9
  localparam PLUS = 16'b0111111111111111; //+
  localparam MINUS = 16'b1111011111111111; //-
  localparam MUL   = 16'b1111111101111111; //×
  localparam DIV   = 16'b1111111111110111; //÷
  localparam EQL   = 16'b1011111111111111; //=
   
  //状态机状态定义
  localparam STATE1 =4'b0001;//初始状态
  localparam STATE2 =4'b0010;//接收完第一个数字
  localparam STATE3 =4'b0011;//接收完第二个数字
  localparam STATE4 =4'b0100;//接收运算符
  localparam STATE5 =4'b0101;//接收完第三个数字
  localparam STATE6 =4'b0110;//接收完第四个数字
  localparam STATE7 =4'b0111;//显示结果
   
  //运算状态定义
  localparam STATE_PLUS =2'b00;//加状态
  localparam STATE_MINUS =2'b01;//简状态
  localparam STATE_MUL   =2'b10;//乘状态
  localparam STATE_DIV   =2'b11;//除状态
   
  reg [15:0]num1;//输入的第一个值
  reg [15:0]num2;//输入的第二个值
  reg [19:0]answer;//计算结果
   
  reg [3:0]state;//状态机状态
  reg [1:0]state_o;//运算状态

  reg [15:0] num1_1;//num1×10000(结果为4位小数去掉小数点)
   
  reg [19:0]answer_r;
   
  reg [3:0]answer_001num = 4'b0;
  reg [3:0]answer_01num = 4'b0;
  reg [3:0]answer_1num = 4'b0;
  reg [3:0]answer_10num = 4'b0;
  reg [3:0]answer_100num = 4'b0;
  reg [3:0]answer_1000num = 4'b0;
   
   

此部分为变量定义部分,按键信息转化中每一个16比特位的信号都对应着注释中的实际意义(根据项目所给的计算器图确定位置)。为保证运行逻辑不出现问题此模块以状态机为主要逻辑,定义了7个状态(比较C语言的写法,在FPGA中实测不推荐使用这么多的状态,建议多开辟几个always块,更方便实现目标)。

    reg [15:0]pre_key_in;//前一次的输入
  reg [15:0]now_key_in;//后一次的输入
   
  wire key_in_state;
   
  always@(posedge clk_in or negedge rst_n_in) begin
  if(!rst_n_in) begin
      now_key_in <= 16'b0;
      pre_key_in <= 16'b0;
  end else begin
      now_key_in <= key_in;
      pre_key_in <= now_key_in;
  end
  end
   
  assign key_in_state = (&pre_key_in)&(~(&now_key_in));   //检测按键信号上升沿
   

为防止calculater模块的运行时钟(12MHz)与Array_KeyBoard模块的运行时钟(200Hz)不同频而导致按键按下一次会连续给calculater模块赋值的情况发生,特写出此部分检测按键信号的上升沿,只有当按键由全部未按下到有一个被按下的情况时key_in_state才会被置1,再根据key_in_state的状态进行输入数字的判断与保存。

        if(key_in_state ) begin
          case(state)
          STATE1:begin
                  state <= STATE2;
                  seg_data_en <= 8'b00000001;
                  case(key_in)
                      ZERO: begin    
                          seg_data_8 <= 4'd0;
                          num1<=16'd0;end
                      ONE : begin    
                          seg_data_8 <= 4'd1;
                          num1<=16'd1;end
                      TWO :   begin    
                          seg_data_8 <= 4'd2;
                          num1<=16'd2;end
                      THREE : begin    
                          seg_data_8 <= 4'd3;
                          num1<=16'd3;end
                      FOUR :   begin    
                          seg_data_8 <= 4'd4;
                          num1<=16'd4;end
                      FIVE : begin    
                          seg_data_8 <= 4'd5;
                          num1<=16'd5;end
                      SIX :   begin    
                          seg_data_8 <= 4'd6;
                          num1<=16'd6;end
                      SEVEN :begin    
                          seg_data_8 <= 4'd7;
                          num1<=16'd7;end
                      EIGHT : begin    
                          seg_data_8 <= 4'd8;
                          num1<=16'd8;end
                      NINE :   begin    
                          seg_data_8 <= 4'd9;
                          num1<=16'd9;end
                      default : begin
                          state <= STATE1;
                          seg_data_en <= 8'b0;
                          num1<=0;
                          end
                  endcase
          end
               
          STATE2:begin
                  seg_data_7 <= seg_data_8;
                  case(key_in)
                      ZERO: begin  
                          seg_data_8 <= 4'd0;
                          num1<=(16'd10*num1) + 16'd0;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      ONE : begin  
                          seg_data_8 <= 4'd1;
                          num1<=(16'd10*num1) + 16'd1;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      TWO : begin  
                          seg_data_8 <= 4'd2;
                          num1<=(16'd10*num1) + 16'd2;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      THREE : begin  
                          seg_data_8 <= 4'd3;
                          num1<=(16'd10*num1) + 16'd3;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      FOUR : begin  
                          seg_data_8 <= 4'd4;
                          num1<=(16'd10*num1) + 16'd4;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      FIVE : begin    
                          seg_data_8 <= 4'd5;
                          num1<=(16'd10*num1) + 16'd5;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      SIX :   begin  
                          seg_data_8 <= 4'd6;
                          num1<=(16'd10*num1) + 16'd6;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      SEVEN : begin  
                          seg_data_8 <= 4'd7;
                          num1<=(16'd10*num1) + 16'd7;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      EIGHT : begin  
                          seg_data_8 <= 4'd8;
                          num1<=(16'd10*num1) + 16'd8;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      NINE : begin  
                          seg_data_8 <= 4'd9;
                          num1<=(16'd10*num1) + 16'd9;
                          seg_data_en <= 8'b00000011;
                          state <= STATE3;end
                      PLUS :begin
                          num1 <= num1;
                          seg_data_en <= 8'b00000001;
                          state <= STATE4;
                          state_o <= STATE_PLUS;
                          end
                      MINUS :begin
                          num1 <= num1;
                          seg_data_en <= 8'b00000001;
                          state <= STATE4;
                          state_o <= STATE_MINUS;
                          end
                      MUL :begin
                          num1 <= num1;
                          seg_data_en <= 8'b00000001;
                          state <= STATE4;
                          state_o <= STATE_MUL;
                          end
                      DIV :begin
                          num1 <= num1;
                          seg_data_en <= 8'b00000001;
                          state <= STATE4;
                          state_o <= STATE_DIV;
                          end
                      default : begin
                          state <= STATE1;
                          seg_data_en <= 8'b0;
                          seg_data_7 <= 4'b0;
                          seg_data_8 <= 4'b0;
                          num1<=0;
                          end
                  endcase
          end
               
          STATE3:begin
                  state <= STATE4;
                  case(key_in)
                      PLUS : state_o <= STATE_PLUS;
                      MINUS : state_o <= STATE_MINUS;
                      MUL :   state_o <= STATE_MUL;
                      DIV :   state_o <= STATE_DIV;
                      default : state <= STATE3;
                  endcase
          end
               
          STATE4:begin
                  ...//同STATE1
          end
          STATE5:begin
                  ...//同STATE2
          end    
          endcase

这一部分只有当输入的按键信号在上升沿时才会运行,要注意的是数码管从左到右最左边对应的使能是seg_data_en的高位,对应的数字是seg_data_1,最右边对应的是seg_data_en的低位与seg_data_8。在STATE1时保存num1的第一位数字,这是数码管只显示一位数字;在STATE2时若输入数字则完成num1的赋值,数码管显示num1(十位在左),还需再进入STATE3输入运算符完成运算状态的赋值。若为运算符则显示保持一位数不变,同时记录输入的运算。STATE4、STATE5功能与STATE1、STATE2近似,为num2赋值。

else begin
  if(state==STATE6) begin
            if(key_in == EQL)begin
            state <= STATE7;
             
            case(state_o)
                STATE_PLUS:begin
                    answer <= num1 + num2;
                 
                end
             
                STATE_MINUS:begin
                    if(num1 >= num2)begin
                        answer <= num1 - num2;
                    end
                    else begin
                        answer <= num2 - num1;
                    end
                 
                end
                 
                STATE_MUL:begin
                    answer <= num1 * num2;
                   
                end
                 
                STATE_DIV:begin
                    DIV_flag <= 1'b1;
                     
                end
            endcase
      end
     
      end
     
      else if(state==STATE7) begin
        if(temp==0)begin
                      seg_data_5 <= 4'd0;
                      seg_data_6 <= 4'd0;
                      seg_data_7 <= 4'd0;
                      seg_data_8 <= 4'd0;
                      seg_data_1 <= 4'd0;
                      seg_data_2 <= 4'd0;
                      seg_data_3 <= 4'd0;
                      seg_data_4 <= 4'd0;
                       
                      num1_1 <= 16'b0;
              num1_2 <= 16'b0;
              num1_3 <= 16'b0;
              num1_4 <= 16'b0;
             
                      answer_10num <= 4'b0;
                      answer_1num <= 4'b0;
                      answer_01num <= 4'b0;
                      answer_001num <= 4'b0;
                      answer_0001num <= 4'b0;
                      answer_00001num <= 4'b0;
                       
                       
                      temp<=1;
                       
                  end
       
                  else if(DIV_flag && (done_flag==0))begin//除法运算的显示
                      if(!DIV_done_flag)begin
                          case(DIV_state)
                              DIV_state0:begin
                                  if(num1 >= num2)begin
                                      num1 <= num1 - num2;
                                      answer_1num <= answer_1num + 4'b1;
                                  end else begin
                                      num1 <= num1 * 16'd10;
                                      DIV_state <= DIV_state1;
                                       
                                  end
                              end
                               
                              DIV_state1:begin
                                  if(num1 >= num2)begin
                                      num1 <= num1 - num2;
                                      answer_01num <= answer_01num + 4'b1;
                                       
                                   
                                  end else begin
                                      num1 <= num1 * 16'd10;
                                      DIV_state <= DIV_state2;
                                       
                                  end
                              end
                               
                              DIV_state2:begin
                                  if(num1 >= num2)begin
                                      num1 <= num1 - num2;
                                      answer_001num <= answer_001num + 4'b1;
                                  end else begin
                                      num1 <= num1 * 16'd10;
                                      DIV_state <= DIV_state3;
                                  end
                              end
                               
                              DIV_state3:begin
                                  if(num1 >= num2)begin
                                      num1 <= num1 - num2;
                                      answer_0001num <= answer_0001num + 4'b1;
                                  end else begin
                                      num1 <= num1 * 16'd10;
                                      DIV_state <= DIV_state4;
                                  end
                              end
                               
                              DIV_state4:begin
                                  if(num1 >= num2)begin
                                      num1 <= num1 - num2;
                                      answer_00001num <= answer_00001num + 4'b1;
                                  end else begin
                                      DIV_state <= DIV_state0;
                                      DIV_done_flag <= 1'b1;
                                  end
                              end
                           
                          endcase
                      end
                      else begin
                          if(answer_1num >=10)begin
                              answer_10num <= answer_10num +1'b1;
                              answer_1num <= answer_1num - 4'd10;
                          end else if(answer_10num)begin
                              seg_dot_en <= 8'b00010000;
                              seg_data_en <= 8'b00111111;
                              seg_data_3 <= answer_10num;
                              seg_data_4 <= answer_1num;
                              seg_data_5 <= answer_01num;
                              seg_data_6 <= answer_001num;
                              seg_data_7 <= answer_0001num;
                              seg_data_8 <= answer_00001num;
                              done_flag <= 1'b1;
                              DIV_done_flag <= 1'b0;
                              DIV_flag <= 1'b0;
                          end else begin
                              seg_dot_en <= 8'b00010000;
                              seg_data_en <= 8'b00011111;
                              seg_data_4 <= answer_1num;
                              seg_data_5 <= answer_01num;
                              seg_data_6 <= answer_001num;
                              seg_data_7 <= answer_0001num;
                              seg_data_8 <= answer_00001num;
                              done_flag <= 1'b1;
                              DIV_done_flag <= 1'b0;
                              DIV_flag <= 1'b0;
                          end
                      end    
                       
                 
        end
        else if(!DIV_flag && (done_flag==0)) begin//非除法运算的显示
       
            if(answer>=16'd1000)begin
                          answer <= answer - 16'd1000;
                          seg_data_5<=seg_data_5 + 4'd1;
                     
                      end
                      else if(answer>=16'd100 )begin
                          answer<=answer - 16'd100;
                          seg_data_6<=seg_data_6 + 4'd1;
                      end
                      else if(answer>=16'd10 )begin
                          answer<=answer - 16'd10;
                          seg_data_7<=seg_data_7 + 4'd1;
                      end
                      else if(answer>=16'd1 )begin//最后一个个位数,显示完毕
                          answer<=answer - 16'd1;
                          seg_data_8<=seg_data_8 + 4'd1;
                      end
                      else begin
                          if(num1 < num2 && state_o == STATE_MINUS)begin
                              if(seg_data_7)begin
                                  seg_data_en<=8'b0000_0111;
                                  seg_data_6<=4'd10;
                              end
                              else begin
                                  seg_data_en<=8'b0000_0011;
                                  seg_data_7<=4'd10;
                              end
                               
                          end
                          else begin
                              if(seg_data_5)begin
                                  seg_data_en<=8'b0000_1111;
                              end
                              else if(seg_data_6)begin
                                  seg_data_en<=8'b0000_0111;
                              end
                              else if(seg_data_7)begin
                                  seg_data_en<=8'b0000_0011;
                              end
                              else if(seg_data_8)begin
                                  seg_data_en<=8'b0000_0001;
                              end
                          end
                          done_flag <= 1'b1;
                           
                      end
       
        end
          else begin
                  state <= STATE1;
                done_flag <= 1'b0;
                temp<=0;
                num1<=0;
                num2<=0;
        end
      end
end
end

这里为运算与显示部分,因为有循环进入模块进行赋值的操作因此不能只在按键信号上升沿才触发。除法的实现先将num1(被除数)不断循环减去 num2,同时个位数循环+1直至 num1 < num2,再将num1*10后继续操作得到十分位数,依次进行直至计算出万分位(4位小数)完成除法。

达到answer后将answer各位数字分别赋值给seg_data_8-seg_data_1,分离的方法与做除法的方法几乎相同,通过不断循环进入模块,每次进入都将answer减去 10n(如计算千位就减去103,计算百位就减去102。)完成拆分后将所得数字传给display模块。

Top模块

module Top
(
  input           clk_in,
  input           rst_n,
  input           [3:0]   KeyBoard_col,//矩阵按键列接口
   
  output         [3:0]   row_out,   //消抖后的信号
  output         top_rclk_out,       //74HC595的RCK管脚
  output         top_sclk_out,       //74HC595的SCK管脚
  output         top_sdio_out       //74HC595的SER管脚

);

wire   [15:0]     key_location; //消抖后的信号

wire   [3:0]       top_seg_data_1;     //SEG1 数码管要显示的数据
wire   [3:0]       top_seg_data_2;     //SEG2 数码管要显示的数据
wire   [3:0]       top_seg_data_3;     //SEG3 数码管要显示的数据
wire   [3:0]       top_seg_data_4;     //SEG4 数码管要显示的数据
wire   [3:0]       top_seg_data_5;     //SEG5 数码管要显示的数据
wire   [3:0]       top_seg_data_6;     //SEG6 数码管要显示的数据
wire   [3:0]       top_seg_data_7;     //SEG7 数码管要显示的数据
wire   [3:0]       top_seg_data_8;     //SEG8 数码管要显示的数据

wire   [7:0]       top_seg_data_en;   //各位数码管数据显示使能,[MSB~LSB]=[SEG8~SEG1]
wire   [7:0]       top_seg_dot_en;     //各位数码管小数点显示使能,[MSB~LSB]=[SEG8~SEG1]

Array_KeyBoard Array_KeyBoard1
(
  .clk_in         (clk_in       ),
  .rst_n_in       (rst_n       ),
  .col           (KeyBoard_col),
   
  .row           (row_out     ),       //矩阵按键行接口
  .key_out       (key_location )         //消抖后的信号

);

calculate calculate1
(
  .clk_in         (clk_in       ),
  .rst_n_in       (rst_n       ),
  .key_in         (key_location ),           //矩阵按键列接口
   
  .seg_data_1     (top_seg_data_1),           //SEG1 数码管要显示的数据
  .seg_data_2     (top_seg_data_2),           //SEG2 数码管要显示的数据
  .seg_data_3     (top_seg_data_3),           //SEG3 数码管要显示的数据
  .seg_data_4     (top_seg_data_4),           //SEG4 数码管要显示的数据
  .seg_data_5     (top_seg_data_5),           //SEG5 数码管要显示的数据
  .seg_data_6     (top_seg_data_6),           //SEG6 数码管要显示的数据
  .seg_data_7     (top_seg_data_7),           //SEG6 数码管要显示的数据
  .seg_data_8     (top_seg_data_8),           //SEG6 数码管要显示的数据
  .seg_data_en   (top_seg_data_en),         //各位数码管数据显示使能,[MSB~LSB]=[SEG8~SEG1]
  .seg_dot_en     (top_seg_dot_en)           //各位数码管小数点显示使能,[MSB~LSB]=[SEG8~SEG1]
   
);

display display1
(
  .clk           (clk_in       ),           //系统时钟
  .rst_n         (rst_n       ),           //系统复位,低有效
  .dat_1         (top_seg_data_1),           //SEG1 数码管要显示的数据
  .dat_2         (top_seg_data_2),           //SEG2 数码管要显示的数据
  .dat_3         (top_seg_data_3),           //SEG3 数码管要显示的数据
  .dat_4         (top_seg_data_4),           //SEG4 数码管要显示的数据
  .dat_5         (top_seg_data_5),           //SEG5 数码管要显示的数据
  .dat_6         (top_seg_data_6),           //SEG6 数码管要显示的数据
  .dat_7         (top_seg_data_7),           //SEG6 数码管要显示的数据
  .dat_8         (top_seg_data_8),           //SEG6 数码管要显示的数据
  .dat_en       (top_seg_data_en),           //各位数码管数据显示使能,[MSB~LSB]=[SEG8~SEG1]
  .dot_en         (top_seg_dot_en),           //各位数码管小数点显示使能,[MSB~LSB]=[SEG8~SEG1]
   
  .seg_rck       (top_rclk_out),     //74HC595的RCK管脚
  .seg_sck       (top_sclk_out),     //74HC595的SCK管脚
  .seg_din       (top_sdio_out)     //74HC595的SER管脚

);

endmodule


6.仿真波形图

top文件仿真

img


Array_KeyBoard模块仿真

img

display模块仿真

img

calculater模块仿真

img

7.FPGA的资源利用说明

以及使用了矩阵按键外设模块、数码管外设模块。

附件下载
archive.zip
项目代码
团队介绍
一个菜菜的电子热爱者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号