2024年寒假练
——基于小脚丫FPGA套件实现两位十进制数加减乘除的计算器
一、项目介绍
1.1项目功能介绍
本项目要求基于小脚丫FPGA套件实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,4×4键盘按键分配如下图所示。
运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
在此基础上本项目支持负数计算,不支持小数运算。可以继续对上一次计算结果进行计算,或者直接按下数字键开始新的一轮计算。
1.2需求分析和设计实现思路
计算器整体设计分为三个大部分:输入和解码,储存和计算,显示。
输入和解码:采用4*4矩阵按键输入,利用状态机扫描获得按下时下降沿信号key_pulse[15:0];对key_pluse[15:0]进行解码,读出所键入数据seg_data[3:0],生成代表数据键入的flag信号并输出;
储存和计算:用四个状态来存储两个操作数num1[7:0], num2[7:0],操作符[3:0],计算结果result[15:0]并用data[31:0]输出;
(此处数据宽度设置考虑到题设为两位十进制数,故最大为99,二进制为8’h63,所以num1,num2设置位宽为8,计算最大值为99*99=9801,二进制为16’h2649,所以result设置位宽为16。最后输出数据data位宽为32是为了匹配八位数码管的八位数据,虽然目前计算结果最大只显示四位,但方便了后续进行其他项显示。)
显示:显示功能由三个模块实现分别是数据处理,数据译码和数码管驱动。其中数据处理是将传入的data[31:0]由二进制处理成需要显示的BCD码和负号,并输出片选信号;数据译码是将BCD码转成七段数码管点亮的八位码;数码管驱动为生成驱动m74HC595的时钟信号,并串行输出片选信号和七段数码管点亮八位码,从而驱动数码管进行扫描显示,利用视觉残留显示数据。
1.3功能框图
1.4硬件框图
1.5软件流程图
输入解码部分:
储存计算部分:
1.6资源占用报告
Design Summary:
Number of registers: 213 out of 4635 (5%)
PFU registers: 213 out of 4320 (5%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 1025 out of 2160 (47%)
SLICEs as Logic/ROM: 1025 out of 2160 (47%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 505 out of 2160 (23%)
Number of LUT4s: 2049 out of 4320 (47%)
Number used as logic LUTs: 1039
Number used as distributed RAM: 0
Number used as ripple logic: 1010
Number used as shift registers: 0
Number of PIO sites used: 13 + 4(JTAG) out of 105 (16%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
二、简单的硬件介绍
在本项目中用到了矩阵键盘,数码管和核心板上的N14作为复位键。
其中矩阵键盘利用row[3:0]输出,通过状态机扫描方式检测col[3:0]输入从而检测按键。
数码管是利用两个级联的74MC5950芯片通过串行输入并行输出来实现三个输入控制十六个输出。其中595_DIN为串行数据输入,595_SCK是时钟信号输入,595_RCK是控制十六位信号读取的时钟信号。
下面为管脚约束
三、实现的功能及图片展示
初始状态:
加法:样例为输入11+22。如图,结果为33,正确。
减法:继续上结果输入-48。如图,结果为-15,正确。
乘法:继续输入*4
如图,结果为-60,正确。
除法:继续输入/12
如图,结果为-5,正确。
虽然无法直接输入负数,但是可以通过输入“0-X=” 再继续输入计算符的方式使负数加入计算,总体实现了两位十进制的加减乘除及符号运算。
四、主要代码片段及说明
4.1输入部分
(1)矩阵键盘扫描状态机
always@(posedge clk_200hz or negedge rst_n) begin
if(!rst_n) 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;key_out = 16'hffff; end
endcase
end
end
四个状态输出四种ROW进行扫描
always@(negedge clk_200hz or negedge rst_n) begin
if(!rst_n) begin
key_out <= 16'hffff;
key <= 16'hffff;
end else begin
case(c_state)
STATE0:begin
//key_out[3:0] = col; //采集当前状态的列数据赋值给对应的寄存器位
key[3:0] <= col; //矩阵键盘采样
key_r[3:0] <= key[3:0]; //键盘数据锁存
key_out[3:0] <= key_r[3:0]|key[3:0]; //连续两次采样判定
end
STATE1:begin
//key_out[7:4] = col;
key[7:4] <= col; //矩阵键盘采样
key_r[7:4] <= key[7:4]; //键盘数据锁存
key_out[7:4] <= key_r[7:4]|key[7:4]; //连续两次采样判定
end
STATE2:begin
//key_out[11:8] = col;
key[11:8] <= col; //矩阵键盘采样
key_r[11:8] <= key[11:8]; //键盘数据锁存
key_out[11:8] <= key_r[11:8]|key[11:8]; //连续两次采样判定
end
STATE3:begin
//key_out[15:12] = col;
key[15:12] <= col; //矩阵键盘采样
key_r[15:12] <= key[15:12]; //键盘数据锁存
key_out[15:12] <= key_r[15:12]|key[15:12]; //连续两次采样判定
end
default:key_out = 16'hffff;
endcase
end
end
每个状态读取对应行的信息,当col为低时被按下。此处讲前一个时钟周期的key进行暂存后按位取或,进行防抖处理。
(2)译码
case(key_pulse) //key_pulse脉宽等于clk_in的周期
//编码
16'h0001: begin seg_data <= 4'h0D; flag<=1'b1;end //%
16'h0002: begin seg_data <= 4'h0E; flag<=1'b1;end //=
16'h0004: begin seg_data <= 4'h0F; flag<=1'b1;end//begin seg_data = seg_data;seven_segement_led_dot=1'b1;end//= 4'h0F; //.//seven_segement_led_dot=1'b1
16'h0008: begin seg_data <= 4'h00; flag<=1'b1;end //0
16'h0010: begin seg_data <= 4'h0C; flag<=1'b1;end//*
16'h0020: begin seg_data <= 4'h01; flag<=1'b1;end //1
16'h0040: begin seg_data <= 4'h02; flag<=1'b1;end //2
16'h0080: begin seg_data <= 4'h03; flag<=1'b1;end //3
16'h0100: begin seg_data <= 4'h0B; flag<=1'b1;end //-
16'h0200: begin seg_data <= 4'h06; flag<=1'b1;end //6
16'h0400: begin seg_data <= 4'h05; flag<=1'b1;end //5
16'h0800: begin seg_data <= 4'h04; flag<=1'b1;end //4
16'h1000: begin seg_data <= 4'h0A; flag<=1'b1;end //+
16'h2000: begin seg_data <= 4'h09; flag<=1'b1;end //9
16'h4000: begin seg_data <= 4'h08; flag<=1'b1;end //8
16'h8000: begin seg_data <= 4'h07; flag<=1'b1;end //7
default: begin seg_data <= seg_data; flag<=1'b0;end //无按键按下时保持
endcase
对key_pulse[15:0]所代表的被按下按键进行译码。
4.2存储和计算部分
(1)总览:
计算部分为有四个状态的状态机。
(2)储存
STATE0: begin
if(flag==1'b1)begin
if(seg_data <10) begin//不可出现链续判断,例如9<deg_data<14
//if(num1!=0 ||seg_data!= 0)begin data<={data[27:0],seg_data};end//向左覆盖
num1<=num1*10+seg_data;
data<=data*10+seg_data;
c_state<=STATE0;
end
else begin
if(seg_data==14) begin//等号误操,不变
c_state<=STATE0;
end
else begin//计算操作符,data不变,仍为num1
opcpde<=seg_data;
c_state<=STATE1;
end
end
end
else begin c_state<=STATE0;end
end
例如STATE1里的num1和opcode暂无操作时进行及时储存。
(3)计算
STATE1: begin
if(flag==1'b1)begin
if(seg_data<10) begin
//if(num1!=0 ||seg_data!= 0)begin data<={data[27:0],seg_data};end//向左覆盖
data<=num2*10+seg_data;
num2<=num2*10+seg_data;
c_state<=STATE1;
end
else if(seg_data==14) begin//等号
case(opcpde)
4'b1010:begin result<=num1+num2; end
4'b1011:begin result<=num1-num2; end
4'b1100:begin result<=num1*num2; end
4'b1101:begin result<=num1/num2; end
endcase
c_state<=STATE2;
end
else begin
c_state<=STATE1;
end
end
else if(!flag) begin//不按
c_state<=STATE1;
end
end
4.3显示部分
(1)数据处理
assign disp_buma = disp_data[15]? ({1'b0,~disp_data[14:0]}+1'b1):disp_data;
assign fuhao = disp_data[15]? 1'b1 : 1'b0;
assign data0 = disp_buma % 4'd10; //个位数
assign data1 = disp_buma / 4'd10 % 4'd10; //十位数
assign data2 = disp_buma / 7'd100 % 4'd10; //百位数
assign data3 = disp_buma / 10'd1000 % 4'd10; //千位数
对传入的数据进行二进制转BCD码,并识别是否有符号,进行补码转换。
(2)译码
case(seven_segement_led_dot)
1'b0:
case(key_in[3:0])
4'b0000:temp = 8'b11111100;//0,abcdef
4'b0001:temp = 8'b01100000;//1,bc
4'b0010:temp = 8'b11011010;//2,abged
4'b0011:temp = 8'b11110010;//3,abcdg
4'b0100:temp = 8'b01100110;//4,bcfg
4'b0101:temp = 8'b10110110;//5,acdfg
4'b0110:temp = 8'b10111110;//6,acdefg
4'b0111:temp = 8'b11100000;//7,abc
4'b1000:temp = 8'b11111110;//8
4'b1001:temp = 8'b11110110;//9,abcdfg
4'b1010:temp = 8'b01100010;//A,bcg
4'b1011:temp = 8'b00000010;//B,g
4'b1100:temp = 8'b01101110;//C,bcefg
4'b1101:temp = 8'b10010010;//D,agd
4'b1110:temp = 8'b10010000;//E,cg
default: temp = 8'b0;
endcase
1'b1:
case(key_in[3:0])
4'b0000:temp = 8'b11111101;//0,abcdef
4'b0001:temp = 8'b01100001;//1,bc
4'b0010:temp = 8'b11011011;//2,abged
4'b0011:temp = 8'b11110011;//3,abcdg
4'b0100:temp = 8'b01100111;//4,bcfg
4'b0101:temp = 8'b10110111;//5,acdfg
4'b0110:temp = 8'b10111111;//6,acdefg
4'b0111:temp = 8'b11100001;//7,abc
4'b1000:temp = 8'b11111111;//8
4'b1001:temp = 8'b11110111;//9,abcdfg
default: temp = 8'b0000_0000;
endcase
endcase
设有显示小数点功能,对应八段数码管最后一位。
(3)驱动时钟
case(SHCP_EDGE_CNT)
5'd0: begin SH_CP <= 1'b0; ST_CP <= 1'b1; DS <= r_data[15]; end
5'd1: begin SH_CP <= 1'b1; ST_CP <= 1'b0;end
5'd2: begin SH_CP <= 1'b0; DS <= r_data[14];end
5'd3: begin SH_CP <= 1'b1; end
5'd4: begin SH_CP <= 1'b0; DS <= r_data[13];end
5'd5: begin SH_CP <= 1'b1; end
5'd6: begin SH_CP <= 1'b0; DS <= r_data[12];end
5'd7: begin SH_CP <= 1'b1; end
5'd8: begin SH_CP <= 1'b0; DS <= r_data[11];end
5'd9: begin SH_CP <= 1'b1; end
5'd10:begin SH_CP <= 1'b0; DS <= r_data[10];end
5'd11:begin SH_CP <= 1'b1; end
5'd12:begin SH_CP <= 1'b0; DS <= r_data[9];end
5'd13:begin SH_CP <= 1'b1; end
5'd14:begin SH_CP <= 1'b0; DS <= r_data[8];end
5'd15:begin SH_CP <= 1'b1; end
5'd16:begin SH_CP <= 1'b0; DS <= r_data[7];end
5'd17:begin SH_CP <= 1'b1; end
5'd18:begin SH_CP <= 1'b0; DS <= r_data[6];end
5'd19:begin SH_CP <= 1'b1; end
5'd20:begin SH_CP <= 1'b0; DS <= r_data[5];end
5'd21:begin SH_CP <= 1'b1; end
5'd22:begin SH_CP <= 1'b0; DS <= r_data[4];end
5'd23:begin SH_CP <= 1'b1; end
5'd24:begin SH_CP <= 1'b0; DS <= r_data[3];end
5'd25:begin SH_CP <= 1'b1; end
5'd26:begin SH_CP <= 1'b0; DS <= r_data[2];end
5'd27:begin SH_CP <= 1'b1; end
5'd28:begin SH_CP <= 1'b0; DS <= r_data[1];end
5'd29:begin SH_CP <= 1'b1; end
5'd30:begin SH_CP <= 1'b0; DS <= r_data[0];end
5'd31:begin SH_CP <= 1'b1; end
default:begin SH_CP <= 1'b0;ST_CP <= 1'b0;DS <= 1'b0; end
endcase
串行传入点亮数码管的信号和片选信号
五、仿真波形图
(1)ArrayKeyBoard.v
当col由1111变成1110后,row变为1101,则key_out变为4’hffef,即第三排的第三个按键被按下。
(2)Decoder.v
key_pulse[15:0]均被译为对应的seg_data[3:0]。
(3)FSM.v
如图,num1=23,num2=45,操作符为4’hb,即减,结果为-22,下一个时钟后,data也会被翻译为-22.
(4)scan.v
可以看到对disp_data[31:0]每一位的显示由seven_segment_led[7:0]和seven_segment_led_sel[7:0]控制,例如当disp_data=32’b0000fffe时,最后一段是8’h5b,倒数第二段是8’h40,是4’b1110对应的led信号。
(5)m74hc595.v
如图,ST_CP的时钟周期为 SH_CP的16倍,且脉冲宽度相同,方向相反。