1、目的:
(1)熟练应用PLL来产生不同的时钟频率
(2)熟练应用FIFO对多比特数据进行跨时钟域(Clock Domain Crossing,CDC)的进行处理
(3)熟练应用同步器对单比特数据进行跨时钟域的进行处理
(3)加深对UART的理解和使用
2、特点:
(1)接收时钟频率为100m,发送时钟频率为50m(不然理论上不需要FIFO做缓冲,但其实作用也不大,因为比特率是固定的)
(2)使用FIFO做数据缓冲和跨时钟域处理
(3)PLL采用Xilinx的Clocking Wizard IP核,FIFO采用Xilinx的FIFO Generator IP核
3、常见问题:
(1)为什么跨时钟域需要进行同步处理?
在数字电路设计中,一个时钟域(clock domain)是由一个时钟信号控制的一组逻辑元件的集合。时钟信号的上升沿或下降沿触发这些元件的操作。在复杂的系统中,可能存在多个时钟域,这些时钟域可能由不同的时钟信号驱动。
以下是跨时钟域同步处理的主要原因:
-
时序不同步: 不同时钟域的时钟信号可能由于时钟源的不同、时钟树路径的不同等原因导致时序存在差异。如果直接将信号从一个时钟域传递到另一个时钟域,可能会发生时序不同步,导致数据的采样错误。
-
元件操作时钟: 不同时钟域的元件可能在不同的时钟信号的上升沿或下降沿触发。如果信号跨越时钟域,可能导致接收端在错误的时钟边沿进行采样,造成数据错误。
-
时钟域边界: 在时钟域之间进行信号传递时,时钟域的边界可能存在不同的延迟,这可能导致数据不同步。
(2)为什么要进行延迟打拍?
在数字电路设计中,延迟打拍(Delay Insertion)是指向电路中有意地引入延迟元素或延迟锁存器的过程,以满足设计中的时序要求、解决时序相关问题,或者实现特定的功能。
以下是一些延迟打拍的常见原因:
-
时序要求满足: 在一些高性能数字电路中,需要确保电路在时序方面满足特定的要求。通过在关键路径上插入延迟元素,可以调整信号传播速度,使得整个电路的时序性能得到改善。
-
解决时序相关问题: 在时序相关的设计中,可能会出现由于不同逻辑路径的信号传播速度不同而导致的时序问题,如时序违规或数据不稳定。通过延迟打拍,可以调整信号的到达时间,以解决这些时序相关的问题。
-
时钟域对齐: 在异步时钟域之间进行信号传递时,为了避免时序不同步的问题,可能需要在信号路径上引入延迟元素,以确保在接收端的时钟边沿上进行合适的采样。
-
时序兼容性: 在某些情况下,不同的电路或模块可能有不同的时序性能。通过引入延迟元素,可以使得这些模块在整体上具有更好的时序兼容性。
4、电路结构:
(1)顶层模块uart
延迟计数使能模块
延迟计数同步模块(不然不能正常接收start_cnt)
延迟计数和uart_rx_done_d0信号模块
延迟打拍,消除亚稳态,确保数据正确传输
module uart(
input clk,
input rst_n,
input uart_rxd,
output uart_txd
);
wire uart_rx_done ;
wire [7:0] uart_rx_data ;
wire [7:0] uart_tx_data ;
wire full ;
wire empty ;
wire locked ;
wire clk_100m ;
wire clk_50m ;
reg [4:0] delay_cnt ;
reg uart_rx_done_d0 ;
reg uart_rx_done_d1 ;
reg uart_rx_done_d2 ;
reg start_cnt ;
reg start_cnt_d0 ;
reg start_cnt_d1 ;
//延迟计数使能模块
always@(posedge clk_100m or negedge rst_n)
if(rst_n == 1'b0)
start_cnt <= 1'b0;
else
if(delay_cnt == 5'd20)
start_cnt <= 1'b0;
else if(uart_rx_done)
start_cnt <= 1'b1;
else
start_cnt <= start_cnt;
//延迟计数同步模块,不然不能正常接收start_cnt
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0) begin
start_cnt_d0 <= 1'b0;
start_cnt_d1 <= 1'b0;
end
else begin
start_cnt_d0 <= start_cnt;
start_cnt_d1 <= start_cnt_d0;
end
//延迟计数和uart_rx_done_d0信号模块
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0) begin
delay_cnt <= 5'd0;
uart_rx_done_d0 <= 1'b0;
end
else
if(start_cnt_d1)
if(delay_cnt == 5'd20) begin
delay_cnt <= 5'd0;
uart_rx_done_d0 <= 1'b1;
end
else begin
delay_cnt <= delay_cnt + 1'b1;
uart_rx_done_d0 <= 1'b0;
end
else begin
delay_cnt <= 1'b0;
uart_rx_done_d0 <= 1'b0;
end
//延迟打拍,消除亚稳态,确保数据正确传输
always@(posedge clk_50m or negedge rst_n)
if(rst_n == 1'b0)begin
uart_rx_done_d1 <= 1'b0;
uart_rx_done_d2 <= 1'b0;
end
else begin
uart_rx_done_d1 <= uart_rx_done_d0;
uart_rx_done_d2 <= uart_rx_done_d1;
end
clk_wiz_0 u_clk_wiz_0
(
// Clock out ports
.clk_100m(clk_100m), // output clk_100m
.clk_50m(clk_50m), // output clk_50m
// Status and control signals
.locked(locked), // output locked
// Clock in ports
.clk_in1(clk) // input clk_in1
);
fifo_generator_0 u_fifo_generator_0 (
.rst(~rst_n), // input wire rst
.wr_clk(clk_100m), // input wire wr_clk
.rd_clk(clk_50m), // input wire rd_clk
.din(uart_rx_data), // input wire [7 : 0] din
.wr_en(uart_rx_done&&(~full)), // input wire wr_en
.rd_en(uart_rx_done_d1&&(~empty)), // input wire rd_en
.dout(uart_tx_data), // output wire [7 : 0] dout
.full(full), // output wire full
.empty(empty) // output wire empty
);
uart_rx u_uart_rx(
.clk (clk_100m),
.rst_n (rst_n),
.uart_rxd (uart_rxd),
.uart_rx_done (uart_rx_done),
.uart_rx_data (uart_rx_data)
);
uart_tx u_uart_tx(
.clk (clk_50m),
.rst_n (rst_n),
.uart_tx_en (uart_rx_done_d2),
.uart_tx_data (uart_tx_data),
.uart_tx_busy (),
.uart_txd (uart_txd)
);
endmodule
(2)串口接收模块uart_rx
捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
多级同步模块
rx_flag信号模块
波特率计数模块
接收数据计数模块
接收数据模块
接收完成模块
module uart_rx(
input clk ,
input rst_n ,
input uart_rxd ,
output reg uart_rx_done,
output reg [7:0] uart_rx_data
);
parameter CLK_FREQ = 100000000; //时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次,相当于分频,由高频到低频
reg uart_rxd_d0;
reg uart_rxd_d1;
reg uart_rxd_d2;
reg rx_flag; //接受标志寄存器
reg [3:0] rx_data_cnt; //接收数据计数器
reg [15:0] baud_cnt; //波特率计数器
reg [7:0] rx_data_t; //接收数据寄存器
wire start_en; //开始标志位
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign start_en = uart_rxd_d2 && (~uart_rxd_d1) && (~rx_flag);
//多级同步模块,消除亚稳态
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)begin
uart_rxd_d0 <= 1'b1;
uart_rxd_d1 <= 1'b1;
uart_rxd_d2 <= 1'b1;
end
else begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
uart_rxd_d2 <= uart_rxd_d1;
end
//rx_flag信号模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
rx_flag <= 1'b0;
else
if(start_en)
rx_flag <= 1'b1;
else if((uart_rxd_d1 == 1'b1) && (rx_data_cnt >= 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1))
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
//波特率计数模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
baud_cnt <= 'b0;
else
if(rx_flag)
if(baud_cnt < BAUD_CNT_MAX - 1'b1)
baud_cnt <= baud_cnt + 16'b1;
else
baud_cnt <= 'b0;
else
baud_cnt <= 'b0;
//接收数据计数模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
rx_data_cnt <= 'b0;
else
if(rx_flag)
if(baud_cnt == BAUD_CNT_MAX - 1'b1)
rx_data_cnt <= rx_data_cnt + 1'b1;
else
rx_data_cnt <= rx_data_cnt;
else
rx_data_cnt <= 'b0;
//接收数据模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
rx_data_t <= 'b0;
else begin
if(rx_flag)
if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1)
case(rx_data_cnt)
4'd1 : rx_data_t[0] <= uart_rxd_d1;
4'd2 : rx_data_t[1] <= uart_rxd_d1;
4'd3 : rx_data_t[2] <= uart_rxd_d1;
4'd4 : rx_data_t[3] <= uart_rxd_d1;
4'd5 : rx_data_t[4] <= uart_rxd_d1;
4'd6 : rx_data_t[5] <= uart_rxd_d1;
4'd7 : rx_data_t[6] <= uart_rxd_d1;
4'd8 : rx_data_t[7] <= uart_rxd_d1;
default:rx_data_t <= rx_data_t;
endcase
else
rx_data_t <= rx_data_t;
else
rx_data_t <= 'b0;
end
//接收完成模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0) begin
uart_rx_done <= 1'b0;
uart_rx_data <= 'b0;
end
else
if((uart_rxd_d1 == 1'b1) && (rx_data_cnt >= 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1)) begin
uart_rx_done <= 1'b1;
uart_rx_data <= rx_data_t;
end
else begin
uart_rx_done <= 1'b0;
uart_rx_data <= uart_rx_data;
end
endmodule
(3)串口发送模块uart_tx
uart_tx_busy和tx_data_t信号,开始发送标志模块
波特率计数模块
发送数据计数模块
发送数据模块
module uart_tx(
input clk,
input rst_n,
input uart_tx_en,
input [7:0] uart_tx_data,
output reg uart_tx_busy,
output reg uart_txd
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
reg [7:0] tx_data_t;
reg [15:0] baud_cnt;
reg [3:0] tx_cnt;
//uart_tx_busy和tx_data_t信号,开始发送标志模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0) begin
uart_tx_busy <= 1'b0;
tx_data_t <= 8'b0;
end
else
if(uart_tx_en) begin
uart_tx_busy <= 1'b1;
tx_data_t <= uart_tx_data;
end
else if(tx_cnt == 4'd9 && baud_cnt >= BAUD_CNT_MAX - BAUD_CNT_MAX/16) begin
uart_tx_busy <= 1'b0;
tx_data_t <= 8'b0;
end
else begin
uart_tx_busy <= uart_tx_busy;
tx_data_t <= tx_data_t;
end
//波特率计数模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
baud_cnt <= 16'b0;
else
if(uart_tx_busy)
if(baud_cnt < BAUD_CNT_MAX - 1'b1)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 16'b0;
else
baud_cnt <= 16'b0;
//发送数据计数模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
tx_cnt <= 16'b0;
else
if(uart_tx_busy)
if(baud_cnt == BAUD_CNT_MAX - 1'b1)
tx_cnt <= tx_cnt + 1'b1;
else
tx_cnt <= tx_cnt;
else
tx_cnt <= 16'b0;
//发送数据模块
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
uart_txd <= 1'b1;
else
if(uart_tx_busy)
case(tx_cnt)
4'd0 : uart_txd <= 1'b0; //起始位
4'd1 : uart_txd <= tx_data_t[0]; //数据1
4'd2 : uart_txd <= tx_data_t[1]; //数据2
4'd3 : uart_txd <= tx_data_t[2]; //数据3
4'd4 : uart_txd <= tx_data_t[3]; //数据4
4'd5 : uart_txd <= tx_data_t[4]; //数据5
4'd6 : uart_txd <= tx_data_t[5]; //数据6
4'd7 : uart_txd <= tx_data_t[6]; //数据7
4'd8 : uart_txd <= tx_data_t[7]; //数据8
4'd9 : uart_txd <= 1'b1; //结束位
default: uart_txd <= 1'b1;
endcase
else
uart_txd <= 1'b1;
endmodule