Abstract: Verilog UART 设计第二版
代码编写思路
先说明几个重要的部分:
- 波特率计数器:表示一个数据位的持续时间,每个时钟上升沿计数一次,最大值为 $BAUD_CNT_THRESHOLD=CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1$,即时钟除以波特率再减一
- 波特率采样时钟:表示对每个数据位的采样时间,高电平持续时间为 $BAUD_CNT_THRESHOLD/2-3$ 到 $BAUD_CNT_THRESHOLD/2+3$,即持续六个时钟,采样六次
- 波特率时钟计数器:表示获取的数据位数(包括起始位和停止位),最大值为 $BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2)$,即有效数据位加二
下面简述代码思路。
接收模块
当检测到起始条件(下降沿)后,波特率计数器开始计数,当计数值达到 $BAUD_CNT_THRESHOLD/2-3$ 和 $BAUD_CNT_THRESHOLD/2+3$ 之间时,对输入进行六次采样,采样结束后,即波特率计数器数值达到 $BAUD_CNT_THRESHOLD/2+4$时,若高电平数目大于三,则保存采样结果为一,否则为零。采样的方法很简单,定义一个三位宽的寄存器,每采样一次寄存器加上本次的采样结果,最后取寄存器的最高位为结果。之后,在波特率计数器数值达到 $BAUD_CNT_THRESHOLD/2+5$ 时采样寄存器清零,在波特率计数器数值达到 $BAUD_CNT_THRESHOLD/2+6$ 时采样波特率时钟计数器加一。当波特率时钟计数器达到最大值时视为接收结束。可以看出,对于每个数据帧,接收模块实际上只检测了大约半个停止位,这样可以保证不漏采下一个数据的起始位。
发送模块
发送模块比接收模块要简单一些。读入要发送的数据后生成一个完整的数据帧,包括起始位和停止位,为了保证连续发送数据时起始位不混乱,实际上完整的数据帧为 {1,1,[有效数据],0}。当波特率计数器达到一时,将数据帧的最低位送出,最高位补零,从结果上看就是右移操作。与接收模块类似,当波特率时钟计数器达到最大值时视为发送接收,不过这里的最大值为 $BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2) + 1$,和数据帧的位宽对应。
模块代码
Rx 部分
// UART 接收模块 可选波特率 可选数据位 仅支持一位停止位
module UART_Rx #(
parameter CLK_FREQ_MHZ = 27,
parameter BAUD_RATE = 9600,
parameter DATA_WIDTH = 8
) (
input clk,
input rst_n,
input rx_data_ready, // 准备好接受串口数据
input rx_pin,
output [DATA_WIDTH-1:0] rx_data, // 接收到的串口数据 rx_data_ready和rx_data_valid均为高时送出
output rx_data_valid // 接收到的串口数据有效
);
localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1; // 波特率计数最大值
localparam BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2); // 波特率时钟计数最大值 数据位+起始位+结束位
reg rx_data_r0, rx_data_r1;
reg rx_data_t0, rx_data_t1;
reg rx_state; // 接收状态
reg rx_done; // 接收接收标识
reg [$clog2(BAUD_CNT_THRESHOLD)-1:0] baud_rate_cnt; // 波特率计数器
reg baud_rate_sample_clk; // 采样时钟 为高时采样
reg [$clog2(BAUD_CLK_CNT_THRESHOLD)-1:0] baud_rate_clk_cnt; // 波特率时钟计数
reg [2:0] rx_sample_bit; // 一位采样数据 六次采样
reg [BAUD_CLK_CNT_THRESHOLD-1:0] rx_data_frame_byte; // 完整数据帧
reg [DATA_WIDTH-1:0] rx_data_r;
reg rx_data_valid_r;
wire rx_negedge;
/****输入数据同步****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rx_data_r0 <= 1'b0;
rx_data_r1 <= 1'b0;
end
else begin
rx_data_r0 <= rx_pin;
rx_data_r1 <= rx_data_r0;
end
end
/******************/
/****同步后的数据暂存 边沿检测****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rx_data_t0 <= 1'b0;
rx_data_t1 <= 1'b0;
end
else begin
rx_data_t0 <= rx_data_r1;
rx_data_t1 <= rx_data_t0;
end
end
assign rx_negedge = rx_data_t1 && (~rx_data_t0);
/******************/
/****接收状态逻辑****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rx_state <= 1'b0;
end
else begin
if (rx_negedge) begin // 下降沿到来
rx_state <= 1'b1;
end
else if (rx_done) begin // 接收完成
rx_state <= 1'b0;
end
else if (baud_rate_clk_cnt=='d1 && rx_data_frame_byte[0]==1'b1) begin // 起始位采样后高电平居多
rx_state <= 1'b0;
end
else begin
rx_state <= rx_state; // 在一次接收中rx_state保持高电平
end
end
end
/******************/
/****接收结束逻辑****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rx_done <= 1'b0;
end
else begin
if (baud_rate_clk_cnt==BAUD_CLK_CNT_THRESHOLD) begin
rx_done <= 1'b1;
end
else begin
rx_done <= 1'b0;
end
end
end
/******************/
/****波特率计数器****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
baud_rate_cnt <= 'd0;
end
else begin
if (rx_state) begin // 接收状态
if (baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
baud_rate_cnt <= 'd0;
end
else begin
baud_rate_cnt <= baud_rate_cnt + 1'd1;
end
end
else begin
baud_rate_cnt <= 'd0;
end
end
end
/******************/
/****波特率采样时钟生成****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
baud_rate_sample_clk <= 1'b0;
end
else begin
if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 - 3) begin // 持续六个时钟
baud_rate_sample_clk <= 1'b1;
end
else if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 3) begin
baud_rate_sample_clk <= 1'b0;
end
end
end
/******************/
/****波特率时钟计数****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
baud_rate_clk_cnt <= 'd0;
end
else begin
if (rx_state) begin
if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 6) begin // 采样结束三个时钟后计数加一
baud_rate_clk_cnt <= baud_rate_clk_cnt + 1'd1;
end
else begin
baud_rate_clk_cnt <= baud_rate_clk_cnt;
end
end
else begin
baud_rate_clk_cnt <= 'd0;
end
end
end
/******************/
/****数据采集部分****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rx_sample_bit <= 3'd0;
end
else begin
if (baud_rate_sample_clk) begin // 采样时钟为高
rx_sample_bit <= rx_sample_bit+rx_data_r1;
end
else begin
if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 5) begin // 采样结束两个时钟后清零
rx_sample_bit <= 3'b0;
end
else begin
rx_sample_bit <= rx_sample_bit;
end
end
end
end
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rx_data_frame_byte <= 'd0;
end
else begin
if (rx_state) begin
if (baud_rate_cnt==BAUD_CNT_THRESHOLD/2 + 4) begin // 采样结束一个时钟后移位寄存采样数据
rx_data_frame_byte <= {rx_sample_bit[2], rx_data_frame_byte[BAUD_CLK_CNT_THRESHOLD-1:1]};
end
else begin
rx_data_frame_byte <= rx_data_frame_byte;
end
end
else begin
rx_data_frame_byte <= 'd0;
end
end
end
/******************/
/****送出接收到的数据****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
rx_data_r <= 'd0;
rx_data_valid_r <= 1'b0;
end
else begin
if (rx_done) begin
if (rx_data_ready) begin
rx_data_r <= rx_data_frame_byte[BAUD_CLK_CNT_THRESHOLD-2:1];
end
else begin
rx_data_r <= rx_data_r;
end
rx_data_valid_r <= 1'b1;
end
else begin
rx_data_r <= 'd0;
rx_data_valid_r <= 1'b0;
end
end
end
assign rx_data = rx_data_r;
assign rx_data_valid = rx_data_valid_r;
/******************/
endmodule
Tx 部分
// UART 发送模块 可选波特率 可选数据位 仅支持一位停止位
module UART_Tx #(
parameter CLK_FREQ_MHZ = 27,
parameter BAUD_RATE = 9600,
parameter DATA_WIDTH = 8
) (
input clk,
input rst_n,
input [DATA_WIDTH-1:0] tx_data, // 准备发送的数据
input tx_data_valid, // 发送的数据有效
output tx_data_ready, // 发送模块已准备好发送数据
output tx_pin // 发送的串口数据 tx_data_ready和tx_data_valid都为高时数据被发送
);
localparam BAUD_CLK_CNT_THRESHOLD = (DATA_WIDTH + 2) + 1; // 波特率时钟计数最大值 数据位+起始位(两位)+停止位(一位)
localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000000 / BAUD_RATE - 1; // 波特率计数最大值
reg [BAUD_CLK_CNT_THRESHOLD-1:0] tx_data_frame; // 准备发送的完整数据帧
reg tx_state; // 发送状态
reg tx_done; // 发送完成
reg [$clog2(BAUD_CNT_THRESHOLD)-1:0] baud_rate_cnt; // 波特率计数器
reg [$clog2(BAUD_CLK_CNT_THRESHOLD)-1:0] baud_rate_clk_cnt; // 波特率时钟计数
reg tx_pin_r;
/****发送数据帧生成****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
tx_data_frame <= 'd0;
end
else begin
if (tx_data_valid && tx_data_ready) begin
tx_data_frame <= {1'b1, 1'b1, tx_data, 1'b0};
end
else begin
if (baud_rate_cnt=='d2) begin // 低位发送后移位
tx_data_frame <= {1'b0, tx_data_frame[BAUD_CLK_CNT_THRESHOLD-1:1]}; // 数据帧移位
end
else begin
tx_data_frame <= tx_data_frame;
end
end
end
end
/******************/
/****发送状态逻辑****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
tx_state <= 1'b0;
end
else begin
if (tx_data_valid && tx_data_ready) begin
tx_state <= 1'b1;
end
else if (tx_done) begin
tx_state <= 1'b0;
end
else begin
tx_state <= tx_state;
end
end
end
/******************/
/****发送结束逻辑****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
tx_done <= 1'b0;
end
else begin
if (baud_rate_clk_cnt==BAUD_CLK_CNT_THRESHOLD) begin
tx_done <= 1'b1;
end
else begin
tx_done <= 1'b0;
end
end
end
/******************/
/****波特率计数器****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
baud_rate_cnt <= 'd0;
end
else begin
if (tx_state) begin // 接收状态
if (baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
baud_rate_cnt <= 'd0;
end
else begin
baud_rate_cnt <= baud_rate_cnt + 1'd1;
end
end
else begin
baud_rate_cnt <= 'd0;
end
end
end
/******************/
/****波特率时钟计数****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
baud_rate_clk_cnt <= 'd0;
end
else begin
if (~tx_done) begin
if (baud_rate_cnt=='d1) begin
baud_rate_clk_cnt <= baud_rate_clk_cnt + 1'd1;
end
else begin
baud_rate_clk_cnt <= baud_rate_clk_cnt;
end
end
else begin
baud_rate_clk_cnt <= 'd0;
end
end
end
/******************/
/****发送数据部分****/
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
tx_pin_r <= 1'b1;
end
else begin
if (baud_rate_cnt=='d1) begin // 数据帧移位前送出
tx_pin_r <= tx_data_frame[0]; // 数据低位送出
end
else if (~tx_state) begin
tx_pin_r <= 1'b1;
end
else begin
tx_pin_r <= tx_pin_r;
end
end
end
assign tx_data_ready = ~tx_state;
assign tx_pin = tx_pin_r;
/******************/
endmodule
Module-sim 测试代码
module UART_test #(
parameter CLK_FREQ_MHZ = 27,
parameter BAUD_RATE = 9600
) (
input sys_clk,
input rst_n,
input rx_pin, // UART 接收
output tx_pin // UART 发送
);
wire rx_data_valid; // 接收数据有效
wire tx_data_ready; // 准备好发送数据
wire [7:0] rx_data; // 接收到的数据
reg [7:0] rx_data_r; // 接收的数据缓存
reg tx_data_valid; // 发送的数据有效
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
rx_data_r <= 8'd0;
end
else begin
if (rx_data_valid) begin
rx_data_r <= rx_data;
end
else begin
rx_data_r <= rx_data_r;
end
end
end
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
tx_data_valid <= 1'b0;
end
else begin
if (rx_data_valid && ~tx_data_ready) begin // 接收到数据但是发送忙
tx_data_valid <= 1'b1;
end
else if (~rx_data_valid && tx_data_ready) begin // 可以发送
tx_data_valid <= 1'b0;
end
else if (rx_data_valid && tx_data_ready) begin // 可以发送
tx_data_valid <= 1'b1;
end
else if (~rx_data_valid && ~tx_data_ready) begin // 可以发送
tx_data_valid <= 1'b0;
end
else begin
tx_data_valid <= tx_data_valid;
end
end
end
UART_Rx #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ),
.BAUD_RATE(BAUD_RATE)
) u_uart_rx (
.clk(sys_clk),
.rst_n(rst_n),
.rx_data_ready(1'b1), // 准备好接受串口数据
.rx_pin(rx_pin),
.rx_data(rx_data), // 接收到的串口数据 rx_data_ready和rx_data_valid均为高时送出
.rx_data_valid(rx_data_valid) // 接收到的串口数据有效
);
UART_Tx #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ),
.BAUD_RATE(BAUD_RATE)
) u_uart_tx (
.clk(sys_clk),
.rst_n(rst_n),
.tx_data(rx_data_r), // 准备发送的数据
.tx_data_valid(tx_data_valid), // 发送的数据有效
.tx_data_ready(tx_data_ready), // 发送模块已准备好发送数据
.tx_pin(tx_pin) // 发送的串口数据 tx_data_ready和tx_data_valid都为高时数据被发送
);
endmodule
Gowin FPGA 烧录测试代码
// 收到数据后发送回去
module UART_test #(
parameter CLK_FREQ_MHZ = 27,
parameter BAUD_RATE = 921600
) (
input sys_clk,
input rst_n,
input rx_pin, // UART 接收
output tx_pin // UART 发送
);
wire rx_data_valid; // 接收数据有效
wire tx_data_ready; // 准备好发送数据
wire [7:0] rx_data; // 接收到的数据
reg [7:0] rx_data_r; // 接收的数据缓存
reg tx_data_valid; // 发送的数据有效
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
rx_data_r <= 8'd0;
end
else begin
if (rx_data_valid) begin
rx_data_r <= rx_data;
end
else begin
rx_data_r <= rx_data_r;
end
end
end
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
tx_data_valid <= 1'b0;
end
else begin
if (rx_data_valid && ~tx_data_ready) begin // 接收到数据但是发送忙
tx_data_valid <= 1'b1;
end
else if (~rx_data_valid && tx_data_ready) begin // 可以发送
tx_data_valid <= 1'b0;
end
else if (rx_data_valid && tx_data_ready) begin // 可以发送
tx_data_valid <= 1'b1;
end
else if (~rx_data_valid && ~tx_data_ready) begin // 可以发送
tx_data_valid <= 1'b0;
end
else begin
tx_data_valid <= tx_data_valid;
end
end
end
UART_Rx #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ),
.BAUD_RATE(BAUD_RATE)
) u_uart_rx (
.clk(sys_clk),
.rst_n(rst_n),
.rx_data_ready(1'b1), // 准备好接受串口数据
.rx_pin(rx_pin),
.rx_data(rx_data), // 接收到的串口数据 rx_data_ready和rx_data_valid均为高时送出
.rx_data_valid(rx_data_valid) // 接收到的串口数据有效
);
UART_Tx #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ),
.BAUD_RATE(BAUD_RATE)
) u_uart_tx (
.clk(sys_clk),
.rst_n(rst_n),
.tx_data(rx_data_r), // 准备发送的数据
.tx_data_valid(tx_data_valid), // 发送的数据有效
.tx_data_ready(tx_data_ready), // 发送模块已准备好发送数据
.tx_pin(tx_pin) // 发送的串口数据 tx_data_ready和tx_data_valid都为高时数据被发送
);
endmodule