电子森林2024寒假一起练项目报告
两位十进制加、减、乘、除计算器
一、项目需求
实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由4×4矩阵键盘来控制。其中每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
二、需求分析
本项目需要实现两位十进制数的加、减、乘、除运算,以4×4矩阵键盘作为输入,数码管作为显示输出。矩阵键盘的按键值读取通过行扫描的方式实现,四则运算中的加、减、乘可以通过’+’、’-‘、‘*’运算符对读取到的两个运算数进行运算,只有除法部分需要单独编写一个除法模块实现除法运算。对于两位十进制数的显示,可以通过对运算数移位后再赋值的方式实现,即定义一个8位信号用于读取按键值,每次按下数字键后,先将该信号左移4位,再对低4位进行赋值,即可实现右侧显示个位、左侧显示十位的效果。
三、实现方式
首先对该设计进行模块划分,根据设计需求初步划分为:①矩阵键盘驱动模块、②按键消抖模块、③状态转换模块、④输入运算数寄存模块、⑤除法模块、⑥四则运算模块、⑦根据当前状态和运算数得出显示数据的模块、⑧二进制码转BCD码模块、⑨hc595驱动模块、⑩数码管动态显示模块。最后用顶层模块将各模块连接起来。
其中①、②、⑧、⑨、⑩属于常用模块,这里不做过多讲解。
③状态转换模块:对处理过程中的不同状态进行划分,并根据按键输入进行状态跳转。比如读取第一个运算数时是一个状态,读取第二个运算数时是一个状态,如果输入第二个操作数之后按下’=’按键,则进入运算结果显示状态,如果输入第二个操作数之后再次按下’+’、’-’、’*’、‘/’按键,则先计算出当前两个操作数的运算结果赋值给操作数1,然后再重新进入读取操作数2的状态,若输入操作数2后仍按下运算符按键则重复上述过程,直至按下‘=’输出最终运算结果。
④输入运算数寄存模块:根据按键输入和当前状态,分别对操作数1、操作数2和运算符寄存器进行赋值并将其输出到后续模块进行处理。
⑤除法模块:采用‘恢复余数法’进行除法运算,以生成可综合的除法运算电路。该方法采用移位后比较大小的方式进行商0或商1,总共移位除数位宽次数后得到操作数1/操作数2向下取整的除法运算结果。
⑥四则运算模块:根据当前状态值、操作数1、操作数2以及运算符寄存值,在适当的状态时计算出当前运算结果的数值,其中减法运算部分还要对操作数大小进行比较以确定是否需要生成负号标志位。
⑦根据当前状态和运算数得出显示数据的模块:该模块根据当前状态、操作数值等参数确定最终输出给数码管的显示数值。首先将输入的二进制码操作数和运算结果值通过模块⑧转换为BCD码形式,然后根据状态值和负号标志位是否有效来确定输出给数码管的32位bcd码,其中4’he用作显示负号,4’hf用作隐去目前没用到的数码管。
四、功能框图
完整RTL结构图如下图所示,左侧不完整部分均为按键消抖模块例化,图中保留三个作为示意。
五、代码及说明
这里只给出部分关键代码块及其说明,完整代码见后续附件部分。
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
operand1<=8'd0;
else if(state==S1 && key_operation)
operand1<=8'd0;
else if(state==S1 || state==S6)
begin
case(key_value)
10'b10_0000_0000:begin operand1<={operand1[3:0] , 4'd9}; end
10'b01_0000_0000:begin operand1<={operand1[3:0] , 4'd8}; end
10'b00_1000_0000:begin operand1<={operand1[3:0] , 4'd7}; end
10'b00_0100_0000:begin operand1<={operand1[3:0] , 4'd6}; end
10'b00_0010_0000:begin operand1<={operand1[3:0] , 4'd5}; end
10'b00_0001_0000:begin operand1<={operand1[3:0] , 4'd4}; end
10'b00_0000_1000:begin operand1<={operand1[3:0] , 4'd3}; end
10'b00_0000_0100:begin operand1<={operand1[3:0] , 4'd2}; end
10'b00_0000_0010:begin operand1<={operand1[3:0] , 4'd1}; end
10'b00_0000_0001:begin operand1<={operand1[3:0] , 4'd0}; end
default:operand1<=operand1;
endcase
end
end
上述代码块主要用于实现,输入十位数字时,该数字显示在右侧数码管,接着输入个位数字后,十位数字跳转到左侧数码管,个位数字显示在右侧数码管。该功能主要通过先移位后赋值的方式实现,定义一个8位的变量,每4位代表一个数字,每次读取按键并赋值之前先将该变量左移4位,再对其低4位进行赋值,以实现上述功能。
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
result<='d0;
negative_flag<=1'b0;
end
else if(state==S1)
begin
result<='d0;
negative_flag<=1'b0;
end
else if(state==S3||state==S4)
begin
case(operation_d1)
2'd0:
begin
if(negative_flag)
begin
if(i_operand1>i_operand2)
begin
result <= i_operand1 - i_operand2;
negative_flag<=1'b1;
end
else
begin
result <= i_operand2 - i_operand1;
negative_flag<=1'b0;
end
end
else
begin
result <= i_operand1 + i_operand2;
negative_flag<=1'b0;
end
end
2'd1:
begin
if(negative_flag)
begin
result <= i_operand1 + i_operand2;
negative_flag<=1'b1;
end
else
begin
if(i_operand1<i_operand2)
begin
result <= i_operand2 - i_operand1;
negative_flag<=1'b1;
end
else
begin
result <= i_operand1 - i_operand2;
negative_flag<=1'b0;
end
end
end
2'd2:
begin
result <= i_operand1 * i_operand2;
negative_flag <= negative_flag;
end
2'd3:
begin
result <= yshang;
negative_flag <= negative_flag;
end
default:result <= result;
endcase
end
end
上述代码块用于实现计算功能,其中operation_d1的2'd0为加法、2'd1为减法、2'd2为乘法、2'd3为除法。可以看到,该设计中考虑了负数参与运算的情况,同时加减乘三种运算均通过运算符实现,只有除法运算有专门的除法模块。
module div(
input wire sys_clk,
input wire rst_n ,
input wire [26:0] A,
input wire [26:0] B,
input wire ready,
output reg [26:0] shang,
output reg [26:0] yushu,
output reg valid
);
reg work_flag;
reg [26:0] yushu_qian;
reg [53:0] chushu;
reg [4:0] cnt;
reg [26:0] shang_qian;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
work_flag <= 1'd0;
else if(cnt == 'd27)
work_flag <= 1'd0;
else if(ready == 1'd1)
work_flag <= 1'd1;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
yushu_qian <= 27'd0;
else if(work_flag == 1'd0)
yushu_qian <= A;
else if(work_flag == 1'd1)
begin
if(yushu_qian >= chushu)
yushu_qian <= yushu_qian - chushu;
else
yushu_qian <= yushu_qian;
end
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
chushu <= 54'd0;
else if(work_flag == 1'd0)
chushu <= {B,27'd0};
else if(work_flag == 1'd1)
chushu <= chushu>>1;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
cnt <= 'd0;
else if(work_flag == 1'd0)
cnt <= 'd0;
else
cnt <= cnt + 'd1;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
shang_qian <= 27'd0;
else if(work_flag == 1'd0)
shang_qian <= 27'd0;
else if(work_flag == 1'd1)
begin
if(yushu_qian >= chushu)
shang_qian[27-cnt] <= 1'd1;
else
shang_qian[27-cnt] <= 1'd0;
end
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
shang <= 28'd0;
else if(cnt == 'd28)
shang = shang_qian;
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
yushu <= 28'd0;
else if(cnt == 'd28)
yushu <= yushu_qian[26:0];
always@(posedge sys_clk,negedge rst_n)
if(!rst_n)
valid <= 'd0;
else if(cnt == 'd28)
valid <= 'd1;
else
valid <= 'd0;
endmodule
以上为除法运算模块,采用‘恢复余数法’进行除法运算,以生成可综合的除法运算电路。该方法采用移位后比较大小的方式进行商0或商1,总共移位除数位宽次数后得到操作数1/操作数2向下取整的除法运算结果。
六、仿真波形图
这里采用modelsim进行仿真测试,各模块完整testbench见后续附件。
③state_transfer_tb:
④data_read_tb:该模块需要state_transfer模块的输出信号state作为输入,所以在testbench中将例化这两个模块一起进行仿真测试;
⑤div_tb:该除法模块输出为商数和余数,这里只用到商数,即除法运算结果向下取整。
⑥calculate_tb:由于该模块的输入需要用到前面的输出,于是将③④⑤⑥全部例化进行仿真。
⑦data_to_disp_tb:根据当前state,将二进制操作数转为bcd码并在合适的时机赋值给data_disp信号以便于传递给数码管进行显示,其中4’he用于显示负号,4’hf用于隐去未使用的数码管。
七、FPGA资源利用情况
在WebIDE中的run.log日志文件中可以得到资源利用说明:
可以看出该设计使用了大约一半的LUT资源和20%的寄存器资源,其中大部分资源消耗在除法运算和二进制码转bcd码部分。
八、演示视频
演示视频上传至以下bilibili平台链接:
【电子森林2024年寒假在家一起练,平台二任务一】 https://www.bilibili.com/video/BV1wx421179K/?share_source=copy_web&vd_source=c6992e78df10d9d4074e9b13c476b65c