一、任务要求
实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制。
基本要求:
运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。
(这里的扩展要求没做)
分析要求
1·在两个四位一体数码管上进行效果的显示。
2·矩阵键盘充当输入,FPGA需要识别并完成解码,转码等操作
3·计算器需要进行加减乘除的四则运算,并且十位数要显示在个位的左侧,这就要考虑到位移。
二、实现方式
组成:一个简易的 FPGA 计算器可以由以下几个主要模块组成:
框图如下
- 矩阵键盘扫描模块:用于扫描矩阵键盘的按键输入,并将按键信息传递给后续的模块进行处理。
- 计算器核心模块:包含加法、减法、乘法、除法等基本运算的计算逻辑。
- 数码管显示模块:负责将计算结果通过74HC595芯片驱动对应数码管,并在对应数码管上进行显示。
- 按键解码模块:用于解码按键输入,将按键值转换为对应的操作数、操作符等信息,以便传递给计算器核心模块进行计算。
5.转码模块:将解码出的运算符等运算结果转成BCD码并在数码管上进行显示。
三、程序分析
1·计算器核心模块
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= 2'b00; // 将状态初始化为闲置状态
num <= 27'd0; // 将num1初始化为0
bin_data <= 27'd0; // 将输出二进制数据初始化为0
opcode <= 4'd0; // 将操作码初始化为0
end
else begin
case (state)
2'b00: begin
if (flag) begin
if (key_data < 4'd10) begin
bin_data <= bin_data * 27'd10 + key_data; // 使用按键输入更新bin_data
end
else begin
if (key_data == 4'd14) begin
state <= 2'b00; // 在清除键按下时返回到闲置状态
end
else begin
opcode <= key_data; // 保存操作码
state <= 2'b01; // 进入第二操作数输入状态
num <= bin_data; // 保存第一个操作数
bin_data <= 27'd0; // 重置bin_data以便输入第二个操作数
end
end
end
end
2'b01: begin
if (flag) begin
if (key_data < 4'd10) begin
bin_data <= bin_data * 27'd10 + key_data; // 使用按键输入更新bin_data
end
else begin
if (key_data == 4'd14) begin
case (opcode)
4'd10: bin_data <= num + bin_data; // 加法运算
4'd11: bin_data <= num - bin_data; // 减法运算
4'd12: bin_data <= num * bin_data; // 乘法运算
4'd13: bin_data <= num / bin_data; // 除法运算
default: bin_data <= 27'd0; // 默认情况
endcase
state <= 2'b10; // 进入显示结果状态
end
end
end
end
2'b10: begin
if (flag) begin
if (key_data < 4'd10) begin
bin_data <= {bin_data[25:0], key_data}; // 向左移动key_data以便继续输入
state <= 2'b00; // 返回输入状态
end
else begin
// 可以在这里添加其他操作
end
end
end
endcase
end
end
2·矩阵键盘核心程序
always@(posedge clk or negedge rst_n) begin
// 计数器计数分频实现5ms周期信号clk_200hz
if(!rst_n) begin
cnt <= 16'd0;
clk_200hz <= 1'b0;
end else begin
if(cnt >= ((CNT_200HZ>>1) - 1)) begin
cnt <= 16'd0;
clk_200hz <= ~clk_200hz;
end else begin
cnt <= cnt + 1'b1;
clk_200hz <= clk_200hz;
end
end
end
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)
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
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
always @ ( posedge clk or negedge rst_n ) begin
// 按键状态锁存
if (!rst_n) key_out_r <= 16'hffff;
else key_out_r <= key_out;
end
assign key_pulse = key_out_r & (~key_out);
扩展板连接图
3·二进制转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}; // 将输入的二进制数据左移5位,并在前面补32位0以进行BCD转换
if (!rst_n)
bcd_code = 0; // 复位时清除BCD码
else begin
repeat(27) begin // 循环27次
// 如果BCD每个数字大于等于5,则加3进行BCD转换
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; // 将寄存器内容左移1位,相当于除以2
end
bcd_code = shift_reg[58:27]; // 输出BCD码结果
end
end
endmodule
这段代码是根据官方给出的例程然后用ChatGPT来进行修改和扩展的。
4·数码管显示
// 使用状态机实现段式显示扫描和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 // 空闲状态,软复位
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;
case(cnt_main)
// 扫描每个8位显示器的数字
// 数据格式为 [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'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
扩展板连接图
四、仿真波型图
这是二进制转换成BCD码的仿真图
五、FPGA资源利用情况说明
设计摘要:
寄存器数量:197个,占总数的4%(4635个)
PFU寄存器:197个,占总数的5%(4320个)
PIO寄存器:0个,占总数的0%(315个)
SLICE数量:1586个,占总数的73%(2160个)
逻辑/ROM的SLICE:1586个,占总数的73%(2160个)
RAM的SLICE:0个,占总数的0%(1620个)
Carry的SLICE:992个,占总数的46%(2160个)
LUT4数量:3165个,占总数的73%(4320个)
用作逻辑LUT的数量:1181个
用作分布式RAM的数量:0个
用作Ripple逻辑的数量:1984个
用作移位寄存器的数量:0个
使用的PIO站点数量:31个+4个(JTAG),占总数的33%(105个)
块RAM数量:0个,占总数的0%(10个)
GSR数量:1个,占总数的100%(1个)
EFB使用:否
JTAG使用:否
读回使用:否
振荡器使用:否
启动使用:否
POR:开启
Bandgap:开启
功率控制器数量:0个,占总数的0%(1个)
动态Bank控制器(BCINRD)数量:0个,占总数的0%(6个)
动态Bank控制器(BCLVDSO)数量:0个,占总数的0%(1个)
DCCA数量:0个,占总数的0%(8个)
DCMA数量:0个,占总数的0%(2个)
PLL数量:0个,占总数的0%(2个)
DQSDLL数量:0个,占总数的0%(2个)
CLKDIVC数量:0个,占总数的0%(4个)
ECLKSYNCA数量:0个,占总数的0%(4个)
ECLKBRIDGECS数量:0个,占总数的0%(2个)
六、总结
通过学习FPGA,我深入了解数字电路设计、Verilog编程语言以及硬件描述语言的应用。在FPGA学习中,我掌握了逻辑设计、时序控制、状态机设计等基础知识,并能将其应用于实际项目中。通过实践项目,我学会了如何优化设计、提高性能,并充分利用FPGA资源。同时,我也了解到FPGA在数字信号处理、嵌入式系统等领域的广泛应用,并不断拓展自己的技术视野。总的来说,学习FPGA不仅提升了我的硬件设计能力,也开拓了我的技术思维,为我未来的发展打下了坚实基础。