用小脚丫FPGA套件STEP BaseBoard V4.0实现简易计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,实现了简易计算器的设计,它的主要功能为:两位十进制数的加、减、乘、除运算,运算数和运算符(加、减、乘、除)由4×4矩阵键盘来输入,运算数和运算结果由数码管来显示。。
标签
FPGA
2024寒假在家一起练
youngmr
更新2024-04-01
196

1.项目需求

实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,4×4键盘按键分配如下图所示。运算数和计算结果通过8个八段数码管显示。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

4×4按键分布图

2.需求分析

根据项目需求,设计出来的计算器需要满足的要求包括:两位十进制数的加法、减法、乘法、除法计算,使用扩展板上的4×4矩阵键盘输入运算数和运算符,使用扩展板上的数码管显示运算数和运算结果。

3.实现方式

此项目设计过程全程使用WebIDE进行设计与仿真。根据上述需求分析,将此次项目设计分为五大部分,分别为矩阵键盘输入模块,解码模块,计算模块,转码模块,数码管显示模块。硬件部分使用矩阵键盘进行运算数和运算符的输入,使用数码管显示运算数和运算结果,同时使用核心板上的KEY1作为计算器的复位键。下图为小脚丫FPGA套件STEP BaseBoard V4.0的硬件资源。

小脚丫FPGA套件STEP BaseBoard V4.0

3.1矩阵键盘输入模块

对于实现运算数和运算符的输入设计了该矩阵键盘输入模块,该模块通过状态机扫描的方式检测 col[3:0] 输入,通过 row[3:0] 输出,从而检测按键。该模块将按键的电平信号转化为脉冲信号。下图为4×4矩阵键盘原理图。

STEP BaseBoard V4.04×4矩阵键盘原理图

下图为矩阵键盘模块在WebIDE当中的仿真截图

3.2解码模块

该模块将先前的矩阵键盘输入模块所产生的脉冲信号通过映射转化为每个按键所对应的数值。

3.3计算模块

本设计的运算部分是通过一个状态机来实现的,该状态机共分为3个状态,分别为作为存储输入数值的状态1,接收运算符的状态2和得出计算结果的状态3。

3.4转码模块

转码模块的功能是将计算模块所得出的运算结果和运算过程当中的运算数转化为向数码管显示模块传输的bcd码。

3.5数码管显示模块

数码管显示是利用两个级联的74HC595D芯片通过串行输入并行输出来实现,其中595_SCK为时钟信号输入,595_RCK控制信号读取的时钟信号,595_DIN为串行数据输入,下图为数码管的原理图。

STEP BaseBoard V4.0数码管原理图

4.功能流程图

5.代码及说明

5.1顶层文件

顶层文件规定了整体的输入输出并对各个模块进行了例化。下面是顶层文件的具体代码。

module top(
input clk,
input rst_n,
input [3:0] col,
output [3:0] row,
output seg_rck,
output seg_sck,
output seg_din
);

parameter NUM_WID = 27;
parameter KEY_WID = 4;
parameter result_bcd_wid = 32;

wire [15:0] key_out;
wire [15:0] key_pulse;

wire [3:0] key_num;
wire [NUM_WID-1:0] result;

wire [7:0] dat_en;
wire [7:0] dot_en;
wire [result_bcd_wid-1:0] display_bcd;
wire flag;
assign dat_en = 8'b1111_1111;
assign dot_en = 8'b0000_0000;

array_keyboard array_keyboard(
.clk (clk ),
.rst_n (rst_n ),
.col (col ),
.row (row ),
.key_out (key_out ),
.key_pulse (key_pulse)
);

calculator_core calculator(
.clk (clk ),
.rst_n (rst_n ),
.flag (flag ),
.key_data (key_num ),
.bin_data (result )
);

bin_to_bcd bin_bcd(
.rst_n (rst_n ),
.bin_code (result ),
.bcd_code (display_bcd)
);

key_decode key_decode(
.clk (clk ),
.rst_n (rst_n ),
.key_pulse (key_pulse),
.seg_data (key_num ),
.flag (flag )
);

segment_scan segment_scan(
.clk (clk ), //系统时钟 12MHz
.rst_n (rst_n ), //系统复位 低有效
.dat_1 (display_bcd[31:28]), //SEG1 显示的数据输入
.dat_2 (display_bcd[27:24]), //SEG2 显示的数据输入
.dat_3 (display_bcd[23:20]), //SEG3 显示的数据输入
.dat_4 (display_bcd[19:16]), //SEG4 显示的数据输入
.dat_5 (display_bcd[15:12]), //SEG5 显示的数据输入
.dat_6 (display_bcd[11: 8]), //SEG6 显示的数据输入
.dat_7 (display_bcd[ 7: 4]), //SEG7 显示的数据输入
.dat_8 (display_bcd[ 3: 0]), //SEG8 显示的数据输入
.dat_en (dat_en ), //数码管数据位显示使能
.dot_en (dot_en ), //数码管小数点位显示使能
.seg_rck (seg_rck ), //74HC595的RCK管脚
.seg_sck (seg_sck ), //74HC595的SCK管脚
.seg_din (seg_din ) //74HC595的SER管脚
);

endmodule

5.2矩阵键盘模块

矩阵键盘模块将4×4矩阵键盘所产生的电平信号转化为代码中名为key_pulse的脉冲信号

矩阵键盘模块代码

module array_keyboard #
(
parameter CNT_200HZ = 60000
)
(
input clk,
input rst_n,
input [3:0] col,
output reg [3:0] row,
output reg [15:0] key_out,
output [15:0] key_pulse
);

localparam STATE0 = 2'b00;
localparam STATE1 = 2'b01;
localparam STATE2 = 2'b10;
localparam STATE3 = 2'b11;

//计数器计数分频实现5ms周期信号clk_200hz,系统时钟12MHz
reg [15:0] cnt;
reg clk_200hz;
always@(posedge clk or negedge rst_n) begin //复位时计数器cnt清零,clk_200hz信号起始电平为低电平
if(!rst_n) begin
cnt <= 16'd0;
clk_200hz <= 1'b0;
end else begin
if(cnt >= ((CNT_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) 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; end
endcase
end
end

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

reg [15:0] key_out_r;

always @ ( posedge clk or negedge rst_n )
if (!rst_n) key_out_r <= 16'hffff;
else key_out_r <= key_out; //将前一刻的值延迟锁存

assign key_pulse= key_out_r & ( ~key_out); //通过前后两个时刻的值判断

endmodule

5.3解码模块

解码模块将矩阵键盘模块所输出的脉冲信号翻译为计算器对应按键的值信息

解码模块代码

module key_decode(
input clk,
input rst_n,
input [15:0] key_pulse,
output reg [3:0] seg_data,
output reg flag
);

//key_pulse transfer to seg_data
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
seg_data <= 4'd00;
end
else begin
case(key_pulse) //key_pulse脉宽等于clk_in的周期
16'h0001: begin
seg_data <= 4'd07; flag <= 1'b1;
end
16'h0002: begin
seg_data <= 4'd08; flag <= 1'b1;
end
16'h0004: begin
seg_data <= 4'd09; flag <= 1'b1;
end
16'h0008: begin
seg_data <= 4'd10; flag <= 1'b1;
end
16'h0010: begin
seg_data <= 4'd04; flag <= 1'b1;
end
16'h0020: begin
seg_data <= 4'd05; flag <= 1'b1;
end
16'h0040: begin
seg_data <= 4'd06; flag <= 1'b1;
end
16'h0080: begin
seg_data <= 4'd11; flag <= 1'b1;
end
16'h0100: begin
seg_data <= 4'd01; flag <= 1'b1;
end
16'h0200: begin
seg_data <= 4'd02; flag <= 1'b1;
end
16'h0400: begin
seg_data <= 4'd03; flag <= 1'b1;
end
16'h0800: begin
seg_data <= 4'd12; flag <= 1'b1;
end
16'h1000: begin
seg_data <= 4'd00; flag <= 1'b1;
end
16'h2000: begin
seg_data <= 4'd00; flag <= 1'b1;
end
16'h4000: begin
seg_data <= 4'd14; flag <= 1'b1;
end
16'h8000: begin
seg_data <= 4'd13; flag <= 1'b1;
end
default: begin seg_data <= seg_data; flag <=1'b0;
end //无按键按下时保持
endcase
end
end

endmodule

5.4计算模块

计算模块主体是通过状态机来实现的

计算模块代码

module calculator_core (
clk,
rst_n,
flag,
key_data,
bin_data
);

input clk;
input rst_n;
input flag;
input [3:0] key_data;

output reg [26:0] bin_data;

reg [1:0] state;
reg [26:0] num1;
reg [3:0] opcode;

always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
state <= 0;
num1 <= 0;
bin_data <= 0;
opcode <= 0;
end
else
begin
case (state)
//状态机实现
0 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= bin_data * 10 + key_data;
end
else
begin
if (key_data == 14)
begin
state <= 0;
end
else
begin
opcode <=key_data;
state <= 1;
num1 <= bin_data;
bin_data <=0;
end

end
end
else
begin
state <= 0;
end
end
//运算状态
1 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= bin_data * 10 + key_data;
end
else
begin
if(key_data == 14)
begin
case (opcode)
10 : begin bin_data <= num1 + bin_data; state <= 2; end
11 : begin bin_data <= num1 - bin_data; state <= 2; end
12 : begin bin_data <= num1 * bin_data; state <= 2; end
13 : begin bin_data <= num1 / bin_data; state <= 2; end
default : bin_data <= 0;
endcase
end
else
begin
state <= 1;
end
end
end
else
begin
state <= 1;
end
end

2 : begin
if (flag)
begin
if (key_data < 10)
begin
bin_data <= {22'd0,key_data};
state <= 0;
end
else
begin
if(key_data == 14)
begin
state <= 2;
end
else
begin
num1 <= bin_data;
opcode <= key_data;
bin_data <= 0;
state <= 1;
end
end
end
else
begin
state <= 2;
end
end
endcase
end
end
endmodule

5.5转码模块

转码模块的主要功能是将计算模块的输出值转换为二进制bcd码

转码模块代码

module bin_to_bcd
(
rst_n, //系统复位,低有效
bin_code, //需要进行BCD转码的二进制数据
bcd_code //转码后的BCD码型数据输出
);

parameter NUM_WID = 27;
parameter result_bcd_wid = 32;

input rst_n;
input [NUM_WID-1:0] bin_code;
output [result_bcd_wid-1:0] bcd_code;
reg [result_bcd_wid-1:0] bcd_code;
reg [58:0] shift_reg;

always@(bin_code or rst_n)begin
shift_reg = {32'h0,bin_code};
if(!rst_n)
bcd_code = 0;
else begin
repeat(27) begin //循环27次
//BCD码各位数据作满5加3操作,
if (shift_reg[30:27] >= 5) shift_reg[30:27] = shift_reg[30:27] + 2'b11;
if (shift_reg[34:31] >= 5) shift_reg[34:31] = shift_reg[34:31] + 2'b11;
if (shift_reg[38:35] >= 5) shift_reg[38:35] = shift_reg[38:35] + 2'b11;
if (shift_reg[42:39] >= 5) shift_reg[42:39] = shift_reg[42:39] + 2'b11;
if (shift_reg[46:43] >= 5) shift_reg[46:43] = shift_reg[46:43] + 2'b11;
if (shift_reg[50:47] >= 5) shift_reg[50:47] = shift_reg[50:47] + 2'b11;
if (shift_reg[54:51] >= 5) shift_reg[54:51] = shift_reg[54:51] + 2'b11;
if (shift_reg[58:55] >= 5) shift_reg[58:55] = shift_reg[58:55] + 2'b11;
shift_reg = shift_reg << 1;
end
bcd_code = shift_reg[58:27];
end
end

endmodule

5.6数码管显示模块

数码管显示模块的功能是将转码模块所输出的bcd码显示在扩展板的数码管上

数码管显示模块代码

module segment_scan(
input clk, //系统时钟 12MHz
input rst_n, //系统复位 低有效
input [3:0] dat_1, //SEG1 显示的数据输入
input [3:0] dat_2, //SEG2 显示的数据输入
input [3:0] dat_3, //SEG3 显示的数据输入
input [3:0] dat_4, //SEG4 显示的数据输入
input [3:0] dat_5, //SEG5 显示的数据输入
input [3:0] dat_6, //SEG6 显示的数据输入
input [3:0] dat_7, //SEG7 显示的数据输入
input [3:0] dat_8, //SEG8 显示的数据输入
input [7:0] dat_en, //数码管数据位显示使能
input [7:0] dot_en, //数码管小数点位显示使能
output reg seg_rck, //74HC595的RCK管脚
output reg seg_sck, //74HC595的SCK管脚
output reg seg_din //74HC595的SER管脚
);

localparam CNT_40KHz = 300; //分频系数

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

//创建数码管的字库,字库数据依段码顺序有关
//这里字库数据[MSB~LSB]={G,F,E,D,C,B,A}
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'h77; // 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

//使用状态机完成数码管的扫描和74HC595时序的实现
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 //IDLE作为第一个状态,相当于软复位
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; //在配置完发给74HC595的数据同时跳转至WRITE状态,完成串行时序
case(cnt_main)
//对8位数码管逐位扫描
//data [15:8]为段选, [7:0]为位选
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)
//74HC595是串行转并行的芯片,3路输入可产生8路输出,而且可以级联使用
//74HC595的时序实现,参考74HC595的芯片手册
6'd0: begin seg_sck <= LOW; seg_din <= data[15]; end //SCK下降沿时SER更新数据
6'd1: begin seg_sck <= HIGH; end //SCK上升沿时SER数据稳定
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 //当16位数据传送完成后RCK拉高,输出生效
6'd33: begin seg_rck <= LOW; state <= MAIN; end
default: ;
endcase
end
default: state <= IDLE;
endcase
end
end

endmodule

6.FPGA的资源利用


附件下载
项目代码.zip
项目代码
团队介绍
杨明睿 哈尔滨工业大学(威海)
团队成员
杨明睿
哈尔滨工业大学(威海)
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号