2024年寒假练 - 基于小脚丫FPGA STEP BaseBoard V4.0的两位十进制计算器
该项目使用了小脚丫FPGA套件STEP BaseBoard V4.0,实现了两位十进制加、减、乘、除计算器的设计,它的主要功能为:实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符由按键来控制。
标签
FPGA
计算器
2024寒假在家一起练
Peking
更新2024-04-01
261

1 项目需求

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

4×4键盘按键分配图

基本要求:

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


STEP BaseBoard V4.0介绍

STEP BaseBoard V4.0是第4代小脚丫FPGA扩展底板,可以用于全系列小脚丫核心板的功能扩展,采用100mm*161.8mm的黄金比例尺寸,板子集成了存储器、温湿度传感器、接近式传感器、矩阵键盘、旋转编码器、HDMI接口、RGBLCD液晶屏、8个7位数码管、蜂鸣器模块、UART通信模块、ADC模块、DAC模块和WIFI通信模块,配合小脚丫FPGA板能够完成多种实验,是数字逻辑、微机原理、可编程逻辑语言以及EDA设计工具等课程完美的实验平台。

 

FheM1anVmLBxLX0YIiIxNkkAIWf6

电路框图:

 

FgLIMTbPPt-dFC9MRQEr5-GkjQCf

搭配采用的小脚丫核心模块是采用Type C接口的基于Lattice MXO2的小脚丫FPGA核心板 ——STEP-MXO2-LPC。


STEP小脚丫FPGA学习平台是苏州思得普信息科技公司专门针对FPGA初学者(尤其是学习数字电路的在校同学)打造的一系列性价比最高、学习门槛最低的学习模块系列。板上选用的芯片兼具了FPGA和CPLD的优点,瞬时上电启动,无需外部重新配置FPGA,是学习数字逻辑绝佳的选择。系列中所有板子的大小兼容标准的DIP40封装,尺寸只有52mm x 18mm,非常便于携带,而且能够直接插在面包板上或以模块的方式放置在其它电路板上以即插即用的方式,大大简化系统的设计。


FgSnaHycoeQy6niFL5FnJrB6lMgG


2 完成的功能

2.1数字键盘输入:

系统能够接收数字键盘的输入信号,将按键信息转换为数字形式并进行处理。

2.2数码管显示:

根据数字键盘输入的信息,将其转换为数码管的段码数据,通过与 74HC595 芯片的通信,实现了数据的串行输出,控制数码管的显示。

2.3数据处理:

对按键信息进行解码处理,根据按键信息,计算最终结果。


3 实现思路

  • 按键输入和解码
    • 使用 array_keyboard 模块处理按键输入,将按键状态传递给 key_out 和 key_pulse 信号。
    • 在 key_decode 模块中,根据按键输入进行解码,实现对两个两位十进制数的输入和运算符的选择。将输入的数字和运算符存储在适当的寄存器中。
  • 数码管显示
    • 使用 segment_led 模块分别显示两个两位十进制数的十位数和个位数。
    • 在 segment_scan 模块中,根据解码后的数据,控制数码管显示结果。根据需要显示的数值,将对应的数码管段数据发送到数码管显示模块。
  • 计算逻辑
    • 在 key_decode 模块中,根据输入的数字和运算符进行加、减、乘、除运算。将计算结果存储在适当的寄存器中。
  • 显示结果
    • 将计算结果经过处理后显示在数码管上,确保结果能正确显示十位和个位数。
  • 清零和重置
    • 提供清零功能,使用户能够清除已输入的数字和运算符,以便开始新的计算。
    • 考虑添加复位功能,以确保系统能够在需要时返回到初始状态。
  • 测试和调试
    • 在设计完成后,进行综合、布局和时序分析,然后通过仿真验证功能是否按预期工作。
    • 在实际硬件上进行测试和调试,确保计算器在实际使用中能够正确运行。

利用webide实现设计。

webide无需下载即可在网页设计,并且可以非常方便的分配管脚,一键下载jed文件,非常值得推荐

image.png

利用电子森林AI助手,可以生成简单Verilog代码,大大减少设计时间,提高效率。

image.png

4 实现过程

4.1 程序流程图 

计算器.png

4.2 按键输入模块

  • 该模块实现了一个基于状态机的按键输入模块,用于处理一个4x4矩阵按键的输入。
  • 通过状态机的方式,周期性扫描按键矩阵的行,以检测按键的按下状态。
  • 采样按键状态,生成按键输出和按键脉冲信号,用于后续按键解码和处理。

工作方式:

  1. 时钟和复位
    • 使用系统时钟 clk 和复位信号 rst_n
    • 通过时钟信号生成 200Hz 的周期性信号 clk_200hz 用于按键扫描。
  2. 状态机设计
    • 使用 c_state 寄存器实现状态机,共有 4 个状态。
    • 每个状态下,对应矩阵按键的行输出有效,以便扫描按键状态。
  3. 按键扫描
    • 在每个状态下,通过采样列接口的电平状态来获取按键状态。
    • 采样得到的按键状态存储在 key 寄存器中,并与之前的状态进行比较,用于检测按键按下。
  4. 按键输出和按键脉冲
    • key_out 寄存器存储当前按键状态,key_out_r 延迟一个时钟周期以锁存前一刻的按键状态。
    • 通过比较前后两个时刻的按键状态,生成按键脉冲信号 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;

/*
因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态
在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样
周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间
对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理
*/

//计数器计数分频实现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;
//Register low_sw_r, lock low_sw to next clk
always @ ( posedge clk or negedge rst_n )
if (!rst_n) key_out_r <= 16'hffff;
else key_out_r <= key_out; //将前一刻的值延迟锁存

//wire [15:0] key_pulse;
//Detect the negedge of low_sw, generate pulse
assign key_pulse= key_out_r & ( ~key_out); //通过前后两个时刻的值判断

endmodule

4.3 运算模块 key_decode

key_decode 模块是用来处理按键输入并进行解码的模块,根据用户按下的按键来实现对两个两位十进制数的输入和选择加、减、乘、除运算符的功能。

模块输入和输出:

  • 输入:
    • clk: 时钟信号,用于同步按键输入处理。
    • rst_n: 复位信号,低有效,用于重置模块状态。
    • key_pulse: 按键脉冲信号,根据按键输入的状态产生的脉冲信号。
  • 输出:
    • seg_data: 用于控制数码管显示的信号。

模块功能:

  • key_decode 模块根据按键输入的状态进行解码,实现对两个两位十进制数的输入和选择加、减、乘、除运算符的功能。
  • 模块内部包含了寄存器来存储输入的数字、运算符以及计算结果。
  • 当按下按键时,根据按键的值进行相应的操作,例如输入数字、选择运算符、清零等。
  • 模块根据按键输入和状态机逻辑来更新存储的数字和运算符,并将结果传递给 seg_data 信号,以便在数码管上显示。

工作方式:

  1. 模块内部包含状态机逻辑,根据当前状态和按键输入进行状态转移和操作。
  2. 使用寄存器来存储输入的数字、运算符和计算结果,以便进行计算和显示。
  3. 根据不同的按键输入,执行相应的操作,例如输入数字、选择运算符、进行计算等。
  4. 在计算完成后,将结果传递给 seg_data 信号,以便在数码管上显示。
module key_decode(
input clk,
input rst_n,
input [15:0] key_pulse,
output reg [31:0] seg_data
);
reg [31:0] seg_data_med;
reg [3:0] num; //加减乘除
reg numd;
reg [31:0] num1;
reg [31:0] num2;
reg [31:0] nummedi;


reg [35:0] data;
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
seg_data_med <= 8'h00;
else
begin
seg_data <= 16'h0000;
if (num == 4'h0) //未按下加减乘除
begin
case (key_pulse)
16'h0001: num1 <= (seg_data_med *10) + 32'h07; // 编码
16'h0002: num1 <= (seg_data_med *10) + 32'h08;
16'h0004: num1 <= (seg_data_med *10) + 32'h09;
16'h0008: num <= 4'h8; // 除
16'h0010: num1 <= (seg_data_med *10) + 32'h04;
16'h0020: num1 <= (seg_data_med *10) + 32'h05;
16'h0040: num1 <= (seg_data_med *10) + 32'h06;
16'h0080: num <= 4'h4; // 乘
16'h0100: num1 <= (seg_data_med *10) + 32'h03;
16'h0200: num1 <= (seg_data_med *10) + 32'h02;
16'h0400: num1 <= (seg_data_med *10) + 32'h01;
16'h0800: num <= 4'h2; // 减
16'h1000: num1 <= (seg_data_med *10) + 32'h00;
16'h2000:
begin
seg_data_med <= 0; // 清零
num2 <= 0;
num1 <= 0;
numd <= 0;
num <= 0;
end
16'h4000:
begin
numd <= 1'd1; // 等于
end
16'h8000: num <= 4'h1; // 加
default: seg_data_med <= seg_data_med; // 无按键按下时保持
endcase
seg_data_med <= num1;
if (numd == 1'd1 )
begin
seg_data_med <= num1;
numd <= 1'd0;
end
end
else
begin
if ( numd != 1)// 未按下等于
begin
case (key_pulse)
16'h0001: num2 <= (seg_data_med *10) + 32'h07; // 编码
16'h0002: num2 <= (seg_data_med *10) + 32'h08;
16'h0004: num2 <= (seg_data_med *10) + 32'h09;
16'h0008: num <= 4'h8; // 除
16'h0010: num2 <= (seg_data_med *10) + 32'h04;
16'h0020: num2 <= (seg_data_med *10) + 32'h05;
16'h0040: num2 <= (seg_data_med *10) + 32'h06;
16'h0080: num <= 4'h4; // 乘
16'h0100: num2 <= (seg_data_med *10) + 32'h03;
16'h0200: num2 <= (seg_data_med *10) + 32'h02;
16'h0400: num2 <= (seg_data_med *10) + 32'h01;
16'h0800: num <= 4'h2; // 减
16'h1000: num2 <= (seg_data_med *10) + 32'h00;
16'h2000:
begin
seg_data_med <= 0; // 清零
num2 <= 0;
num1 <= 0;
numd <= 0;
num <= 0;
end
16'h4000:
begin
numd <= 1'b1; // 等于
end
16'h8000: num <= 4'h1; // 加
default: seg_data_med <= num2; // 无按键按下时保持
endcase
if(numd != 1)
seg_data_med <= num2;

end
if ( numd == 1)
begin
if (num == 4'h1 )
begin
num2 <= num2 + num1;
end
if (num == 4'h2 )
begin
num2 <= (num1 - num2);
end
if (num == 4'h4 )
begin
num2 <= num1 * num2;
end
if (num == 4'h8 )
begin
num2 <= num1 / num2;
end
numd<=0;
case (key_pulse)
16'h2000:
begin
seg_data_med <= 0; // 清零
num2 <= 0;
num1 <= 0;
numd <= 0;
num <= 0;
end
default: seg_data_med <= num2; // 无按键按下时保持
endcase
end
end
end

data = {20'd0,seg_data_med[15:0]};
repeat(16) begin //二进制码总共8位,所以循环位数是8
if(data[19:16]>=5)
data[19:16] = data[19:16] + 3;
if(data[23:20]>=5)
data[23:20] = data[23:20] + 3;
if(data[27:24]>=5)
data[27:24] = data[27:24] + 3;
if(data[31:28]>=5)
data[31:28] = data[31:28] + 3;
if(data[35:32]>=5)
data[35:32] = data[35:32] + 3;
data = data << 1;
end
seg_data <= data[35:16];


end
endmodule

4.4 数码管显示模块

分为segment_led 模块(核心板数码管)和segment_scan 模块(扩展底板数码管)。

segment_led 模块:

模块功能:

  • 数码管段码转换:根据输入的 seg_data 数据,将其转换为对应的数码管段码输出。
  • 数码管显示:将转换后的段码输出到 segment_led,控制数码管显示特定数字。
  • 输入:4位输入信号 seg_data,用于表示要显示的数字(0 到 9)。
  • 输出:9位输出信号 segment_led,用于控制数码管的各段(SEG, DP, G, F, E, D, C, B, A)。

模块工作方式:

  • 组合逻辑:使用 always@(seg_data) 块来对输入的 seg_data 进行处理,根据不同的输入值,将对应的段码数据存储在 segment_led 中。
  • 段码转换:根据输入的数字,将其转换为对应的数码管段码数据,以便正确显示该数字。
module segment_led
(
input [3:0] seg_data, //seg_data input
output reg [8:0] segment_led //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
);

always@(seg_data)
case(seg_data)
4'd0: segment_led = 9'h3f;
4'd1: segment_led = 9'h06;
4'd2: segment_led = 9'h5b;
4'd3: segment_led = 9'h4f;
4'd4: segment_led = 9'h66;
4'd5: segment_led = 9'h6d;
4'd6: segment_led = 9'h7d;
4'd7: segment_led = 9'h07;
4'd8: segment_led = 9'h7f;
4'd9: segment_led = 9'h6f;
default: segment_led = 9'h00;
endcase

endmodule

segment_scan 的模块,用于控制扩展底板数码管的显示。

模块功能:

  • 数码管显示控制:通过输入的数据信号 dat_1 到 dat_8 控制8个数码管显示的内容。
  • 数据使能控制:通过 dat_en 和 dot_en 信号控制每个数码管的数据位和小数点的显示使能。
  • 74HC595串行输出:使用 seg_rckseg_sckseg_din 控制 74HC595 芯片的输出,实现数据的串行输出。
  • 输入
    • 系统时钟信号 clk(12MHz)和系统复位信号 rst_n
    • 每个数码管的数据输入 dat_1 到 dat_8,数据位显示使能信号 dat_en 和小数点显示使能信号 dot_en
  • 输出
    • 74HC595 的控制信号 seg_rckseg_sckseg_din

字库数据初始化:

  • 在复位时,对数码管的字库进行初始化,将不同数字对应的段码数据存储在 seg 中。

模块工作方式:

  • 时钟分频:通过计数器 cnt 和 clk_40khz 信号实现对系统时钟的分频,生成 40kHz 的时钟信号。
  • 状态机:使用状态机实现数码管的扫描和数据传输时序的控制。
  • 数码管数据处理:根据状态机的状态,将对应的数码管数据和显示使能信号传送给 74HC595 芯片,实现数码管的显示。
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, //数码管数据位显示使能,[MSB~LSB]=[SEG1~SEG8]
input [7:0] dot_en, //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]
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

4.5 top模块 type_system 

type_system 的模块,该模块实现了一个数字键盘输入系统,将按键输入转换为数码管的显示。

模块功能:

  • 数字键盘输入:通过输入的 col 和 row 信号表示数字键盘的行列输入,将按键信息转换为 key_out 和 key_pulse
  • 数码管段码转换:根据按键信息,将其转换为数码管的段码数据并输出到 seg_1 和 seg_2
  • 74HC595控制:通过输出信号 seg_rckseg_sckseg_din 控制 74HC595 芯片的输出,实现数据的串行输出。

模块输入和输出:

  • 输入
    • 系统时钟信号 clk 和系统复位信号 rst_n
    • 数字键盘的列信号 col 和行信号 row
  • 输出
    • 数码管显示的两组段码输出 seg_1 和 seg_2
    • 74HC595 的控制信号 seg_rckseg_sckseg_din

模块工作方式:

  • 键盘输入处理:使用 array_keyboard 模块处理数字键盘输入,将按键信息转换为 key_out 和 key_pulse
  • 数据处理:通过 key_decode 模块处理按键信息,将其转换为数码管的段码数据存储在 seg_data 中。
  • 数码管显示:使用两个 segment_led 模块将 seg_data 分别输出到 seg_1 和 seg_2 控制数码管显示。
  • 数码管扫描:使用 segment_scan 模块控制数码管的显示,包括数据输入、显示使能和 74HC595 控制信号输出。
module type_system(
input clk,
input rst_n,
input [3:0] col,
output [3:0] row,
output [8:0] seg_1, //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
output [8:0] seg_2, //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
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 [31:0] seg_data_in;
wire [31:0] seg_data;
wire [7:0] dat_en; //控制数码管点亮

assign dat_en[7] = seg_data[31:28]? 1'b1:1'b0;
assign dat_en[6] = dat_en[7]|(seg_data[27:24]? 1'b1:1'b0);
assign dat_en[5] = dat_en[6]|(seg_data[23:20]? 1'b1:1'b0);
assign dat_en[4] = dat_en[5]|(seg_data[19:16]? 1'b1:1'b0);

assign dat_en[3] = dat_en[4]|(seg_data[15:12]? 1'b1:1'b0);
assign dat_en[2] = dat_en[3]|(seg_data[11:8]? 1'b1:1'b0);
assign dat_en[1] = dat_en[2]|(seg_data[7:4]? 1'b1:1'b0);
assign dat_en[0] = 1'b1;

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


//processing data
key_decode u2(
.clk (clk ),
.rst_n (rst_n ),
.key_pulse (key_pulse ),
.seg_data (seg_data ) //高4位代表十位,低4位代表个位
);

//Segment led display module
segment_led u3
(
.seg_data (seg_data[7:4] ), //seg_data input
.segment_led (seg_1 ) //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
);

//Segment led display module
segment_led u4
(
.seg_data (seg_data[3:0] ), //seg_data input
.segment_led (seg_2 ) //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
);
//segment_scan display module
segment_scan u5(
.clk(clk), //系统时钟 12MHz
.rst_n(rst_n), //系统复位 低有效
.dat_1(seg_data[31:28] ), //SEG1 显示的数据输入
.dat_2(seg_data[27:24] ), //SEG2 显示的数据输入
.dat_3(seg_data[23:20] ), //SEG3 显示的数据输入
.dat_4(seg_data[19:16]), //SEG4 显示的数据输入
.dat_5(seg_data[15:12] ), //SEG5 显示的数据输入
.dat_6(seg_data[11:8]), //SEG6 显示的数据输入
.dat_7(seg_data[7:4] ), //SEG7 显示的数据输入
.dat_8(seg_data[3:0] ), //SEG8 显示的数据输入
.dat_en(dat_en ), //数码管数据位显示使能,[MSB~LSB]=[SEG1~SEG8]
.dot_en(8'b0001_0001 ), //数码管小数点位显示使能,[MSB~LSB]=[SEG1~SEG8]
.seg_rck(seg_rck ), //74HC595的RCK管脚
.seg_sck(seg_sck ), //74HC595的SCK管脚
.seg_din(seg_din ) //74HC595的SER管脚
);

endmodule

4.6资源利用率

image.png

  • 寄存器数量:225个寄存器,占总数的5%。
  • SLICE数量:2047个SLICE,占总数的95%。其中,95%用于逻辑/ROM,70%用于Carry。
  • LUT4数量:4090个LUT4,占总数的95%。其中,1060个用于逻辑运算,3030个用于时序逻辑。
  • PIO站点数量:31个PIO站点和4个JTAG站点,占总数的33%。

5 遇到的主要难题

5.1 always语句赋值问题

在Verilog中,确保敏感信号具有相同的触发方式,并且在always块中只使用reg类型的变量进行赋值操作,并且这些赋值操作需要保持一致,即要么都是阻塞赋值,要么都是非阻塞赋值。这些规则有助于确保时序逻辑的正确性和避免潜在的问题,最重要的是可以减少大量调试时间


建议:

  1. 相同触发方式
    • always块中,确保所有敏感信号具有相同的触发方式,例如都是在时钟的上升沿或下降沿触发。混合使用不同的触发方式可能会导致意外的行为和时序问题。
  2. 只使用reg类型变量
    • always块中,应该只使用reg类型的变量进行赋值操作。reg类型的变量用于存储时序逻辑的状态,并且在时钟信号触发时更新。
  3. 保持一致的赋值方式
    • always块中,确保所有的赋值操作要么都是阻塞赋值(=),要么都是非阻塞赋值(<=)。混合使用这两种赋值方式可能会导致意外的逻辑错误和时序问题。


5.2 时序逻辑和时钟域交叉问题

在数字电路设计中,时钟信号的稳定性和时序逻辑的正确性至关重要。确保各模块在正确的时钟域中操作,避免时钟域交叉可能是一个巨大的工程。


5.3 注意webide的run.log文件报错

在进行fpga设计时,难免会出现因代码问题导致的映射错误。但是在使用webide设计时,如果有映射错误,只会在run.log文件的最后一行报错,并且报错后可以正常下载上一次jed文件,这是难以发现的,这会使得

6 未来的计划建议

该项目已经成功实现了二位十进制计算器的功能,并达到了预期指标。然而,还有许多可以提升与扩展的地方:

功能扩展与优化

  • 考虑增加更多功能,如连续运算,以提升计算器的实用性和功能性。
  • 优化除法算法,使得资源占用降低。

性能优化:

  • 对代码进行优化,降低资源占用。
附件下载
archive.rar
团队介绍
电子信息工程专业学生
团队成员
Peking
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号