两位十进制加、减、乘、除计算器的任务报告
@本项目完全在WebIDE平台上进行设计与运行
1.项目需求
实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,4×4键盘按键分配如下图所示。
基本需求:
运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。在WebIDE平台上完成代码编译与测试。
2.需求分析
项目需求可以分为两部分,一是完成加减乘除的运算并得到正确的结果,二是将输入的数字与得到的结果正确显示在数码管上。完成加减乘除的运算中加减乘是相对容易的,而除法相对比较困难。完成数码管的显示原理较为容易理解,需要按照题目要求合理设置显示顺序以及确保显示的正确性。
3.实现的方式
实现本项目功能主要可以分为三部分:准确获取按键按下的位置、将按键信息处理为数字信息并计算、将数字显示出来。
准确获取按键按下的位置:
在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式,使用行线和列线分别连接到按键开关的两端,这样我们就可以通过4根行线和4根列线(共8个I/O口)连接16个按键。
上图为4×4矩阵按键的硬件电路图,可以看到4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4),同时列线通过上拉电阻连接到VCC电压(3.3V),对于矩阵按键来讲:
- 4根行线是输入的,是由FPGA控制拉高或拉低,
- 4根列线数输出的,是由4根行线的输入及按键的状态决定,输出给FPGA
按照扫描的方式,一共分为4个时刻,分别对应4根行线中的一根拉低,4个时刻依次循环,这样就完成了矩阵按键的全部扫描检测,通过对按键扫描得到的信号进行定义便可以得到按键按下所代表的输入。
将按键信息处理为数字信息并计算:
通过对按键读取模块传入的数据进行信号与数的一一对应将前后两次键入的数字存入num1与num2中,对num1与num2进行加减乘除的运算,将num1、num2与计算结果的各个位进行拆分并依次传入到显示模块
将数字显示出来:
这里显示数字使用基于74HC595的数码管模块,共8位数码显示块,通过对传入的数据与使能标志位的控制将num1、num2与计算结果的各个位按要求依次显示在数码管上。
4.功能框图
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位数码管上,可以轻松完成数码管驱动任务。
例程中只有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文件仿真
Array_KeyBoard模块仿真
display模块仿真
calculater模块仿真
7.FPGA的资源利用说明
以及使用了矩阵按键外设模块、数码管外设模块。
