> 技术文档 > 跨时钟域处理(含同步FIFO,异步FIFO等verilog代码)_异步fifo跨时钟域处理

跨时钟域处理(含同步FIFO,异步FIFO等verilog代码)_异步fifo跨时钟域处理


亚稳态

同步设计:整个设计都是使用同一个时钟源,所有时钟的频率和相位都是已知的;

异步设计:时钟之间同频不同相或者频率不同。

建立时间:在时钟的有效沿(以上升沿为例)到来之前,数据的输入端信号必须保持稳定的最短时间。

保持时间:在时钟的有效沿(以上升沿为例)到来之后,数据的输入端信号必须保持稳定的最短时间。

亚稳态产生的原因:

数据不满足触发器的建立时间和保持时间;在规定的时间内无法达到一个可以确定的状态。

单比特数据跨时钟域传输

慢到快

单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态。

第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为 70%~80%左右,第二级寄存

器可以稳定输出的概率为 99%左右,后面再多加寄存器的级数改善效果就不明显了,所以

数据进来后一般选择打两拍即可。

always@(posedge clk)begin rx_reg1 <= rx; rx_reg2 <= rx_reg1;end

快到慢

慢时钟域相比快时钟域采样速度更慢,也就是说从快时钟域来到慢时钟域的信号极有可能被漏采。

对于电平信号而言(一般电平信号持续时间足够长),信号长度可以得到保证,所以正常采用两级同步器采样即可。

对于脉冲信号而言(一般脉冲信号持续时间很短),长度难以得到保证,需要对信号进行延长。

快时钟域下对脉冲信号进行检测,检测为高电平时输出高电平信号req;

慢时钟域对快时钟域的信号req进行延迟打拍采样。此时的脉冲信号被快时钟域保持拉高状态,延迟打拍肯定会采集到该信号。

慢时钟域确认采样得到高电平信号req_r2后,拉高反馈信号ack,再反馈给快时钟域。

快时钟域对反馈信号ack进行延迟采样得到ack_r0。如果检测到反馈信号为高电平,证明慢时钟域已经接收到有效的高电平信号,信号恢复到原来状态。

module cdc_sbit_handshake ( input fast_clk, input arst_n, input signal_a, input slow_clk, input brst_n, output signal_b); reg req; reg ack_r0; always @(posedge fast_clk or negedge arst_n) begin if(~arst_n) req <= 1\'b0; else if(signal_a) req <= 1\'b1; else if(ack_r0) req <= 1\'b0; end reg req_r0; reg req_r1; reg req_r2; always@(posedge slow_clk or negedge brst_n)begin if(~brst_n)begin req_r0 <= 1\'b0; req_r1 <= 1\'b0; req_r2 <= 1\'b0; end else begin req_r0 <= req; req_r1 <= req_r0; req_r2 <= req_r1; end end reg ack; always @(posedge fast_clk or negedge arst_n) begin if(~arst_n)begin ack <= 1\'b0; ack_r0 <= 1\'b0; end else begin ack <= req_r1; ack_r0 <= ack; end end assign signal_b = ~req_r2 & req_r1;endmodule

多比特数据跨时钟域传输

对于多比特数据,在进行传输时候会因为时序问题导致所有寄存器不会同时翻转

DMUX跨时钟域传输

通过一个使能信号来判断data信号是否已经稳定,当使能信号有效的时候说明data处于稳定状态,在这种情况下终点寄存器才对信号进行采样,可以保证没有setup/hold违例,相当于把多比特信号的CDC问题转换成了单比特信号的CDC问题。

不需要考虑地址线递增递减问题,并且支持处理跳变的多bit数据;

数据和使能信号在源时钟域为同步到来的数据,DMUX原则是数据不同步只对使能信号同步;

源时钟域下的数据需要在目的时钟域下保持好几个时钟周期,在目的时钟域对数据完成采样前数信号需要保持不变。

module cdc_dmux #( parameter data_width = 8;) ( input aclk, input rst_n, input bclk, input [data_width-1:0] din, input  din_en, output [data_width-1:0] dout, output  dout_valid); reg [data_width-1:0] din_r, reg  din_en_r, //在时钟域a下对输入数据和使能信号打拍 always @(posedge aclk or negedge rst_n) begin if(~rst_n)begin din_r <= \'d0; din_en_r <= \'d0; end else begin din_r <= din; din_en_r <= din_en; end end //将使能信号同步到b时钟域下 reg din_en_d0,din_en_d1; always @(posedge bclk or negedge rst_n ) begin if(~rst_n)begin din_en_d0 <= \'d0; din_en_d1 <= \'d0; end else begin din_en_d0 <= din_en_r; din_en_d1 <= din_en_d0; end end //在b时钟域下当使能信号有效时赋值 reg [data_width-1:0] dout_r; always @(posedge bclk or negedge rst_n) begin if(~rst_n) dout_r <= \'d0; else if(din_en_d1) dout_r <= din_r; else dout_r <= dout_r; end reg dout_valid_r; always @(posedge bclk or negedge rst_n) begin if(~rst_n) dout_valid_r <= 1\'b0; else if(din_en_d1) dout_valid_r <= 1\'b1; else dout_valid_r <= 1\'b0; end assign dout = dout_r; assign dout_valid = dout_valid_r;endmodule

使用上升沿作为条件

//同步模块工作时钟为 100MHz 的模块//异步数据对来自工作时钟为 20MHz 的模块module delay_sample( input  rstn, input  clk1, input [31:0] din, input  din_en, input  clk2, output [31:0] dout, output  dout_en); //sync din_en reg [2:0] din_en_r ; always @(posedge clk2 or negedge rstn) begin if (!rstn) din_en_r <= 3\'b0 ; else din_en_r <= {din_en_r[1:0], din_en} ; end wire din_en_pos = din_en_r[1] && !din_en_r[2] ; //sync data reg [31:0]  dout_r ; reg  dout_en_r ; always @(posedge clk2 or negedge rstn) begin if (!rstn) dout_r <= \'b0 ; else if (din_en_pos) dout_r <= din ; end //dout_en delay always @(posedge clk2 or negedge rstn) begin if (!rstn) dout_en_r <= 1\'b0 ; else  dout_en_r <= din_en_pos ; end assign dout = dout_r ; assign dout_en = dout_en_r ;endmodule

握手协议

module cdc_mbit_handshake ( input clk_a, input clk_b, input rst_n, input a_en, input [3:0] data_in, output b_en, output [3:0] data_out); reg a_en_d1; wire a_en_neg; //在a时钟域下对使能信号进行下降沿�?�? always @(posedge clk_a or negedge rst_n) begin if(~rst_n)begin a_en_d1 <= \'d0; end else begin a_en_d1 <= a_en; end end assign a_en_neg = (a_en) && (~a_en_d1); reg req_a; reg ack_a_r; reg ack_a_rr; always @(posedge clk_a or negedge rst_n) begin if(~rst_n) req_a <= 1\'b0; else if(a_en_neg) req_a <= 1\'b1; else if(ack_a_rr) req_a <= 1\'b0; end reg req_b_r; reg req_b_rr; always @(posedge clk_b or negedge rst_n) begin if(~rst_n)begin req_b_r <= \'d0; req_b_rr <= \'d0; end else begin req_b_r <= req_a; req_b_rr <= req_b_r; end end assign b_en = req_b_r && (!req_b_rr); assign data_out = b_en ? data_in : data_out; always @(posedge clk_a or negedge rst_n) begin if(~rst_n)begin ack_a_r <= \'d0; ack_a_rr <= \'d0; end else begin ack_a_r <= req_b_rr; ack_a_rr <= ack_a_r; end endendmodule

同步fifo

FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。 FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。

module asyn_ram #( parameter DATA_WIDTH = 8, parameter ADDR_WIDTH = 4;) ( input csen, input write_clock, input write_en, input [ADDR_WIDTH-1:0] write_addr, input [DATA_WIDTH-1:0] write_data, input read_clock, input read_en, input [ADDR_WIDTH-1:0] read_addr, output reg [DATA_WIDTH-1:0] read_data);reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH-1:0];always @(posedge write_clock) begin if((write_en) &&(csen)) mem[write_addr] <= write_data;endalways @(posedge read_clock) begin if((read_en) && (csen)) read_data <= mem[read_addr];end endmodule

同步FIFO有三部分组成: (1) FIFO写控制逻辑; (2)FIFO读控制逻辑; (3)FIFO 存储实体(如Memory、Reg)。

FIFO写控制逻辑主要功能:产生FIFO写地址、写有效信号,同时产生FIFO写满、写错等状态信号;

FIFO读控制逻辑主要功能:产生FIFO读地址、读有效信号,同时产生FIFO读空、读错等状态信号。

FIFO设计的关键是正确的读写控制和生成FIFO“空/满”状态标志。基本原则是**“满不能写,空不能读。

FIFO读写控制

当FIFO初始化(复位)时fifo_write_addr与fifo_read_addr同指到0x0,此时FIFO为空状态;

当FIFO进行写操作时,fifo_write_addr递增(增加到FIFO深度时回绕),与fifo_read_addr错开,此时FIFO处于非空状态;

当FIFO进行读操作时,fifo_read_addr递增,当读到最后一个数据后,就追赶上fifo_write_addr,此时FIFO也是空状态。

判断空满状态的实现方法

计数器实现同步FIFO

为产生FIFO空满标志,引入fifo_data_cnt计数器,fifo_data_cnt计数器用于指示FIFO内部存储数据个数;

复位时,该计数器为0;

当读写使能信号均无效或者有效时,说明同时读写或者同时不读不写,fifo_data_cnt不变;

当写使能有效且fifo_full=0(fifo未满),则计数器+1;

当读使能有效且fifo_empty=0(fifo为空),则计数器-1;

计数器=0表示fifo为空,需要设置fifo_empty=1;

计数器=fifo深度时,fifo为满,需要设置fifo_full=1;

module sync_fifo_cnt#( parameter fifo_data_width = \'d8, parameter fifo_addr_depth = \'d16)( input clk, input rst_n, input fifo_wr_en, input fifo_rd_en, input [fifo_data_width-1:0] fifo_wr_data, output fifo_full, output fifo_wr_err, output fifo_empty, output fifo_rd_err, output reg [$clog2(fifo_addr_depth)-1:0] fifo_data_cnt, output reg [fifo_data_width-1:0] fifo_rd_data);reg [$clog2(fifo_addr_depth)-1:0] fifo_wr_addr;reg [$clog2(fifo_addr_depth)-1:0] fifo_rd_addr;reg [fifo_data_width-1:0] fifo_mem [fifo_addr_depth-1:0];always @(posedge clk or negedge rst_n) begin if(~rst_n) fifo_rd_addr <= 1\'b0; else if(fifo_rd_en && (~fifo_empty))begin fifo_rd_addr <= fifo_rd_addr + 1\'b1; fifo_rd_data <= fifo_mem[fifo_rd_addr];endendalways @(posedge clk or negedge rst_n) begin if(~rst_n) fifo_wr_addr <= 1\'b0; else if(fifo_wr_en && (~fifo_full))begin fifo_wr_addr <= fifo_wr_addr + 1\'b1; fifo_mem[fifo_wr_addr] <= fifo_wr_data; endendalways @(posedge clk or negedge rst_n) begin if(~rst_n) fifo_data_cnt <= 1\'b0; else if(fifo_wr_en & (~fifo_full)&(~(fifo_rd_en & (~fifo_empty)))) fifo_data_cnt <= fifo_data_cnt + 1\'b1; else if(~(fifo_wr_en & (~fifo_full))&(fifo_rd_en & (~fifo_empty))) fifo_data_cnt <= fifo_data_cnt - 1\'b1; else fifo_data_cnt <= fifo_data_cnt;endassign fifo_empty = (fifo_data_cnt == 0);assign fifo_rd_err = (fifo_data_cnt == 0) & fifo_rd_en;assign fifo_full = (fifo_data_cnt == fifo_addr_depth);assign fifo_wr_err = (fifo_data_cnt == fifo_addr_depth) & (fifo_wr_en);endmodule

高位拓展法实现同步FIFO

当读写地址相同时,判断fifo为空

当读写地址最高位相反,其他位相同时,则可判断fifo为满

如图,当ram深度为8,拓展后地址为4位。

开始写入4个数据,此时写地址为0100,读地址为0000;

然后开始读数据,读完四个数据后,读地址为0100,此时判断fifo为空;

后对FIFO进行写数据,连续写入8个数据后,此时写地址为1100,深度为8,判断fifo为满:读写地址最高位相反,其余位相同。

module sync_fifo_ptr#( parameter fifo_data_width = \'d8, parameter fifo_addr_depth = \'d16)( input clk, input rst_n, input fifo_wr_en, input fifo_rd_en, input [fifo_data_width-1:0] fifo_wr_data, output fifo_full, output fifo_wr_err, output fifo_empty, output fifo_rd_err, output reg [fifo_data_width-1:0] fifo_rd_data);wire [$clog2(fifo_addr_depth)-1:0] fifo_wr_addr;wire [$clog2(fifo_addr_depth)-1:0] fifo_rd_addr;reg [$clog2(fifo_addr_depth):0] fifo_wr_addr_e;reg [$clog2(fifo_addr_depth):0] fifo_rd_addr_e;reg [fifo_data_width-1:0] fifo_mem [fifo_addr_depth-1:0];assign fifo_wr_addr = fifo_wr_addr_e[$clog2(fifo_addr_depth)-1:0];assign fifo_rd_addr = fifo_rd_addr_e[$clog2(fifo_addr_depth)-1:0];always @(posedge clk or negedge rst_n) begin if(~rst_n) fifo_rd_addr_e <= 1\'b0; else if(fifo_rd_en && (~fifo_empty))begin fifo_rd_addr_e <= fifo_rd_addr_e + 1\'b1; fifo_rd_data <= fifo_mem[fifo_rd_addr];endendalways @(posedge clk or negedge rst_n) begin if(~rst_n) fifo_wr_addr_e <= 1\'b0; else if(fifo_wr_en && (~fifo_full))begin fifo_wr_addr_e <= fifo_wr_addr_e + 1\'b1; fifo_mem[fifo_wr_addr] <= fifo_wr_data; endendassign fifo_empty = (fifo_wr_addr_e == fifo_rd_addr_e);assign fifo_rd_err = fifo_empty & fifo_rd_en;assign fifo_full = (fifo_wr_addr == fifo_rd_addr)&(fifo_rd_addr_e[$clog2(fifo_addr_depth)] != fifo_wr_addr_e[$clog2(fifo_addr_depth)]);assign fifo_wr_err = fifo_full & (fifo_wr_en);endmodule

异步FIFO

二进制转格雷码

二进制码->格雷码(编码):从最右边一位起,依次将每一位与左边一位异或(XOR),作为本位的格雷码值,最高位不变(相当于最高位的左边是 0);

module bin2gray( bin_in, gray_out); parameter WIDTH = 4; input [WIDTH-1:0] bin_in; output [WIDTH-1:0] gray_out; //=================================================== // ------------------- MAIN CODE ------------------- //=================================================== assign gray_out = (bin_in >> 1) ^ bin_in; endmodule

格雷码转二进制码

格雷码->二进制码(解码):从左边第二位起,将每位与左边一位解码后的值异或,作为该位解码后的值(最左边一位依然不变)。

module gray2bin( gray_in, bin_out); parameter WIDTH = 4; input [WIDTH-1:0] gray_in; output reg [WIDTH-1:0] bin_out; //=================================================== // ------------------- MAIN CODE ------------------- //=================================================== integer i; always @(*) begin bin_out[WIDTH-1] = gray_in[WIDTH-1]; for (i = 1; i < WIDTH; i = i + 1) begin bin_out[WIDTH-i-1] = gray_in[WIDTH-i-1]^bin_out[WIDTH-i]; end end endmodule

为什么用格雷码

多比特信号在跨时钟域传输时发生错误的概率肯定比单比特大,所以才会采用格雷码,保证每次只有一位信号变化。

例如:四位二进制码从0111变为1000的过程中,这两个数虽然在数值上相邻,但它们的每个比特都将发生改变,这时如果它们之间有skew,采样的值就可能是任意的四位二进制数,这会给空满标志的判断带来问题,如果错误触发空满标志还好,但如果在空满成立时没有触发,就会导致数据被覆盖掉或者重复读出。

如果使用格雷码,每次只改变一位信号,就不会出现上述的问题。例如,格雷码从0001递增到0011时,即便没有采集到变化后的0011,也会采集到变化之前的0001,这只会导致“不该报空满而报了空满”,但并不会导致“该报空满而未报”的情况。详细来说,如果是读指针从0001递增到0011,假设写时钟域采到的是0001,那么也只是会报写满(因为写时钟域不知道读时钟域已经读到下一个地址了),从而停止写入,这是安全的;同理,如果是写指针从0001递增到0011,假设读时钟域采到的是0001,那么也只是会报读空(因为读时钟域不知道写时钟域已经写到下一个地址了),从而停止读出,这也是安全的。

module async_fifo #( parameter DATA_WIDTH = \'d8, parameter DATA_DEPTH = \'d16) ( input wr_clk, input wr_rst_n, input wr_en, input [DATA_WIDTH - 1:0] data_in, input rd_clk, input rd_rst_n, input rd_en, output reg [DATA_WIDTH-1:0] data_out, output empty, output full); reg [DATA_WIDTH-1:0] fifo_buff [DATA_DEPTH - 1:0]; reg [$clog2(DATA_DEPTH):0] wr_ptr; reg [$clog2(DATA_DEPTH):0] rd_ptr; reg [$clog2(DATA_DEPTH):0] rd_ptr_g_d1; reg [$clog2(DATA_DEPTH):0] rd_ptr_g_d2; reg [$clog2(DATA_DEPTH):0] wr_ptr_g_d1; reg [$clog2(DATA_DEPTH):0] wr_ptr_g_d2; wire [$clog2(DATA_DEPTH):0] rd_ptr_g; wire [$clog2(DATA_DEPTH):0] wr_ptr_g; wire [$clog2(DATA_DEPTH)-1:0] rd_ptr_true; wire [$clog2(DATA_DEPTH)-1:0] wr_ptr_true; assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1); assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1); assign wr_ptr_true = wr_ptr[$clog2(DATA_DEPTH)-1:0]; assign rd_ptr_true = rd_ptr[$clog2(DATA_DEPTH)-1:0]; always @(posedge wr_clk or negedge wr_rst_n) begin if(~wr_rst_n) wr_ptr <= 0; else if(!full && wr_en)begin wr_ptr <= wr_ptr + 1\'d1; fifo_buff[wr_ptr_true] <= data_in; end end always @(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n)begin rd_ptr_g_d1 <= 0; rd_ptr_g_d2 <= 0; end else begin rd_ptr_g_d1 <= rd_ptr_g; rd_ptr_g_d2 <= rd_ptr_g_d1; end end always@(posedge rd_clk or negedge rd_rst_n) begin if(~rd_rst_n) rd_ptr <= \'d0; else if(rd_en && !empty) data_out <= fifo_buff[rd_ptr_true]; rd_ptr <= rd_ptr + 1\'d1; end always @(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n)begin wr_ptr_g_d1 <= 0; wr_ptr_g_d2 <= 0; end else begin wr_ptr_g_d1 <= wr_ptr_g; wr_ptr_g_d2 <= wr_ptr_g_d1; end end assign empty = (wr_ptr_g_d2 == rd_ptr_g) ? 1\'b1 : 1\'b0; assign full = (wr_ptr== {~(rd_ptr_g_d2[$clog2(DATA_DEPTH):$clog2(DATA_DEPTH)-1]),rd_ptr_g_d2[$clog2(DATA_DEPTH)-2:0]})?1\'b1:1\'b0;endmodule

参考内容:

跨时钟域传输总结(包含verilog代码|Testbench|仿真结果)

FIFO设计笔记(双口RAM、同步FIFO、异步FIFO)Verilog及仿真

【总结篇】深入理解亚稳态及跨时钟域处理方法

【数字IC基础】跨时钟域(CDC,Clock Domain Crossing)