同步FIFO学习
需要了解的一些事情
如何区分同步与异步
区分方式 | 描述 |
---|---|
狭义理解 | 电路中只有一个时钟, 为同步电路;有多个时钟为异步电路 |
广义理解 | 电路中只有多个时钟,但时钟之间具有固定的相位关系为同步,否则为异步 |
同步FIFO的作用
先入先出的数据交互方式,用于做数据的速率匹配,其本质为存储器。例如A,B两个模块传递数据,A发送数据的能力为3组,B接收数据的能力为1组,为了保证A发送的数据不至于丢失,设计FIFO作为缓冲。而同步异步则取决于A和B模块的时钟关系。
同步FIFO的组成
(1)FIFO写控制逻辑----产生写地址、写有效信号、FIFO写满、写错等状态信号
(2)FIFO读控制逻辑----产生读地址、读有效信号、FIFO读空、读错等状态信号
(3)FIFO存储实体(如REG\MEM)
FIFO的基本概念
如图所示的FIFO由8个位宽为8的REG组成,因此FIFO宽度与深度均为8。FIFO正常情况下写指针在读指针前,当写指针清零,从头开始写数据时,会出现读指针在写之前的情况。FIFO可以被写满,被读空,因此,满信号是在写逻辑中产生,空信号是在读逻辑中产生。
计数器判断模式下同步FIFO代码
计数器判断逻辑
本部分代码为读写速率相同时的同步FIFO计数器逻辑
always @(posedge clk or negedge rstn)begin
if(!rstn) begin //复位状态下计数器清零,当深度为8时,计数器需要4个bit位宽
cnt <= {$clog2(DEPTH){1'b0},{1'b0}}; //$clog2(DEPTH)为取深度的对数,8个字节深度则为3位
end
else begin //复位无效状态
if((rd_en && !empty) && (wr_en && !full)) begin //同时读写的状态,此时计数器不变
cnt <= cnt;
end
else if(rd_en && !empty) begin //读使能,且FIFO非空,计数器减一
cnt <= cnt-1'b1;
end
else if(wr_en && !full) begin //写使能,且FIFO未满,计数器加一
cnt <= cnt+1'b1;
end
end
end
写逻辑
integer i
always @(posedge clk or negedge rstn)begin
if(!rstn) begin //复位状态下FIFO清零
for(i = 0;i < DEPTH; i=i+1) begin
mem[i] <= {WIDTH{1'b0}};
end
end
else begin //复位无效状态
if(wr_en && !full) begin //写使能且未满,将输入信号wr_data写入FIFO
mem[wr_ptr] <= wr_data;
end
end
end
写指针逻辑
always @(posedge clk or negedge rstn)begin
if(!rstn) begin //复位状态下FIFO清零
wr_ptr <= {($clog2(DEPTH)){1'b0}}; //3'b0
end
else begin //复位无效状态
if(wr_en && !full) begin //写使能且未满,写指针加1
wr_ptr <= wr_ptr+{{($clog2(DEPTH)-1){1'b0}},{1'b1}}; //3'b001
end
end
end
写满判断逻辑
assign full = (cnt == DEPTH); //当计数器数值等于FIFO深度时,FIFO满
读逻辑
always @(posedge clk or negedge rstn)begin
if(!rstn) begin //复位状态下读寄存器清零
rd_data_r <= {(WIDTH){1'b0}}; //8'b0
end
else begin
if(rd_en && !empty) begin
rd_data_r <= mem[rd_ptr]; //读使能且非空,返回正确的值
end
else if(rd_en && empty) begin
rd_data_r <= bad_beef; //读使能且空,返回错误
end
else begin //读禁能,复位读寄存器
rd_data_r <= {(WIDTH){1'b0}}; //8'b0
end
end
end
读指针逻辑
always @(posedge clk or negedge rstn)begin
if(!rstn) begin //复位状态下FIFO清零
rd_ptr <= {($clog2(DEPTH)){1'b0}}; //3'b0
end
else begin //复位无效状态
if(rd_en && !empty) begin //写使能且未满,读指针加1
rd_ptr <= rd_ptr+{{($clog2(DEPTH)-1){1'b0}},{1'b1}}; //3'b001
end
end
end
读空判断逻辑
assign empty = (cnt == 0); //计数器值为0,读空
计数器判断模式下同步FIFO测试代码
module sync_fifo_tb;
parameter DW = 16;
parameter FD = 8;
reg clk,rstn,wr_en,rd_en;
reg[DW-1:0] wr_data;
reg[DW-1:0] rd_data;
wire full,empty;
wire[($clog2(FD)):0] cnt;
sync_fifo #(.WIDTH(DW),.DEPTH(FD)) sync_fifo_inst( //例化
.clk(clk),
.rstn(rstn),
.wr_en(wr_en),
.rd_en(rd_en),
.wr_data(wr_data),
.rd_data(rd_data),
.full(full),
.empty(empty),
.cnt(cnt)
);
always #10 clk = ~clk; //时钟,每10个时间单位翻转一次电平,周期为20个时间单位
reg[7:0] tempdata;
initial begin //激励
clk = 0; //信号复位
rstn = 0;
wr_en = 0;
rd_en = 0;
wr_data = 0;
#15 //延时15个时间单位
rstn = 1; //第15个时间单位,复位拉高
push(1); //入栈,数据为1,第15个时间单位写使能拉高,第35个时间单位写使能拉低
fork //并行块,35timer写使能拉高,入栈数据2,读使能高,出栈数据1,53timer tempdata数据给出,读使能拉低
push(2);
pop(tempdata);
join
push(10); //55timer,入栈10
push(20);
push(30);
push(40);
push(50);
push(60);
push(70); //此时FIFO写满,不再执行PUSH
push(80);
push(90);
push(100);
push(110);
push(120);
push(130);
pop(tempdata); //读出第三个数据10
push(tempdata); //将10入栈
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
push(140); //140入栈
pop(tempdata);
push(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
push(5);
pop(tempdata);
#100
$finish; //仿真结束
end
task push(input[7:0] data);
if(full)
$display("---Cannot push %d:Full---",data);
else begin
$display("Push",data);
wr_data = data; //入栈任务,将数据线上的数据送入写寄存器
wr_en = 1; //使能,FIFO入栈
@(posedge clk); //等待上升沿到来
#5 //延时5个timer
wr_en = 0; //拉低写使能,本次入栈结束
end
endtask
task pop(output[7:0] data);
if(empty)
$display("---Cannot Pop:Empty---")
else begin
rd_en = 1; //读使能,FIFO出栈
@(posedge clk); //等待上升沿到来
#3 //延时3个时间单位
rd_en = 0; //拉低读使能,结束出栈
data = rd_data; //将出栈数据给出
$display("---Poped",,data);
end
endtask
endmodule