1、项目需求
项目需求实现两位十进制数加减乘除,运算数及加减乘除由矩阵按键输入;使用8位数码管显示,每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
2、需求分析
硬件方面,扩展板已经搭载了必要的硬件外设,开箱即用;
软件方面,可分为输入,显示,运算三部分;
(1)、矩阵键盘输入处理部分,可分为数字按键,和功能按键两部,将键值分别存储起来,在对应功能按键按下后进行运算和显示;
(2)、显示部分;经查原理图,需要编写595驱动代码来驱动数码管;
(3)、运算部分;verilog中加减乘法都是可以直接使用‘+’,‘-‘ , ’*‘ 来运算的,简单的除法也可以使用 ’/ ‘来计算,或者使用循环移位减法来求,原理和小学时候学习的除法列竖式一样,不过在此处替换成二进制表示。
3、实现方式
小脚丫开发板配备了非常方便的webide,网站页面简洁,还有丰富的例程,讲解也十分清楚。本项目大部分代码实现也是借鉴官网提供的例程。
(1)、矩阵键盘输入:矩阵键盘需要用到行列扫描以及按键消抖,所以在代码中需要构建一个时钟信号,用来定时扫描,消抖。在网站资料中看到有时钟分频示例divid_mod,以及按键消抖示例 debounce。
(2)、8位数码管显示:
此处借鉴小脚丫开源社区中矩阵键盘模块例程;
(3)、进制转换:
另外需要注意的是,我们键盘输入的数字是BCD码,运算时候需要转换成十进制数字,运算完成后输出显示时又需要将二进制转换成bcd码。二进制转BCD码的例程也可以在webide中找到“BCD转码”
4、功能框图
确定了基本需求以及实现方式,接下来就是项目规划了。代码架构如下:
矩阵按键输出键值给TOP经过滤波模块的debounce模块滤波后给,键值处理模块keyhandle.v.在该模块检测到加减乘除按键按下后计算对应结果,输出给bcd.v模块,完成进制转换后,返回顶层文件TOP,最后下发给数码管显示结果。
5、代码及说明
项目中矩阵键盘驱动代码,以及数码管显示代码借鉴官网示例,此处不做过多介绍;
项目主要涉及的代码有:按键处理代码,和加减乘除以及进制转换代码;
(1)、按键处理相关
为了方便调试,我将矩阵按键输出的按键键值进行了重映射:
wire [4:0] operation; //按键键值重定向
assign operation[0]=key_out[3]; //加号
assign operation[1]=key_out[7]; //减号
assign operation[2]=key_out[11]; //乘号
assign operation[3]=key_out[15]; //除号
assign operation[4]=key_out[2]; //等于号
wire [9:0] keyboard_num; //数字按键重定向
assign keyboard_num[0]=key_out[0]; //'0'
assign keyboard_num[1]=key_out[4]; //'1'
assign keyboard_num[2]=key_out[5]; //'2'
assign keyboard_num[3]=key_out[6]; //'3'
assign keyboard_num[4]=key_out[8]; //'4'
assign keyboard_num[5]=key_out[9]; //'5'
assign keyboard_num[6]=key_out[10]; //'6'
assign keyboard_num[7]=key_out[12]; //'7'
assign keyboard_num[8]=key_out[13]; //'8'
assign keyboard_num[9]=key_out[14]; //'9'
数字按键获取键值:
wire num_pressed; //定义按键按下的总监控线
assign num_pressed = keyboard_num[0] & keyboard_num[1] & keyboard_num[2] & keyboard_num[3]
& keyboard_num[4] & keyboard_num[5] & keyboard_num[6] & keyboard_num[7]
& keyboard_num[8] & keyboard_num[9];
always @(posedge num_pressed or negedge rst ) //任意数字按键按下
begin
case(keyboard_num) //判断数字按键键值
10'b1111111110: keyboard_value<=4'd0;
10'b1111111101: keyboard_value<=4'd1;
10'b1111111011: keyboard_value<=4'd2;
10'b1111110111: keyboard_value<=4'd3;
10'b1111101111: keyboard_value<=4'd4;
10'b1111011111: keyboard_value<=4'd5;
10'b1110111111: keyboard_value<=4'd6;
10'b1101111111: keyboard_value<=4'd7;
10'b1011111111: keyboard_value<=4'd8;
10'b0111111111: keyboard_value<=4'd9;
default: keyboard_value<=keyboard_value+1'b0;
endcase
end
加减乘除按键获取键值:当我按照上述数字按键键值来出来加减乘除时发现,键值存储不了,所以改了一种键值获取方法如下。
wire operand_pressed; //定义功能按键按下的总监控线
assign operand_pressed = operation[0] & operation[1] & operation[2] & operation[3];
reg [31:0]operand_temp; //功能按键历史缓存
always @(posedge operand_pressed or negedge rst) //功能按键动作缓存 一组加减乘除共四个信号,每次按下存储一组信号,
begin
if(!rst)
operand_temp[27:0]<=28'b0; //清除数字按键缓存;
else
operand_temp[27:0]<={operand_temp[23:0],operand_stage}; //左移一个数码管位并获取最新数字键值
end
reg [3:0] now_operand; //存储最新功能按键键值; 取值为 1,2,4,8 对应 加、 减、 乘、 除、
always @(negedge clk or negedge rst) //判断最新按键
begin
if(!rst)
now_operand<=4'b0000; //当前记录的功能按键键值清零
else
if (operand_temp[3:0]==operand_temp[7:4]) //若当前按键与前一次按键键值相等,则保持当前键值;
now_operand=now_operand;
else if(operand_temp[3:0]>operand_temp[7:4]) //若当前按键与前一次按键键值不等,两者相减获得更新位 ;
now_operand=operand_temp[3:0]-operand_temp[7:4];
else
now_operand=operand_temp[7:4]-operand_temp[3:0];
end
(2)、加减乘除运算:当加减乘除相应按键按下时,立刻计算出结果并存储起来。
reg [3:0]operand_stage;
reg equal_pressd;
assign result_down=equal_pressd;
always @(posedge key_out[2]) //等于运算
begin
equal_pressd<=~equal_pressd; //标志位取反,切换结果与运算数
end
reg [14:0] operand_add; //存储加法结果
always @(negedge key_out[3]) //加运算
begin
operand_stage[0]<=~operand_stage[0];
operand_add<=operand_a+operand_b;
end
reg [14:0] operand_subtract; //存储减法结果
always @(negedge key_out[7]) //减运算
begin
operand_stage[1]<=~operand_stage[1];
if(operand_a>=operand_b)
operand_subtract<=operand_a-operand_b;
else
operand_subtract<=operand_b-operand_a+15'd10000; //加10000表示结果为负
end
reg [14:0] operand_multiply; //存储乘法结果
always @(negedge key_out[11]) //乘运算
begin
operand_stage[2]<=~operand_stage[2];
operand_multiply<=operand_a*operand_b;
end
reg [14:0] operand_divide; //存储除法结果
always @(negedge key_out[15]) //除运算
begin
operand_stage[3]<=~operand_stage[3];
if(operand_b!=4'b0000)
operand_divide<=operand_a/operand_b;
else
operand_divide<=15'd9999; //若除以0,则显示最大数9999
end
(3)、数值转换,将BCD码转换为二进制存储
gea<=display_temp[11:8]; //获得个位数
shia<=display_temp[15:12]; //获得十位数
operand_a<=(shia<<3)+(shia<<1)+gea; //合成二进制数a
geb<=display_temp[3:0]; //获得个位数
shib<=display_temp[7:4]; //获得十位数
operand_b<=(shib<<3)+(shib<<1)+geb; //合成二进制数b
数值转换,将二进制数转换为bcd码
/*-------------------------------------*/
// Module name : bin2bcd
// Author : STEP
// Description : 15位二进制数转BCD码 (32768)
// Web : www.stepfpga.com
/*-------------------------------------*/
module bin2bcd (
input [14:0] bitcode,
output [19:0] bcdcode
);
reg [34:0] data;
assign bcdcode = data[34:15];
always@(bitcode) begin
data = {20'd0,bitcode};
repeat(15) begin //二进制码总共8位,所以循环位数是8
if(data[18:15]>=5)
data[18:15] = data[18:15] + 3;
if(data[22:19]>=5)
data[22:19] = data[22:19] + 3;
if(data[26:23]>=5)
data[26:23] = data[26:23] + 3;
if(data[30:27]>=5)
data[30:27] = data[30:27] + 3;
if(data[34:31]>=5)
data[34:31] = data[34:31] + 3;
data = data << 1;
end
end
endmodule
6、仿真波形图
暂无。
7、FPGA资源利用说明
资源 | 参数数量 | 比例 |
LUTs | 1102 | 26% |
寄存器 | 362 | 8% |
IO管脚 | 36 | |
时钟频率 | 12MHz |
8、问题点及改进方向
(1)、本次项目在按键输入,键值判断上花费很多时间。写好数字按键键值判断存储模块后,按同样的方式处理加减乘除按键,发现触发不了,或者触发后键值存储不了。未找到原因;
(2)、除法运算制作了简单的测试,未计算小数点,后面可以探索一下。
9、心得体会
WEBIDE调试非常方便,尤其是引脚分配方面,可以很直观的看到目前操作的是哪个IO口。小脚丫的官网资源丰富,板子上所有外设都能找到相应的教程,对初学者来说相当友好。