整理总结下目前遇到的常见CDC问题及处理方法(如有不对,请多多指教)
目录
1、单bit信号
慢时钟域到快时钟域:打两拍,消除亚稳态即可
// 单bit的慢时钟域-->快时钟域
module single_bit_slow2fast(
input clk_fast,
input clk_slow,
input rst_n,
input data_in, //待同步信号
output dataout //同步后的信号
);
//1、在慢时钟采样
reg data_in_reg;
always@(posedge clk_slow or negedge rst_n) begin
if(!rst_n)
data_in_reg <= 1'b0;
else
data_in_reg <= data_in;
end
//2、在快时钟打2拍,消除亚稳态
reg [1:0] data_out_reg;
always@(posedge clk_fast or negedge rst_n) begin
if(!rst_n)
data_out_reg <= 2'b00;
else
data_out_reg <= {data_out_reg[0],data_in_reg};
end
//3、输出同步信号
assign dataout = data_out_reg[1];
endmodule
快时钟到慢时钟域:
(1)展宽+打拍同步
//方法一:信号展宽+打拍同步
module sync_fast2slow(
input clka , //快时钟
input read , //单脉冲(控制)信号,原本在快时钟,需要同步到慢时钟的
input clkb , //慢时钟
input rst_n ,
output read_sync_pulse //输出已同步的信号
);
//1、在clka快时钟域下,对单脉冲信号打两拍,取或,得到展宽信号read_or
reg [1:0] read_delay;
reg read_or;
always@(posedge clka or negedge rst_n) begin
if(!rst_n) begin
read_delay <= 2'b00;
end
else begin
read_delay <= {read_delay[0],read};
end
end
always@(posedge clka or negedge rst_n) begin
if(!rst_n)
read_or <= 1'b0;
else
read_or <= read | read_delay[0] | read_delay[1];
end
//2、在慢时钟域clkb下,对展宽信号采样+打两拍
reg read_sync;
always@(posedge clkb or negedge rst_n) begin
if(!rst_n)
read_sync <= 1'b0;
else
read_sync <= read_or;
end
reg [1:0] read_sync_delay;
always@(posedge clkb or negedge rst_n) begin
if(!rst_n)
read_sync_delay <= 2'b0;
else
read_sync_delay <= {read_sync_delay[0],read_sync};
end
//3、根据已打2拍(已同步)的信号对输出赋值
assign read_sync_pulse = read_sync_delay[0] & ~read_sync_delay[1];
endmodule
(2)握手协议handshake
//方法二:握手协议
module shake_sync(
input clk1,
input clk2,
input rst_n,
input read,
output read_sync_pulse
);
reg reg_in1;
reg ack_in1;
reg ack_in1_delay1;
//1、clk1时钟域下的reg_in1信号生成(展宽)
always@(posedge clk1 or negedge rst_n) begin
if(!rst_n)
reg_in1 <= 1'b0;
else if(read) //遇到read信号一直拉高
reg_in1 <= 1'b1;
else if(ack_in1_delay1) //直到遇到ack_in1_delay1拉低
reg_in1 <= 1'b0;
else
reg_in1 <= 1'b0;
end
reg reg_in2;
reg reg_in2_delay1;
//2、clk2时钟域下的reg信号采样+打1拍
always@(posedge clk2 or negedge rst_n)begin
if(!rst_n) begin
reg_in2 <= 1'b0;
reg_in2_delay1 <= 1'b0;
end
else begin
reg_in2 <= reg_in1;
reg_in2_delay1 <= reg_in2;
end
end
//3、clk1时钟下直接采样reg_in2_delay1,得到ack+并打1拍
always@(posedge clk1 or negedge rst_n) begin
if(!rst_n) begin
ack_in1 <= 1'b0;
ack_in1_delay1 <= 1'b0;
end
else begin
ack_in1 <= reg_in2_delay1;
ack_in1_delay1 <= ack_in1;
end
end
//4、dout信号产生
assign read_sync_pulse = reg_in2 & ~reg_in2_delay1;
endmodule
(3)脉冲同步器
// 一、快时钟域-->慢时钟域
// 从A时钟域提取一个单时钟周期宽度脉冲(2个),然后在新的时钟域B建立另一个单时钟宽度的脉冲(2个)。
// A时钟域的频率是B时钟域的10倍;A时钟域脉冲之间的间隔很大,无需考虑脉冲间隔太小的问题。
module pulse_detect(
input clk_fast ,
input clk_slow ,
input rst_n ,
input data_in ,
output dataout
);
//1.在快时钟域,根据一个周期的data_in脉冲(2次),转换成电平信号data_in_reg
reg data_in_reg;
always@(posedge clk_fast or negedge rst_n) begin
if(!rst_n)
data_in_reg <= 1'b0;
else
data_in_reg <= data_in?(~data_in_reg):data_in_reg;
end
//2.在慢时钟域,对data_in_reg打2拍,消除亚稳态。再打1拍用于边沿检测,所以总共打3拍。
reg [2:0] data_slow;
always@(posedge clk_slow or negedge rst_n) begin
if(!rst_n)
data_slow <= 3'b0;
else
data_slow <= {data_slow[1:0],data_in_reg};
end
//3.对输出赋值
assign dataout = data_slow[2] ^ data_slow[1];
//assign dataout = data_slow3 ^ data_slow2;
endmodule
2、多bit信号
慢到快时钟域:D_MUX
D_MUX(针对多bits信号+有效信号)。在快时钟域值得注意的是,对有效信号采用两级D触发器缓存,然后作为二选一数据选择器的选择信号
// 1、慢到快时钟。两级D触发器 对有效信号打2拍
//当data_en为高至少3个clk_b时,才将data_in数据输出。
//思路:(1)当首个clk_a到来时,将data_en和data_in信号缓存。
// (2)对data_en_reg在clk_b下额外缓存2个clk_b,得到b时钟域下的data_en_b。
// (3) 在clk_b到来时,根据data_en_b对输出信号赋值。
module mux_clka2b(
input clk_a ,
input clk_b ,
input arstn ,
input brstn ,
input [3:0] data_in , //clk_a
input data_en , //clk_a
output reg [3:0] dataout //clk_b
);
//clk_a到来时对data_en进行缓存
reg data_en_reg;
always@(posedge clk_a or negedge arstn) begin
if(!arstn)
data_en_reg <= 1'b0;
else
data_en_reg <= data_en;
end
//clk_a到来时对data_in进行缓存
reg [3:0] data_in_reg;
always@(posedge clk_a or negedge arstn) begin
if(!arstn)
data_in_reg <= 4'b0;
else
data_in_reg <= data_in;
end
//对data_en_reg在B时钟域缓存2个周期,使得data_en总持续时间大于3个clk_b
reg [1:0] data_en_b_t;
always@(posedge clk_b or negedge brstn) begin
if(!brstn)
data_en_b_t <= 2'b00;
else
data_en_b_t <= {data_en_b_t[0],data_en_reg};
end
//在data_en满足条件时,对输出dataout赋值
always@(posedge clk_b or negedge brstn) begin
if(!brstn)
dataout <= 4'b0;
else
dataout <= (data_en_b_t[1])?data_in_reg:dataout;
end
endmodule
快到慢时钟域:
(1)D_MUX
(这里与慢到快不同的是,在慢时钟域对有效信号采用脉冲同步+边沿检测,然后再作为选择信号输入)
//2、快到慢时钟域。
//对有效信号而言,将两级D触发器换成脉冲同步,将脉冲信号展宽,再进行边沿检测
module mux_clkb2a(
input clk_a , //慢
input clk_b , //快
input arstn ,
input brstn ,
input [3:0] data_in ,
input data_en ,
output reg [3:0] dataout
);
//快时钟域
reg data_en_reg;
always@(posedge clk_b or negedge brstn) begin
if(!brstn)
data_en_reg <= 1'b0;
else
data_en_reg <= data_en;
end
reg [3:0] data_in_reg;
always@(posedge clk_b or negedge brstn) begin
if(!brstn)
data_in_reg <= 4'b0;
else
data_in_reg <= data_in;
end
//慢时钟域,展宽
reg [2:0] data_en_b_t;
always@(posedge clk_a or negedge arstn) begin
if(!arstn)
data_en_b_t <= 3'b000;
else
data_en_b_t <= {data_en_b_t[1:0],data_en_reg};
end
//边沿检测
wire data_en_b;
assign data_en_b = data_en_b_t[1] ^ data_en_b_t[2];
always@(posedge clk_a or negedge arstn) begin
if(!arstn)
dataout <= 'd0;
else
dataout <= (data_en_b)?data_in_reg:dataout;
end
endmodule
(2)异步FIFO(基于双口RAM+格雷码实现)
//一、异步fifo模块
//保持同步FIFO类似的读写控制逻辑,区别是增加了转换格雷码,以及格雷码的跨时钟域
//空满信号的产生是根据格雷码判断的
module asyn_fifo#(
parameter WIDTH =8,
parameter DEPTH =16
)(
input wclk,
input rclk,
input wrstn,
input rrstn,
input winc,
input rinc,
input [WIDTH-1:0] wdata,
output wfull,
output rempty,
output [WIDTH-1:0] rdata
);
localparam ADDR_WIDTH = $clog2(DEPTH);
//1.写地址产生逻辑(地址信号为FIFO内部产生的信号,输入到RAM) waddr
reg [ADDR_WIDTH:0] waddr;
reg [ADDR_WIDTH:0] raddr;
always@(posedge wclk or negedge wrstn) begin
if(!wrstn)
waddr <= 'd0;
else
waddr <= (winc&&~wfull)?waddr+1'b1:waddr;
end
//2.读地址产生逻辑 raddr
always@(posedge rclk or negedge rrstn) begin
if(!rrstn)
raddr <= 'd0;
else
raddr <= (rinc&&~rempty)?raddr+1'b1:raddr;
end
//3.读写地址-->读写地址的格雷码
//3.1 读写地址的格雷码生成
wire [ADDR_WIDTH:0] waddr_grey;
wire [ADDR_WIDTH:0] raddr_grey;
assign waddr_grey = waddr ^ (waddr>>1);
assign raddr_grey = raddr ^ (raddr>>1);
//打1拍,避免格雷码亚稳态
reg [ADDR_WIDTH:0] waddr_grey_reg;
always@(posedge wclk or negedge wrstn) begin
if(!wrstn)
waddr_grey_reg <= 'd0;
else
waddr_grey_reg <= waddr_grey;
end
reg [ADDR_WIDTH:0] raddr_grey_reg;
always@(posedge rclk or negedge rrstn) begin
if(!rrstn)
raddr_grey_reg <= 'd0;
else
raddr_grey_reg <= raddr_grey;
end
//3.2 写指针同步到读时钟域(打2拍实现,看读的rclk\rrst)
reg [ADDR_WIDTH:0] addr_w2r_t;
reg [ADDR_WIDTH:0] addr_w2r;
always@(posedge rclk or negedge rrstn) begin
if(!rrstn) begin
if(!rrstn) begin
addr_w2r_t <= 'd0;
addr_w2r <= 'd0;
end
end
else begin
addr_w2r_t <= waddr_grey_reg;
addr_w2r <= addr_w2r_t;
end
end
//3.3 读时针同步到写时钟域(打2拍实现,看写的wclk wrst)
reg [ADDR_WIDTH:0] addr_r2w_t;
reg [ADDR_WIDTH:0] addr_r2w;
always@(posedge wclk or negedge wrstn) begin
if(!wrstn) begin
addr_r2w_t <= 'd0;
addr_r2w <= 'd0;
end
else begin
addr_r2w_t <= raddr_grey_reg;
addr_r2w <= addr_r2w_t;
end
end
//4. 写满、读空信号的产生逻辑
// wfull:写地址的格雷码 == 读地址(已同步到写时钟域)的最高位和次高位相反 + 其余位相同
// rempty:读地址的格雷码 == 写地址(已从写时钟域同步到读时钟域)
assign wfull = (waddr_grey_reg == {~addr_r2w[ADDR_WIDTH:ADDR_WIDTH-1],addr_r2w[ADDR_WIDTH-2:0]});
assign rempty = (raddr_grey_reg == addr_w2r);
//5. 例化RAM(内部连接RAM)
// 需要注意读写使能、读写地址信号连接的位宽
dual_port_RAM #(
.WIDTH(WIDTH),
.DEPTH(DEPTH)
)dual_port_RAM_inst(
.wclk(wclk),
.wenc(winc&&~wfull),
.waddr(waddr[ADDR_WIDTH-1:0]),
.wdata(wdata),
.rclk(rclk),
.renc(rinc&&~rempty),
.raddr(raddr[ADDR_WIDTH-1:0]),
.rdata(rdata)
);
endmodule
// 二、双口RAM模块
module dual_port_RAM #(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk,
input wenc,
input [$clog2(DEPTH)-1:0] waddr,
input [WIDTH-1:0] wdata,
input rclk,
input renc,
input [$clog2(DEPTH)-1:0] raddr,
output reg [WIDTH-1:0] rdata
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always@(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always@(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
(3)将多bit信号转换为单bit信号
(4)握手
好像跟单bit握手类似