Bootstrap

单/多bit数据信号的跨时钟域传输 学习记录

整理总结下目前遇到的常见CDC问题及处理方法(如有不对,请多多指教)

目录

1、单bit信号

慢时钟域到快时钟域:打两拍,消除亚稳态即可

快时钟到慢时钟域:

(1)展宽+打拍同步   

(2)握手协议handshake

(3)脉冲同步器

2、多bit信号

慢到快时钟域:D_MUX

快到慢时钟域:

(1)D_MUX

(2)异步FIFO(基于双口RAM+格雷码实现)

(3)将多bit信号转换为单bit信号

(4)握手


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握手类似

;