本文使用 Zhihu On VSCode 创作并发布
跨时钟域同步(异步FIFO)
之前学习了跨时钟域下的单bit信号同步的方法,这些单bit信号多是作为控制信号或者标志信号来使用,再实际的项目中,处理多bit数据也是十分常见的,即数据的同步。
异步FIFO的实现其实本质上和双口RAM是一样的,其实现思路就是将数据在src_clk的时钟下写入自己设定大小的ram中,然后通过读时钟des_clk从ram中将数据读出来即可。
本次还是以一个具体的例子来说明其实现,现在我们从一个较低的时钟域进入到一个较高的时钟域中,低时钟域的数据信号为8bit数据,现在需要将其缓存,并转换成32bit的信号送入32位宽的总线上进行传输。具体实现代码如下:
module nsync_fifo(input src_clk,input rst_n,input des_clk,input [8-1:0] fifo_data_in,input fifo_data_in_vaild,output reg fifo_data_out_vaild,output [32-1:0] fifo_data_out);// write fifo
reg [1:0] buffer_wr_addr;
reg [32-1:0] temp_buffer;
always @(posedge src_clk or negedge rst_n)
beginif (!rst_n)beginbuffer_wr_addr <= 2'b00;endelsebeginbuffer_wr_addr <= (fifo_data_in_vaild) ? buffer_wr_addr + 1'b1 : buffer_wr_addr;end
endalways @(posedge src_clk)
beginif (fifo_data_in_vaild)begincase(buffer_wr_addr)2'd0:temp_buffer[0+:8] <= fifo_data_in;2'd1:temp_buffer[8+:8] <= fifo_data_in;2'd2:temp_buffer[16+:8] <= fifo_data_in;2'd3:temp_buffer[24+:8] <= fifo_data_in;endcaseend
end//gen wr_fifo signal && sync
reg fifo_wr;
reg fifo_wr_sync;
wire fifo_wr_clr;
reg fifo_wr_dfb;
always @(posedge src_clk)
beginfifo_wr <= (&buffer_wr_addr & fifo_data_in_vaild);
endassign fifo_wr_clr = !rst_n | fifo_wr_sync; // feed backalways @(posedge fifo_wr or posedge fifo_wr_clr)
beginif (fifo_wr_clr)beginfifo_wr_dfb <= 1'b0;endelse beginfifo_wr_dfb <= 1'b1;end
endalways @(posedge des_clk) // under des_clk sampled
beginfifo_wr_sync <= fifo_wr_dfb;
end// recv fifo
wire recv_fifo_wr;
reg [1:0] recv_fifo_wr_addr;assign recv_fifo_wr = fifo_wr_sync;
always @(posedge des_clk or rst_n)
beginif (!rst_n)beginrecv_fifo_wr_addr <= 2'b00;endelsebeginrecv_fifo_wr_addr <= (recv_fifo_wr) ? recv_fifo_wr_addr + 1'b1 : recv_fifo_wr_addr;end
end// sync fifo data
reg [32-1:0] fifo_0;
reg [32-1:0] fifo_1;
always @(posedge des_clk)
beginif (recv_fifo_wr) // only buffer_wr_addr == 3 genbegincase (recv_fifo_wr_addr[0])1'b0 : fifo_0 <= temp_buffer;1'b1 : fifo_1 <= temp_buffer; endcaseend
end// read data from recv fifo
wire [1:0] recv_fifo_cnt;
wire recv_fifo_full;
wire recv_fifo_ready;
reg [1:0] recv_fifo_rd_addr;
assign recv_fifo_ready = | recv_fifo_cnt;
assign recv_fifo_full = recv_fifo_cnt[1]; // 2'b11 = 3
assign recv_fifo_cnt = recv_fifo_wr_addr - recv_fifo_rd_addr;always @(posedge des_clk or negedge rst_n)
beginif (!rst_n)beginrecv_fifo_rd_addr <= 2'b00;endelsebeginrecv_fifo_rd_addr <= (recv_fifo_ready) ? recv_fifo_rd_addr + 1'b1 : recv_fifo_rd_addr;end
endreg [32-1:0] recv_fifo_data;
always @(*)
begincase(recv_fifo_rd_addr[0])1'b0 : recv_fifo_data = fifo_0;1'b1 : recv_fifo_data = fifo_1;endcase
end// out
assign fifo_data_out = recv_fifo_data;
always @(posedge des_clk or negedge rst_n)
beginif(!rst_n)beginfifo_data_out_vaild <= 1'b0;endelsebeginfifo_data_out_vaild <= recv_fifo_ready;end
endendmodule
从上面的代码可以发现,我这里将src_clk中的写信号fifo_wr通过上章讲解的单bit反馈同步方法同步到了des_clk的时钟域下fifo_wr_sync;这样后续的信号处理造作就可以在我们的目标时钟des_clk下进行操作了。因为这里需要缓存的数据很少,地址线就显得比较简单了,通过一个简单的乒乓操作不断将数据从buffer中写入fifo_0和fifo_1中;只要fifo不空,就开始读数据。时序如下: