异步FIFO的结构如下图所示:
具体的逻辑如下图所示:
空满标志逻辑如下图所示:
fifo_async.v代码如下:
`timescale 1ns / 1ps
module fifo_async
#(parameter ADDR_WIDTH=5,BIT_WIDTH=8) // DEPTH = 32,ADDR_WIDTH=ADDR_WIDTH
(
input iResetN
,input iWriteClk,iWriteEnable // write part
,input [BIT_WIDTH-1:0] iWriteData
,output oWriteFull
,input iReadClk,iReadEnable // read part
,output [BIT_WIDTH-1:0] oReadData
,output oReadEmpty
);
wire wClear;
reg [ADDR_WIDTH:0] rWritePointer='b0,rReadPointer='b0; // gray code
reg [ADDR_WIDTH:0] rWriteCnt='b0,rReadCnt='b0; // natual binary code
reg [ADDR_WIDTH:0] rW2rPointerFront='b0,rR2wPointerFront='b0;
reg [ADDR_WIDTH:0] rW2rPointerNext='b0,rR2wPointerNext='b0;
assign wClear = ~ iResetN;
// this dual ram content is 32(depth) * 8(bit width) ;
dual_ram dual_ram (
.rd_aclr ( wClear )
,.rdclock ( iReadClk )
,.rden ( iReadEnable )
,.rdaddress ( rReadPointer[ADDR_WIDTH-1:0] )
,.q ( oReadData )
,.wrclock ( iWriteClk )
,.wren ( iWriteEnable )
,.wraddress ( rWritePointer[ADDR_WIDTH-1:0] )
,.data ( iWriteData )
);
// output write addreass
always@(negedge iResetN or posedge iWriteClk) begin
if(!iResetN)begin
rWritePointer <= 'b0;
rWriteCnt <= 'b0;
end
else begin
if(iWriteEnable)begin
rWritePointer <= {1'b0 , rWriteCnt[ADDR_WIDTH:1]} ^ rWriteCnt; // convert natual binary code to gray code
rWriteCnt <= rWriteCnt + (1'b1 & ~oWriteFull);
end
end
end
// output read addreass
always@(negedge iResetN or posedge iReadClk) begin
if(!iResetN)begin
rReadPointer <= 'b0;
rReadCnt <= 'b0;
end
else begin
if(iReadEnable)begin
rReadPointer <= {1'b0 , rReadCnt[ADDR_WIDTH:1]} ^ rReadCnt; // convert natual binary code to gray code
rReadCnt <= rReadCnt + (1'b1 & ~oReadEmpty);
end
end
end
always @(negedge iResetN or posedge iWriteClk) begin
if (!iResetN) begin
rR2wPointerFront <= 'b0;
rR2wPointerNext <= 'b0;
end
else begin
rR2wPointerFront<= rReadPointer;
rR2wPointerNext <= rR2wPointerFront;
end
end
always @(negedge iResetN or posedge iReadClk) begin
if (!iResetN) begin
rW2rPointerFront <= 'b0;
rW2rPointerNext <= 'b0;
end
else begin
rW2rPointerFront <= rWritePointer;
rW2rPointerNext <= rW2rPointerFront;
end
end
assign oReadEmpty = (rW2rPointerNext == rReadPointer)?1'b1:1'b0;
assign oWriteFull = (rR2wPointerNext[ADDR_WIDTH]=={~rWritePointer[ADDR_WIDTH],rWritePointer[ADDR_WIDTH-1:0]})?1'b1:1'b0;
endmodule
dual_ram.v可由quartus生成,代码如下:
`timescale 1 ps / 1 ps
// synopsys translate_on
module dual_ram (
data,
rd_aclr,
rdaddress,
rdclock,
rden,
wraddress,
wrclock,
wren,
q);
input [7:0] data;
input rd_aclr;
input [4:0] rdaddress;
input rdclock;
input rden;
input [4:0] wraddress;
input wrclock;
input wren;
output [7:0] q;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_off
`endif
tri0 rd_aclr;
tri1 rden;
tri1 wrclock;
tri0 wren;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_on
`endif
wire [7:0] sub_wire0;
wire [7:0] q = sub_wire0[7:0];
altsyncram altsyncram_component (
.clock0 (wrclock),
.wren_a (wren),
.aclr1 (rd_aclr),
.address_b (rdaddress),
.clock1 (rdclock),
.address_a (wraddress),
.data_a (data),
.rden_b (rden),
.q_b (sub_wire0),
.aclr0 (1'b0),
.addressstall_a (1'b0),
.addressstall_b (1'b0),
.byteena_a (1'b1),
.byteena_b (1'b1),
.clocken0 (1'b1),
.clocken1 (1'b1),
.clocken2 (1'b1),
.clocken3 (1'b1),
.data_b ({8{1'b1}}),
.eccstatus (),
.q_a (),
.rden_a (1'b1),
.wren_b (1'b0));
defparam
altsyncram_component.address_reg_b = "CLOCK1",
altsyncram_component.clock_enable_input_a = "BYPASS",
altsyncram_component.clock_enable_input_b = "BYPASS",
altsyncram_component.clock_enable_output_a = "BYPASS",
altsyncram_component.clock_enable_output_b = "BYPASS",
altsyncram_component.intended_device_family = "Cyclone II",
altsyncram_component.lpm_type = "altsyncram",
altsyncram_component.numwords_a = 32,
altsyncram_component.numwords_b = 32,
altsyncram_component.operation_mode = "DUAL_PORT",
altsyncram_component.outdata_aclr_b = "CLEAR1",
altsyncram_component.outdata_reg_b = "CLOCK1",
altsyncram_component.power_up_uninitialized = "FALSE",
altsyncram_component.rdcontrol_reg_b = "CLOCK1",
altsyncram_component.widthad_a = 5,
altsyncram_component.widthad_b = 5,
altsyncram_component.width_a = 8,
altsyncram_component.width_b = 8,
altsyncram_component.width_byteena_a = 1;
endmodule
fifo_async_tb.v代码如下:
`timescale 1 ns/ 1 ps
module fifo_async_tb();
reg iResetN;
reg iReadClk;
reg iReadEnable;
reg iWriteClk;
reg [7:0] iWriteData;
reg iWriteEnable;
wire [7:0] oReadData;
wire oReadEmpty;
wire oWriteFull;
fifo_async fifo_async (
.iResetN ( iResetN )
,.iReadClk ( iReadClk )
,.oReadData ( oReadData )
,.oReadEmpty ( oReadEmpty )
,.iReadEnable ( iReadEnable )
,.iWriteClk ( iWriteClk )
,.iWriteData ( iWriteData )
,.iWriteEnable ( iWriteEnable )
,.oWriteFull ( oWriteFull )
);
always @ (*) begin
iResetN = 1;
iReadEnable = (oReadEmpty == 1'b0)?1'b1:1'b0;
iWriteEnable = (oWriteFull == 1'b0)?1'b1:1'b0;
end // always
// fast writting and slow reading
initial begin
forever begin
iWriteClk = 0;
#10 iWriteClk = 1;
#10;
end
end
initial begin
iWriteData = 0;
#20;
forever begin
iWriteData = (oWriteFull == 1'b0)?$random:8'b0;
#20;
end
end
initial begin
forever begin
iReadClk = 0;
#80 iReadClk = 1;
#80;
end
end
// fast reading and slow writting
// initial begin
// forever begin
// iWriteClk = 0;
// #40 iWriteClk = 1;
// #40;
// end
// end
// initial begin
// iWriteData = 0;
// #80;
// forever begin
// iWriteData = (oWriteFull == 1'b0)?$random:8'b0;
// #80;
// end
// end
// initial begin
// forever begin
// iReadClk = 0;
// #10 iReadClk = 1;
// #10;
// end
// end
endmodule
快读慢写,会产生读空(oReadEmpty),仿真结果如下:
快写慢读,会产生写满(oWriteFull),仿真结果如下:
其它问题:
1、仿真时需要添加quartus的库,以使用双口RAM。
2、FIFO与双口RAM不同,在于FIFO不能指定地址,其地址由计数器(自然二进制码)转Gray码来产生,Gray码翻转的bit较少,从而减少亚稳态。此外,FIFO可以用于同步两个异频/异相时钟。
3、这里的FIFO使用四象限法来设计,将RAM中的地址以最高两位来区分象限,
4、当写指针比读指针落后一个象限时,意味着写指针即将从追上读指针,FIFO处于“可能满”的状态。
5、当读指针比写指针落后一个象限时,意味着读指针即将从追上写指针,FIFO处于“可能空”的状态。
6、利用写时钟给读指针打两拍,以实现同步读指针;同理,利用读时钟给写指针打两拍,以实现同步写指针。
7、在实际应用中,很少使用full和empty标志,而是使用almost full和almost empty,将要满和将要空标志,给RAM的空间留有一定的余量。
8、在实际应用中,往往使用厂家提供的FIFO的IP核,但是我们需要学习异步FIFO的同步思想,所以自己写一下还是很有必要的。