2024年寒假练-基于小脚丫FPGA套件实现两位十进制加、减、乘、除计算器
该项目使用了Verilog语言,lattice diamond软件,小脚丫FPGA套件STEP BaseBoard V4.0,实现了两位十进制加、减、乘、除计算器的设计,它的主要功能为:实现两位十进制加、减、乘、除,(包含小数点的情况)。
标签
FPGA
数字逻辑
verilog
YYT_0901
更新2024-04-01
北京理工大学
211

1.   项目需求

实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,4×4键盘按键分配如下图所示。

image.png

运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

2.   需求分析

l  功能需求:

实现两位十进制数的加法、减法、乘法、除法运算。通过4×4键盘进行输入,包括数字键(0-9)和运算符键(加、减、乘、除)。运算数和计算结果通过8个八段数码管进行显示。

每个运算数使用两个数码管显示,分别表示十位数和个位数。输入两位十进制数时,最高位先在右侧显示,然后跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

 

l  输入需求:

用户通过4×4键盘输入数字和运算符。支持数字输入(0-9)和运算符输入(加、减、乘、除)。支持连续输入两位十进制数进行运算(包括小数)。输入的数字和运算符需要进行有效性检查,确保输入的格式正确。

 

l  显示需求:

使用8个八段数码管进行显示,分成两组,每组用于显示一个两位十进制数或计算结果。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。运算结构根据是否是小数判断,如果是小数就保留两位小数,否则显示整数。

 

l  运算需求:

支持加法、减法、乘法、除法四种基本运算。进行运算时,根据用户输入的运算符进行相应的计算。运算过程中需要处理运算数的进位、借位和除法中的除数为零等异常情况。

 

3.   实现的方式

首先,array_keyboard 模块负责扫描矩阵键盘并检测按键是否被按下。这意味着它需要轮询键盘的状态,并检测是否有按键被按下。一旦按键被按下,该模块会记录下按键的位置以及按键的动作,然后将这些信息传递给下一个模块。


接下来,key_decode 模块接收从 array_keyboard 模块传递过来的按键信息。它的任务是将按键的动作脉冲信号解码成对应的按键值,并标记按键的类型,比如数字、运算符等。这个模块需要维护一个按键映射表,以便将脉冲信号转换为可识别的按键值。


随后,cal 模块是整个系统的主要计算核心。它接收来自 key_decode 模块的按键值和按键类型,并根据这些信息执行相应的计算逻辑。在这个模块中,包括对输入按键的解析、数学表达式的构建以及实际的计算过程。最终,该模块会生成一个表示计算结果的二进制数。


为了将计算结果显示出来,需要将二进制数转换成BCD(Binary Coded Decimal)格式。这项任务由 bin2bcd 模块负责实现。BCD 是一种数字编码方式,将每个十进制数位转换成对应的四位二进制数,以便在数码管上显示。


最后,segment_scan 模块控制数码管的扫描和显示过程。它接收来自 bin2bcd 模块的BCD格式结果,并将其分解为单个数字,然后根据数码管的显示规则进行相应的显示。这个模块负责确保计算结果以人类可读的方式在数码管上展示出来。

 


4.  功能框图

image.png

1.  代码及说明

Calculator.v

该模块负责整合所有模块,实现功能的统一执行


module type_system(
input clk, // 系统时钟
input rst_n, // 重置按钮
input [3:0] col, // 矩阵键盘
output [3:0] row, // 矩阵键盘
output seg_rck, // 74HC595的RCK管脚
output seg_sck, // 74HC595的SCK管脚
output seg_din // 74HC595的SER管脚
);

wire [15:0] key_out;
wire [15:0] key_pulse; // 脉冲信号
wire [3:0] seg_data; // 记录点击的按键对应的值
wire [26:0] sshow; // 记录二进制运算的bin_code
wire [3:0] statu; // 标记点击的是数字或运算符等
wire [31:0] bcd_disp; // 记录最终显示
wire un_change; // 标记是否要进行转换
wire [7:0] dat_en; // 数码管数据位显示使能
wire [7:0] dot_en; // 数码管小数点位显示使能
wire [3:0] minus; // 标记结果是否是负数
wire [7:0] temp_dat;

// 矩阵键盘的扫描和按键检测
array_keyboard u1(
.clk (clk ),
.rst_n (rst_n ),
.col (col ),
.row (row ),
.key_out (key_out ),
.key_pulse (key_pulse )
);

// 将按键的按下动作脉冲信号解码成对应的按键值
key_decode u2(
.clk (clk ),
.rst_n (rst_n ),
.key_pulse (key_pulse ),
.seg_data (seg_data ),
.statu (statu )
);

// 主要计算模块
cal u3(
.clk (clk ),
.rst_n (rst_n ),
.seg_data (seg_data ),
.statu (statu ),
.sshow (sshow ),
.un_change (un_change ),
.dat_en (temp_dat ),
.dot_en (dot_en ),
.minus (minus )
);

// 将输入的二进制数转换为BCD(二进制码十进制)格式
bin2bcd u4(
.rst_n (rst_n ),
.un_change (un_change ),
.bin_code (sshow ),
.minus (minus ),
.dat_en (temp_dat ),
.out_dat_en (dat_en ),
.bcd_code (bcd_disp )
);

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

endmodule


array_keyboard.v

该模块的主要功能是对矩阵键盘进行扫描,并检测键盘按键的按下状态,最终输出按键脉冲信号。

// 矩阵键盘的扫描和按键检测
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 begin
key_out_r <= key_out; //将前一刻的值延迟锁存
end

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

endmodule


key_decode.v

该模块的主要功能是根据按键的动作脉冲信号,将按键值解码,并提供相应的按键状态。

  • 在每个时钟周期,根据 key_pulse 的值进行解码,确定按下的是哪一个按键。
  • 通过 case 语句根据 key_pulse 的值进行匹配,确定对应的按键值和状态值。
  • 当有按键按下时,更新 seg_datastatu 的值。
// 将按键的按下动作脉冲信号(key_pulse)解码成对应的按键值
module key_decode(
input clk,
input rst_n,
input [15:0] key_pulse, //按键消抖后动作脉冲信号
output reg [3:0] seg_data, //表示按键按下的值
output reg [3:0] statu
);

always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
statu <= 4'd0;
end else begin
case(key_pulse) //key_pulse脉宽等于clk_in的周期
16'h0001: begin seg_data <= 4'd7; statu <= 4'd1; end
16'h0002: begin seg_data <= 4'd8; statu <= 4'd1; end
16'h0004: begin seg_data <= 4'd9; statu <= 4'd1; end
16'h0008: begin seg_data <= 4'd14;statu <= 4'd2; end
16'h0010: begin seg_data <= 4'd4; statu <= 4'd1; end
16'h0020: begin seg_data <= 4'd5; statu <= 4'd1; end
16'h0040: begin seg_data <= 4'd6; statu <= 4'd1; end
16'h0080: begin seg_data <= 4'd13;statu <= 4'd2; end
16'h0100: begin seg_data <= 4'd3; statu <= 4'd1; end
16'h0200: begin seg_data <= 4'd2; statu <= 4'd1; end
16'h0400: begin seg_data <= 4'd1; statu <= 4'd1; end
16'h0800: begin seg_data <= 4'd12;statu <= 4'd2; end
16'h1000: begin seg_data <= 4'd0; statu <= 4'd1; end
16'h2000: begin seg_data <= 4'd15;statu <= 4'd3; end
16'h4000: begin seg_data <= 4'd10;statu <= 4'd4; end
16'h8000: begin seg_data <= 4'd11;statu <= 4'd2; end
default: begin seg_data <= seg_data; statu <= 4'd0; end //无按键按下时保持
endcase

end
end

endmodule


cal.v

这个模块是主要的计算核心,它接收按键解码模块传来的按键值和按键类型,并进行相应的计算逻辑。根据输入的数字和运算符进行运算,并产生相应的输出结果。

  • 在每次点击数字或运算符时,根据状态 statu 的不同进行相应的操作。
  • 如果点击的是数字:
    • 根据点击次数 t_press 决定显示的数码管位数,并将点击的数字添加到显示结果 sshow 中。
    • 如果需要清空数码管,则将 sshow 清零。
  • 如果点击的是运算符:
    • 如果之前输入的是小数,记录运算数的小数点位置。
    • 将当前显示结果 sshow 赋值给运算数1 num_1
    • 根据点击的运算符更新运算符标记 op 和显示结果 sshow
    • 设置小数点显示使能 dot_en
  • 如果点击的是小数点:
    • 如果之前已输入数字,设置小数点显示使能 dot_en,并记录小数点位置。
  • 如果点击的是等于:
    • 将当前显示结果 sshow 赋值给运算数2 num_2
    • 如果运算数1和运算数2其中一个是小数,根据小数点位置进行对齐。
    • 根据点击的运算符进行加减乘除运算,将结果存储到 result 中。
    • 根据结果的大小确定数码管显示位数和负数标记。
    • 根据小数点位置确定小数点的显示。
  • 根据结果的大小设置数码管的显示位数,确保结果在合适的范围内显示。
// 主要计算模块
module cal (
input clk,
input rst_n,
input [3:0] seg_data, // 记录点击了什么数字或运算符
input [3:0] statu, // 标记点击的是数字或运算符等
output reg [26:0] sshow, // 记录二进制运算的bin_code
output reg un_change, // 标记是否需要进行转换
output reg [7:0] dat_en, // 数码管数据位显示使能
output reg [7:0] dot_en, // 数码管小数点位显示使能
output reg [3:0] minus // 标记结果是否是负数
);

reg [3:0] t_press; // 点击数字次数
reg [26:0] num_1; // 运算数1
reg [26:0] num_2; // 运算数2
reg [26:0] result; // 运算结果
reg [3:0] op; // 标记是否点击了运算符
reg flag; // 标记是否清空数码管
reg float_num1; // 标记运算数1是否是小数
reg float_num2; // 标记运算数2是否是小数
reg [3:0] press_num1; // 运算数1小数点位置
reg [3:0] press_num2; // 运算数2小数点位置
reg [3:0] temp_press;

always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin // 重置
num_1 <= 27'b0;
num_2 <= 27'b0;
sshow <= 27'b0;
dot_en <= 8'h0;
op <= 4'd0;
flag <= 0;
dat_en <= 8'h0;
t_press <= 0;
float_num1 <= 0;
float_num2 <= 0;
minus <= 0;
press_num1 <= 0;
press_num2 <= 0;
end else begin
if(statu) begin
case(statu)
4'd1: begin // 如果点击的是数字
t_press = t_press + 4'b1;
dot_en = dot_en << 1;
case(t_press) // 每点击一次多亮一位
4'd1: dat_en <= 8'b0000_0001;
4'd2: dat_en <= 8'b0000_0011;
4'd3: dat_en <= 8'b0000_0111;
4'd4: dat_en <= 8'b0000_1111;
4'd5: dat_en <= 8'b0001_1111;
4'd6: dat_en <= 8'b0011_1111;
4'd7: dat_en <= 8'b0111_1111;
endcase
un_change = 0;
if(flag == 1) begin
sshow = 27'd0;
end
sshow <= sshow * 10 + seg_data;
flag = 0;
end
4'd2: begin // 如果点击的是运算符
if(float_num1)
press_num1 = t_press - temp_press;

t_press = 0;
dat_en <= 8'b0000_0001;
dot_en = 8'b0;
un_change = 1;
flag = 1;
num_1 = sshow;
if(seg_data == 4'd11) begin // 标记点击的运算符及显示该运算符
op <= 4'd11;
sshow = 4'd11;
end else if(seg_data == 4'd12) begin
op <= 4'd12;
sshow = 4'd12;
end else if(seg_data == 4'd13) begin
op <= 4'd13;
sshow = 4'd13;
end else if(seg_data == 4'd14) begin
op <= 4'd14;
sshow = 4'd14;
end
end
4'd3: begin // 如果点击的是小数点
if(t_press) begin
dot_en <= 8'b0000_0001;
if(op)
float_num2 = 1;
else
float_num1 = 1;
temp_press = t_press;
end
end
4'd4: begin // 如果点击的是等于
num_2 = sshow;
if(float_num2)
press_num2 = t_press - temp_press;
if((float_num1 || float_num2) && op != 4'd13) begin
if(press_num1 > press_num2) begin
case(press_num1 - press_num2) // 对齐小数位
4'b0001: num_2 = num_2 * 10;
4'b0010: num_2 = num_2 * 100;
4'b0011: num_2 = num_2 * 1000;
endcase
end else begin
case(press_num2 - press_num1)
4'b0001: num_1 = num_1 * 10;
4'b0010: num_1 = num_1 * 100;
4'b0011: num_1 = num_1 * 1000;
endcase
end
end

if(op == 4'd11) begin // 加法处理
result = num_1 + num_2;
sshow = result;
end else if(op == 4'd12) begin // 减法处理
if(num_1 >= num_2)
result = num_1 - num_2;
else begin
result = num_2 - num_1;
minus = 4'd1;
end
sshow = result;
end else if(op == 4'd13) begin // 乘法处理
result = num_1 * num_2;
sshow = result;
end else if(op == 4'd14) begin // 除法处理
if(num_2 == 0) begin // 如果除数为0, 显示xxxx
un_change = 1;
sshow = 16'b1101_1101_1101_1101;
end else begin
result = num_1 * 100 / num_2;
dot_en <= 8'b0000_0100;
sshow = result;
end
end

if((float_num1 || float_num2) && (op == 4'd11 || op == 4'd12)) begin // 对其小数位
if(press_num1 > press_num2) begin
case(press_num1)
4'b0001: dot_en <= 8'b0000_0010;
4'b0010: dot_en <= 8'b0000_0100;
4'b0011: dot_en <= 8'b0000_1000;
4'b0100: dot_en <= 8'b0001_0000;
endcase
end else begin
case(press_num2)
4'b0001: dot_en <= 8'b0000_0010;
4'b0010: dot_en <= 8'b0000_0100;
4'b0011: dot_en <= 8'b0000_1000;
4'b0100: dot_en <= 8'b0001_0000;
endcase
end
end else if((float_num1 || float_num2) && op == 4'd13) begin
case(press_num1 + press_num2)
4'b0001: dot_en <= 8'b0000_0010;
4'b0010: dot_en <= 8'b0000_0100;
4'b0011: dot_en <= 8'b0000_1000;
4'b0100: dot_en <= 8'b0001_0000;
endcase
end else if((float_num1 || float_num2) && op == 4'd14) begin
dot_en <= 8'b0000_0100;
dat_en <= 8'b0000_0111;
end


if(sshow >= 27'd1000000) // 结果显示控制
dat_en <= 8'hff;
else if(sshow >= 27'd100000)
dat_en <= 8'b1011_1111;
else if(sshow >= 27'd10000)
dat_en <= 8'b1001_1111;
else if(sshow >= 27'd1000)
dat_en <= 8'b1000_1111;
else if(sshow >= 27'd100)
dat_en <= 8'b1000_0111;
else if(sshow >= 27'd10 && op != 4'd14)
dat_en <= 8'b1000_0011;
else if(sshow >= 27'd0 && op != 4'd14)
dat_en <= 8'b1000_0001;
end
endcase
end
end
end

endmodule


bin2bcd.v

该模块的主要功能是实现二进制数到BCD码的转换,并根据负数标记和数码管数据位显示使能信号,控制输出的BCD码和数码管显示使能信号。

  • un_change 为真时,直接将输入的二进制数传递给输出的BCD码。
  • un_change 为假时,模块将执行BCD转换的逻辑。
  • 对输入的二进制数进行移位操作,每次移位一位,检查每一位是否大于等于5,若大于等于5则加上3,以确保BCD码的正确性。
  • 在负数标记为真时,根据不同的数码管数据位显示使能信号,在对应位置加上负号。
  • 将经过转换的BCD码输出到 bcd_code 中,同时根据负数标记和数码管数据位显示使能信号,设置输出的数码管显示使能信号 out_dat_en
// 将输入的二进制数转换为BCD(二进制码十进制)格式
module bin2bcd(
input rst_n,
input un_change, // 标记是否要进行转换
input [26:0] bin_code, // 需要转换成bcd的二进制
input [3:0] minus, // 标记结果是否是负数
input [7:0] dat_en, // 数码管数据位显示使能
output reg [7:0] out_dat_en, // 返回八位数码管显示
output reg [31:0] bcd_code // 记录转换后的bcd_code
);

reg [58:0] shift_reg;

always @ (bin_code or rst_n) begin
shift_reg = {32'h0, bin_code};
out_dat_en <= dat_en;
if (!rst_n)
bcd_code <= 0;
else begin
if (un_change)
bcd_code <= bin_code;
else begin
repeat (27) begin
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
if(minus) begin // 如果结果是负数, 在对应位置加上负号
case(dat_en)
8'b1000_0001: begin shift_reg[34:31] = 4'd12; out_dat_en <= 8'b1000_0011; end
8'b1000_0011: begin shift_reg[38:35] = 4'd12; out_dat_en <= 8'b1000_0111; end
8'b1000_0111: begin shift_reg[42:39] = 4'd12; out_dat_en <= 8'b1000_1111; end
8'b1000_1111: begin shift_reg[46:43] = 4'd12; out_dat_en <= 8'b1001_1111; end
8'b1001_1111: begin shift_reg[50:47] = 4'd12; out_dat_en <= 8'b1011_1111; end
8'b1011_1111: begin shift_reg[54:51] = 4'd12; out_dat_en <= 8'b1111_1111; end
endcase
end
bcd_code <= shift_reg[58:27];
end
end
end

endmodule


segment_scan.v

该模块的主要功能是对数码管的数据进行扫描和控制,根据外部输入的数据和使能信号,完成数码管的显示操作。

  • 在模块内部,通过时钟信号 clk 和复位信号 rst_n 实现数码管的扫描和显示控制。
  • 模块内部使用了状态机来实现数码管的控制逻辑。
  • 在不同的状态下,模块完成对数码管数据的加载和输出。
  • MAIN 状态下,模块根据 dat_endot_en 控制对应数码管的数据和小数点的显示,同时根据 cnt_main 计数,依次加载每个数码管的数据。
  • WRITE 状态下,模块通过时序控制,将加载好的数据通过74HC595芯片进行串行数据传输,最后通过 seg_rck 将并行数据输出到数码管。
  • 模块同时支持负数的显示。
// 实现数码管的扫描和控制,控制数码管显示的数据和小数点的显示
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;

//创建数码管的字库,字库数据依段码顺序有关
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'b100_1000; // =
seg[11] = 7'b100_0110; // +
seg[12] = 7'b100_0000; // -
seg[13] = 7'b111_0110; // *
seg[14] = 7'b100_1001; // /
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

FPGA的资源利用说明

仿真波形图

image.png

image.png

image.png



演示视频

项目功能演示详情可见项目视频


总结

在结束之际,我要由衷地感谢 2024 寒假在家一起练这个活动。这段时间,我从一个什么都不懂的小白开始,经过自主学习、观看视频、翻阅书籍,逐渐掌握了 Verilog 这门语言,学会了使用 Diamond 软件进行仿真等等。这一路上,收获颇丰,不仅仅是技术的提升,更重要的是锻炼了自学能力和解决问题的能力。再次感谢这段难忘的时光,让我不断成长,更加期待未来的挑战与机遇。

附件下载
array_keyboard.v
该模块的主要功能是对矩阵键盘进行扫描,并检测键盘按键的按下状态,最终输出按键脉冲信号。
bin2bcd.v
该模块的主要功能是实现二进制数到BCD码的转换,并根据负数标记和数码管数据位显示使能信号,控制输出的BCD码和数码管显示使能信号。
cal.v
这个模块是主要的计算核心,它接收按键解码模块传来的按键值和按键类型,并进行相应的计算逻辑。根据输入的数字和运算符进行运算,并产生相应的输出结果。
key_decode.v
该模块的主要功能是根据按键的动作脉冲信号,将按键值解码,并提供相应的按键状态。
segment_scan.v
该模块的主要功能是对数码管的数据进行扫描和控制,根据外部输入的数据和使能信号,完成数码管的显示操作。
Calculator.v
该模块负责整合上述所有模块,实现功能的统一执行。
2024寒假在家一起练.pdf
Screenshot_20240307_153839.jpg
Calculator_impl1.jed
下载程序
团队介绍
丘绎楦 - 北京理工大学
团队成员
丘绎楦
北京理工大学
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号