一、顶层代码
module test#(
parameter SYS_CLK_FREQ = 26'd50_000_000, // 系统时钟频率50MHz
parameter BAUD_RATE = 9600, // 串口波特率
parameter CHECK_BIT = "None" // 校验位类型(None表示无校验)
)(
input wire sys_clk, // 系统时钟输入
input wire sys_rst, // 系统复位(低电平有效)
input wire [3:0] key_in, // 按键输入
output wire [7:0] sel, // 数码管位选信号
output wire [7:0] seg, // 数码管段选信号
output reg [7:0] led, // LED指示灯
output wire tx // 串口发送线
);
// ========== 状态定义 ==========
localparam STOP = 3'd0; // 停止状态
localparam START = 3'd1; // 启动状态
// ========== 信号定义 ==========
wire [3:0] key_data; // 按键消抖后的数据
reg [31:0] data; // 数码管显示数据(8位BCD码)
reg [3:0] status_count; // 当前状态(START/STOP)
reg [7:0] data_count; // 计数器值
reg [3:0] tx_len; // 串口发送数据长度
wire start_send; // 发送启动信号
reg start_send_char; // 字符发送启动标志
reg start_send_status; // 状态发送启动标志
wire tx_done; // 发送完成标志
reg [7:0] char_idx; // 当前发送字符索引
reg [7:0] tx_buffer[0:255]; // 发送缓冲区(存储ASCII字符)
reg tx_done0; // 发送完成信号打拍(同步用)
reg tx_done1; // 发送完成信号打拍(同步用)
wire send_next; // 发送下一个字符的触发信号
// ========== 模块实例化 ==========
// 数码管显示模块
seg seg_inst(
.clk (sys_clk),
.rst (sys_rst),
.seg (seg),
.sel (sel),
.dsp_data (data) // 显示数据输入
);
// 按键消抖模块
key key_inst(
.sys_clk (sys_clk),
.sys_rst (sys_rst),
.key_in (key_in),
.key_data (key_data) // 消抖后的按键数据
);
// 串口发送模块
uart_tx#(
.CLOCK (SYS_CLK_FREQ),
.BUAD (BAUD_RATE),
.CHECK_BIT(CHECK_BIT)
) uart_tx_list(
.clk (sys_clk),
.rst (sys_rst),
.tx_data (tx_buffer[char_idx]), // 当前发送字符
.tx_data_vld (start_send), // 发送使能
.ready (tx_done), // 发送完成标志
.tx (tx) // 串口输出
);
// ========== LED控制逻辑 ==========
always @(posedge sys_clk or negedge sys_rst) begin
if (!sys_rst)
led <= 8'b1111_1111; // 复位时LED全灭
else if (status_count == START)
led <= 8'b1111_1110; // START状态时点亮最低位LED
else
led <= 8'b1111_1111; // 其他状态LED全灭
end
// ========== 数码管显示数据处理 ==========
always @(posedge sys_clk or negedge sys_rst) begin
if (sys_rst == 1'b0)
// 复位时显示" 0"(11表示不显示)
data <= {4'd10, 4'd11, 4'd11, 4'd11, 4'd11, 4'd11, 4'd11, 4'd0};
else begin
// 动态更新显示数据(BCD码转换)
data[11:8] <= (data_count > 99) ? (data_count / 100) : 4'd11; // 百位
data[7:4] <= (data_count > 9) ? (data_count % 100 / 10) : 4'd11; // 十位
data[3:0] <= data_count % 10; // 个位
end
end
// ========== 按键处理逻辑 ==========
always @(posedge sys_clk or negedge sys_rst) begin
if (sys_rst == 1'b0) begin
data_count <= 8'b0; // 计数器清零
status_count <= START; // 初始状态为START
start_send_char <= 0; // 发送标志清零
end else begin
case (key_data) // 根据按键值更新状态
4'b0001: begin // 按键1:切换START/STOP状态
if (status_count == START)
status_count <= STOP;
else if (status_count == STOP)
status_count <= START;
end
4'b0010: begin // 按键2:计数器加1(仅在START状态)
if (status_count == START)
data_count <= data_count + 8'd1;
end
4'b0100: begin // 按键3:计数器减1(仅在STOP状态且值>0)
if ((status_count == STOP) && (data_count > 0))
data_count <= data_count - 1;
end
4'b1000: begin // 按键4:触发串口发送
start_send_char <= 1;
end
default: start_send_char <= 0; // 无按键时清零
endcase
end
end
// ========== 发送缓冲区初始化 ==========
always @(posedge sys_clk or negedge sys_rst) begin
if (!sys_rst) begin
// 初始化发送缓冲区内容为"COUNT:"
tx_buffer[0] <= "C";
tx_buffer[1] <= "O";
tx_buffer[2] <= "U";
tx_buffer[3] <= "N";
tx_buffer[4] <= "T";
tx_buffer[5] <= ":";
end
end
// ========== 串口发送数据格式化 ==========
always @(posedge sys_clk or negedge sys_rst) begin
if (!sys_rst) begin
tx_len <= 7; // 默认发送长度("COUNT:X")
end else begin
// 根据计数器值动态生成ASCII字符
if (data_count > 99) begin // 3位数
tx_len <= 9;
tx_buffer[6] <= data_count / 100 + 8'h30; // 百位ASCII
tx_buffer[7] <= data_count % 100 / 10 + 8'h30; // 十位ASCII
tx_buffer[8] <= data_count % 10 + 8'h30; // 个位ASCII
end else if (data_count > 9) begin // 2位数
tx_len <= 8;
tx_buffer[6] <= data_count / 10 + 8'h30; // 十位ASCII
tx_buffer[7] <= data_count % 10 + 8'h30; // 个位ASCII
end else begin // 1位数
tx_len <= 7;
tx_buffer[6] <= data_count + 8'h30; // 个位ASCII
end
end
end
// ========== 串口发送完成检测 ==========
always @(posedge sys_clk or negedge sys_rst) begin
if (!sys_rst) begin
tx_done0 <= 0;
tx_done1 <= 0;
end else begin
tx_done0 <= tx_done; // 打拍同步
tx_done1 <= tx_done0; // 用于边沿检测
end
end
// 发送完成信号的上升沿检测
assign send_next = ~tx_done1 && tx_done0;
// 发送使能信号(按键触发或状态机触发)
assign start_send = start_send_char || start_send_status;
// ========== 串口发送状态机 ==========
always @(posedge sys_clk or negedge sys_rst) begin
if (!sys_rst) begin
char_idx <= 0;
start_send_status <= 0;
end else begin
if (start_send) begin // 发送使能有效
if (char_idx < tx_len - 1) begin // 未发送完所有字符
start_send_status <= 1;
if (send_next) // 检测到发送完成上升沿
char_idx <= char_idx + 1; // 发送下一个字符
end else begin
start_send_status <= 0; // 发送完成
end
end else
char_idx <= 0; // 发送结束或未启动时复位索引
end
end
endmodule
二、数码管模块
// 数码管驱动模块
// 功能:实现8位数码管的动态扫描显示
module seg
(
input wire clk, // 系统时钟输入(假设50MHz)
input wire rst, // 异步复位信号,低电平有效
input wire [31:0] dsp_data, // 显示数据输入,32位分为8个4位BCD码
output reg [7:0] seg, // 段选信号输出,控制显示内容
output reg [7:0] sel // 位选信号输出,控制数码管选择
);
// ===== 数码管段码定义(共阴极数码管)=====
// 格式:DP g f e d c b a (高位到低位)
localparam [7:0] DIGIT0 = 8'b1100_0000; // 数字0的段码(C0)
localparam [7:0] DIGIT1 = 8'b1111_1001; // 数字1的段码(F9)
localparam [7:0] DIGIT2 = 8'b1010_0100; // 数字2的段码(A4)
localparam [7:0] DIGIT3 = 8'b1011_0000; // 数字3的段码(B0)
localparam [7:0] DIGIT4 = 8'b1001_1001; // 数字4的段码(99)
localparam [7:0] DIGIT5 = 8'b1001_0010; // 数字5的段码(92)
localparam [7:0] DIGIT6 = 8'b1000_0010; // 数字6的段码(82)
localparam [7:0] DIGIT7 = 8'b1111_1000; // 数字7的段码(F8)
localparam [7:0] DIGIT8 = 8'b1000_0000; // 数字8的段码(80)
localparam [7:0] DIGIT9 = 8'b1001_0000; // 数字9的段码(90)
localparam [7:0] DIGITX = 8'b1011_1111; // 特殊符号"X"的段码
localparam [7:0] DIGOFF = 8'b1111_1111; // 关闭显示的段码(FF)
localparam [7:0] DIGITC = 8'hC6; // 特殊字符"C"的段码
// 1ms计数器的最大值(50MHz时钟下,50000个周期=1ms)
localparam DSP_COUNT = 20'd50000;
// ===== 内部寄存器定义 =====
reg [19:0] dsp_count; // 1ms计数器
reg [3:0] bits; // 当前扫描的数码管编号(0-7)
reg [3:0] bcd; // 当前数码管对应的BCD码
// ===== 1ms定时计数器 =====
always @(posedge clk or negedge rst) begin
if(!rst) begin
dsp_count <= 20'd0; // 复位时清零计数器
end else begin
if (dsp_count == DSP_COUNT-1) begin
dsp_count <= 20'd0; // 计数到1ms时归零
end else begin
dsp_count <= dsp_count + 20'd1; // 计数器递增
end
end
end
// ===== 位选控制逻辑 =====
always @(posedge clk or negedge rst) begin
if(!rst) begin
sel <= 8'b1111_1111; // 复位时关闭所有数码管
bits <= 4'd0; // 复位时从第0位数码管开始
end else begin
if(dsp_count == DSP_COUNT-1) begin // 每1ms更新一次
if(bits == 4'd8) begin
bits <= 4'd0; // 扫描完8位数码管后归零
end else begin
bits <= bits + 4'd1; // 指向下一个数码管
end
// 根据当前数码管编号选择对应的位选信号和BCD码
case(bits)
4'd0: begin sel <= 8'b1111_1110; bcd <= dsp_data[31:28]; end // 第1位数码管
4'd1: begin sel <= 8'b1111_1101; bcd <= dsp_data[27:24]; end // 第2位数码管
4'd2: begin sel <= 8'b1111_1011; bcd <= dsp_data[23:20]; end // 第3位数码管
4'd3: begin sel <= 8'b1111_0111; bcd <= dsp_data[19:16]; end // 第4位数码管
4'd4: begin sel <= 8'b1110_1111; bcd <= dsp_data[15:12]; end // 第5位数码管
4'd5: begin sel <= 8'b1101_1111; bcd <= dsp_data[11:8]; end // 第6位数码管
4'd6: begin sel <= 8'b1011_1111; bcd <= dsp_data[7:4]; end // 第7位数码管
4'd7: begin sel <= 8'b0111_1111; bcd <= dsp_data[3:0]; end // 第8位数码管
default: sel <= 8'b1111_1111; // 默认关闭所有数码管
endcase
end
end
end
// ===== 段选控制逻辑 =====
always @(posedge clk or negedge rst) begin
if(!rst) begin
seg <= DIGOFF; // 复位时关闭显示
end else begin
// 根据BCD码选择对应的段码
case(bcd)
4'd0: seg <= DIGIT0; // 显示数字0
4'd1: seg <= DIGIT1; // 显示数字1
4'd2: seg <= DIGIT2; // 显示数字2
4'd3: seg <= DIGIT3; // 显示数字3
4'd4: seg <= DIGIT4; // 显示数字4
4'd5: seg <= DIGIT5; // 显示数字5
4'd6: seg <= DIGIT6; // 显示数字6
4'd7: seg <= DIGIT7; // 显示数字7
4'd8: seg <= DIGIT8; // 显示数字8
4'd9: seg <= DIGIT9; // 显示数字9
4'd10: seg <= DIGITC; // 显示特殊字符"C"
default: seg <= DIGOFF; // 其他值关闭显示
endcase
end
end
endmodule
三、按键驱动模块
module key(
input wire sys_clk,
input wire sys_rst,
input wire [3:0] key_in,
output reg [3:0] key_data
);
//按键键值定义
localparam KEY_VAL_S1 =4'b0001;
localparam KEY_VAL_S2 =4'b0010;
localparam KEY_VAL_S3 =4'b0100;
localparam KEY_VAL_S4=4'b1000;
localparam KEY_VAL_NL=4'b1111;
localparam KEY_COUNT_MAX =20'd500000;
//按键状态定义
localparam IDLE =3'd0;
localparam PRESS =3'd1;
localparam RELEASE =3'd2;
//按键扫描计数器,每隔0.01秒扫描一次按键
reg [19:0] key_count;
//寄存器的定义用来存储不同的状态
reg [2:0] key_status;
//按键计数更新
always@(posedge sys_clk or negedge sys_rst) begin
if(sys_rst==1'b0)
key_count <=0;
else begin
if(key_count == KEY_COUNT_MAX-1)begin
key_count <= 0;
end else
key_count = key_count + 1;
end
end
//按键状态更新
always@(posedge sys_clk or negedge sys_rst)begin
if(sys_rst==1'b0)begin//复位
key_data <= KEY_VAL_NL;
key_status<=IDLE;
end else begin
if(key_data == KEY_VAL_NL) begin
if(key_count==KEY_COUNT_MAX-1)begin
if(key_status==IDLE)begin//当前没有按键时,并且按键扫描时间到,进行判断
if(key_in!=4'b1111)//当按键键不等于1111
key_status<=PRESS;//按键处于按下状态
end else if(key_status==PRESS)begin//在有按键按下,赋予键值
case(key_in)
4'b1110: begin key_data <=KEY_VAL_S1; key_status <=RELEASE; end
4'b1101: begin key_data <=KEY_VAL_S2; key_status <=RELEASE; end
4'b1011: begin key_data <=KEY_VAL_S3; key_status <=RELEASE; end
4'b0111: begin key_data <=KEY_VAL_S4; key_status <=RELEASE; end
default: begin key_data <=KEY_VAL_NL; key_status <=IDLE; end
endcase
end else begin
if(key_in == 4'b1111)
key_status <=IDLE;
end
end
end else
key_data <=KEY_VAL_NL;
end
end
endmodule
四、串口驱动模块
// UART发送模块
// 功能:实现UART串口数据发送,支持可配置波特率和校验位
module uart_tx #(
parameter CLOCK = 26'd50_000_000, // 系统时钟频率(默认50MHz)
parameter BUAD = 9600, // 波特率(默认9600)
parameter CHECK_BIT = "None" // 校验位类型:"None"/"Odd"/"Even"
)(
input clk, // 系统时钟
input rst, // 异步复位,低电平有效
input [7:0] tx_data, // 待发送数据(8位)
input tx_data_vld, // 数据有效信号(高电平有效)
output wire ready, // 发送就绪信号(高电平表示可以接收新数据)
output reg tx // 串行数据输出
);
// ===== 参数定义 =====
parameter MAX_1bit = CLOCK/BUAD; // 每bit所需的时钟周期数
// 状态定义(独热码编码)
localparam IDLE = 5'b00001, // 空闲状态
START = 5'b00010, // 起始位
DATA = 5'b00100, // 数据位
CHECK = 5'b01000, // 校验位
STOP = 5'b10000; // 停止位
// ===== 寄存器定义 =====
reg [4:0] cstate; // 当前状态
reg [4:0] nstate; // 下一状态
reg [19:0] cnt_baud; // 波特率计数器
reg [2:0] cnt_bit; // 位计数器(计数当前发送的bit数)
reg [3:0] bit_max; // 各状态需要计数的最大bit数
reg [7:0] tx_data_r; // 发送数据寄存器
// ===== 状态转移条件 =====
wire IDLE_START; // 空闲→起始位
wire START_DATA; // 起始位→数据位
wire DATA_CHECK; // 数据位→校验位
wire DATA_STOP; // 数据位→停止位(无校验时)
wire CHECK_STOP; // 校验位→停止位
wire STOP_IDLE; // 停止位→空闲
// ===== 计数器控制信号 =====
wire add_cnt_baud; // 波特率计数器递增使能
wire end_cnt_baud; // 波特率计数器结束(达到1bit时间)
wire add_cnt_bit; // 位计数器递增使能
wire end_cnt_bit; // 位计数器结束(达到当前状态所需bit数)
// ===== 校验位计算 =====
wire check_val; // 校验位值
// ==============================================
// 波特率计数器(控制每个bit的持续时间)
// ==============================================
always @(posedge clk or negedge rst) begin
if(!rst) begin
cnt_baud <= 'd0;
end else if(add_cnt_baud) begin
if(end_cnt_baud) begin
cnt_baud <= 'd0; // 计数器归零
end else begin
cnt_baud <= cnt_baud + 1'd1; // 计数器递增
end
end
end
assign add_cnt_baud = (cstate != IDLE); // 非空闲状态下计数
assign end_cnt_baud = (add_cnt_baud && (cnt_baud == MAX_1bit - 1'd1)); // 达到1bit时间
// ==============================================
// 位计数器(计数当前状态已发送的bit数)
// ==============================================
always @(posedge clk or negedge rst) begin
if(!rst) begin
cnt_bit <= 'd0;
end else if(add_cnt_bit) begin
if(end_cnt_bit) begin
cnt_bit <= 'd0; // 计数器归零
end else begin
cnt_bit <= cnt_bit + 1'd1; // 计数器递增
end
end
end
assign add_cnt_bit = end_cnt_baud; // 每bit时间结束时递增
assign end_cnt_bit = (add_cnt_bit && (cnt_bit == bit_max - 1'd1)); // 达到当前状态所需bit数
// ==============================================
// 各状态需要计数的bit数
// ==============================================
always @(*) begin
case (cstate)
IDLE : bit_max = 'd0; // 空闲状态不计数
START : bit_max = 'd1; // 起始位(1bit)
DATA : bit_max = 'd8; // 数据位(8bit)
CHECK : bit_max = 'd1; // 校验位(1bit)
STOP : bit_max = 'd1; // 停止位(1bit)
default: bit_max = 'd0;
endcase
end
// ==============================================
// 状态转移条件
// ==============================================
assign IDLE_START = (cstate == IDLE) && tx_data_vld; // 空闲时收到有效数据
assign START_DATA = (cstate == START) && end_cnt_bit; // 起始位发送完成
assign DATA_STOP = (cstate == DATA) && end_cnt_bit && (CHECK_BIT == "None"); // 无校验时直接到停止位
assign DATA_CHECK = (cstate == DATA) && end_cnt_bit && (CHECK_BIT != "None"); // 有校验时到校验位
assign CHECK_STOP = (cstate == CHECK) && end_cnt_bit; // 校验位发送完成
assign STOP_IDLE = (cstate == STOP) && end_cnt_bit; // 停止位发送完成
// ==============================================
// 状态寄存器
// ==============================================
always @(posedge clk or negedge rst) begin
if(!rst) begin
cstate <= IDLE; // 复位到空闲状态
end else begin
cstate <= nstate; // 状态更新
end
end
// ==============================================
// 状态转移逻辑(有限状态机)
// ==============================================
always @(*) begin
case(cstate)
IDLE: begin
if (IDLE_START) nstate = START; // 收到数据,进入起始位
else nstate = cstate; // 保持空闲
end
START: begin
if (START_DATA) nstate = DATA; // 起始位完成,进入数据位
else nstate = cstate; // 保持起始位
end
DATA: begin
if (DATA_CHECK) nstate = CHECK; // 有校验时进入校验位
else if (DATA_STOP) nstate = STOP; // 无校验时直接到停止位
else nstate = cstate; // 保持数据位
end
CHECK: begin
if (CHECK_STOP) nstate = STOP; // 校验位完成,进入停止位
else nstate = cstate; // 保持校验位
end
STOP: begin
if (STOP_IDLE) nstate = IDLE; // 停止位完成,回到空闲
else nstate = cstate; // 保持停止位
end
default: nstate = cstate; // 默认保持当前状态
endcase
end
// ==============================================
// 发送数据寄存器(锁存输入数据)
// ==============================================
always @(posedge clk or negedge rst) begin
if (!rst) begin
tx_data_r <= 'd0; // 复位清零
end else if (tx_data_vld) begin
tx_data_r <= tx_data; // 锁存有效数据
end
end
// ==============================================
// 校验位计算
// ==============================================
// 奇校验:1的个数为奇数时校验位=0
// 偶校验:1的个数为偶数时校验位=0
assign check_val = (CHECK_BIT == "Odd") ? ~^tx_data_r : ^tx_data_r;
// ==============================================
// 串行数据输出
// ==============================================
always @(*) begin
case (cstate)
IDLE : tx = 1'b1; // 空闲时保持高电平
START : tx = 1'b0; // 起始位(低电平)
DATA : tx = tx_data_r[cnt_bit]; // 数据位(LSB first)
CHECK : tx = check_val; // 校验位
STOP : tx = 1'b1; // 停止位(高电平)
default: tx = 1'b1;
endcase
end
// ==============================================
// 就绪信号(空闲时可接收新数据)
// ==============================================
assign ready = (cstate == IDLE);
endmodule
五、管脚约束
声明: 本代码借鉴网络资料,如有侵权请联系作者删除,如有错误请在评论区或者私信作者纠正。